The Modifying(<Range>) { <Block> } construct always evaluates even if the block is empty.
With TextKit integration, this means
- an undo group is being started (and ended)
NSTextView.shouldChangeText(in:replacementString:) and didChangeText() are being run to guard against unwanted changes
With syntax highlighting in the text storage, you may end up processing the text for what's essentially a no-op.
How to test
To get an empty block, use a for-loop to trigger the buildArray path of the result builder, but without any actual iterations:
Modifying(selectedRange){ _ infor_in0..<0{Insert(0){"loop never runs"}}}I'm not sure whether we can figure out at all whether a result builder produces nothing (i.e. empty array).
Complete test case
This test fails with a thrown error at try buffer.evaluate because the text view doesn't permit changes in the range.
This should not be a problem, because the range is not actually changed.
func testModifying_EmptyLoopBlock_SkipsEvaluation()throws{classTextViewSpy:NSTextView{vardidCallShouldChangeText=falsevardidCallDidChangeText=falseoverridefunc shouldChangeText(in affectedCharRange:NSRange, replacementString:String?)->Bool{
didCallShouldChangeText =truereturnfalse // Would abort modification an error
}overridefunc didChangeText(){
didCallDidChangeText =true}}lettextViewSpy=TextViewSpy()
textViewSpy.string ="Lorem ipsum."letbuffer=NSTextViewBuffer(textView: textViewSpy)letselectedRange:SelectedRange=.init(location:6, length:5)assertBufferState(buffer,"Lorem ipsum.ˇ")try buffer.evaluate{Modifying(selectedRange){ _ infor_in0..<0{Insert(0){"loop never runs"}}}}XCTAssertFalse(textViewSpy.didCallShouldChangeText)XCTAssertFalse(textViewSpy.didCallDidChangeText)}
The
Modifying(<Range>) { <Block> }construct always evaluates even if the block is empty.With TextKit integration, this means
NSTextView.shouldChangeText(in:replacementString:)anddidChangeText()are being run to guard against unwanted changesWith syntax highlighting in the text storage, you may end up processing the text for what's essentially a no-op.
How to test
To get an empty block, use a for-loop to trigger the
buildArraypath of the result builder, but without any actual iterations:I'm not sure whether we can figure out at all whether a result builder produces nothing (i.e. empty array).
Complete test case
This test fails with a thrown error at
try buffer.evaluatebecause the text view doesn't permit changes in the range.This should not be a problem, because the range is not actually changed.