Description
The Python TokenStreamRewriter class has a bug when there's a mixture of both Replace and Insert operations queued up, and some of the Insert operations overlap. The "Walk inserts" section of _reduceToSingleOperationPerIndex uses the wrong index when combining Inserts, which risks setting the wrong operation to None.
To Reproduce
- Define a list of
rewrites which has a Replace, then two Inserts which overlap. Eg [ReplaceOp(tokens, 2, 4, "foo"), InsertBeforeOp(tokens, 6, "bar"), InsertBeforeOp(tokens, 6, "baz")]
- Try call
_reduceToSingleOperationPerIndex (either directly in a unit test, or by calling getText()
- Expected behaviour: should combine the Insert operations and leave the ReplaceOp untouched, leaving:
[ReplaceOp(tokens, 2, 4, "foo"), None, InsertBeforeOp(tokens, 6, "bazbar")]
- Actual behaviour: Incorrectly sets the
ReplaceOp to None because the Index reference is wrong, leaving: [None, InsertBeforeOp(tokens, 6, "bar"), InsertBeforeOp(tokens, 6, "baz")]
- It then fails with a
ValueError because two different operations are still defined for the same index, 6.
Suggested Fix
The "# Walk Replaces" section of the same method is already solving this issue by referencing .instructionIndex. I suspect that Inserts don't do the same is an oversight. So I'd suggest implementing the same approach for Inserts checks as well:
- Change both line 177 and line 180:
- from:
rewrites[prev_index] = None
- to:
rewrites[prevIop.instructionIndex] = None
- After that change,
prev_index is no longer needed anywhere, so we can remove the enumerate call and change line 174:
- from:
for prev_index, prevIop in enumerate(prevInserts):
- to:
for prevIop in prevInserts: (this is same code as the Walk Replace section)
Description
The Python
TokenStreamRewriterclass has a bug when there's a mixture of both Replace and Insert operations queued up, and some of the Insert operations overlap. The "Walk inserts" section of_reduceToSingleOperationPerIndexuses the wrong index when combining Inserts, which risks setting the wrong operation toNone.To Reproduce
rewriteswhich has a Replace, then two Inserts which overlap. Eg[ReplaceOp(tokens, 2, 4, "foo"), InsertBeforeOp(tokens, 6, "bar"), InsertBeforeOp(tokens, 6, "baz")]_reduceToSingleOperationPerIndex(either directly in a unit test, or by callinggetText()[ReplaceOp(tokens, 2, 4, "foo"), None, InsertBeforeOp(tokens, 6, "bazbar")]ReplaceOptoNonebecause the Index reference is wrong, leaving:[None, InsertBeforeOp(tokens, 6, "bar"), InsertBeforeOp(tokens, 6, "baz")]ValueErrorbecause two different operations are still defined for the same index, 6.Suggested Fix
The "# Walk Replaces" section of the same method is already solving this issue by referencing
.instructionIndex. I suspect that Inserts don't do the same is an oversight. So I'd suggest implementing the same approach for Inserts checks as well:rewrites[prev_index] = Nonerewrites[prevIop.instructionIndex] = Noneprev_indexis no longer needed anywhere, so we can remove theenumeratecall and change line 174:for prev_index, prevIop in enumerate(prevInserts):for prevIop in prevInserts:(this is same code as the Walk Replace section)