antlr/antlr4 · error · ValueError
replace op boundaries of {} overlap with previous {}
Error message
replace op boundaries of {} overlap with previous {} What it means
During reduceProgramToSingleCommandPerIndex(), the rewriter folds overlapping rewrite operations into one command per token index. Two ReplaceOps whose ranges intersect (and are not strictly nested, and not both pure deletions) cannot be merged unambiguously, so ANTLR raises this ValueError instead of guessing the resulting text.
Source
Thrown at runtime/Python3/src/antlr4/TokenStreamRewriter.py:167
rewrites[iop.instructionIndex] = None
rop.text = '{}{}'.format(iop.text, rop.text)
elif all((iop.index > rop.index, iop.index <= rop.last_index)):
rewrites[iop.instructionIndex] = None
# Drop any prior replaces contained within
prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]
for prevRop in prevReplaces:
if all((prevRop.index >= rop.index, prevRop.last_index <= rop.last_index)):
rewrites[prevRop.instructionIndex] = None
continue
isDisjoint = any((prevRop.last_index<rop.index, prevRop.index>rop.last_index))
if all((prevRop.text is None, rop.text is None, not isDisjoint)):
rewrites[prevRop.instructionIndex] = None
rop.index = min(prevRop.index, rop.index)
rop.last_index = min(prevRop.last_index, rop.last_index)
print('New rop {}'.format(rop))
elif (not(isDisjoint)):
raise ValueError("replace op boundaries of {} overlap with previous {}".format(rop, prevRop))
# Walk inserts
for i, iop in enumerate(rewrites):
if any((iop is None, not isinstance(iop, TokenStreamRewriter.InsertBeforeOp))):
continue
prevInserts = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.InsertBeforeOp)]
for prev_index, prevIop in enumerate(prevInserts):
if prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertBeforeOp:
iop.text += prevIop.text
rewrites[prev_index] = None
elif prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertAfterOp:
iop.text = prevIop.text + iop.text
rewrites[prev_index] = None
# look for replaces where iop.index is in range; error
prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]
for rop in prevReplaces:
if iop.index == rop.index:
rop.text = iop.text + rop.textView on GitHub (pinned to 7d5770395b)
Solutions
- Use a separate program per independent edit batch: rewriter.replace('pass1', ...) / rewriter.replace('pass2', ...), or call rewriter.getText() between batches.
- Merge overlapping replaces yourself before submitting: coalesce ranges and concatenate/decide text deterministically.
- Sort edits by start index and rewrite them so each range is disjoint (extend ranges over gaps to eliminate partial overlaps).
Example fix
// before rewriter.replace(2, 5, 'a') rewriter.replace(4, 7, 'b') // partial overlap -> ValueError on getText() // after rewriter.replace(2, 7, 'ab') # single merged, deterministic edit
Defensive patterns
Strategy: validation
Validate before calling
def assert_disjoint_replaces(edits):
# edits: list of (start, end, text) sorted by start
edits = sorted(edits, key=lambda e: e[0])
for (s1, e1, _), (s2, e2, _) in zip(edits, edits[1:]):
if s2 <= e1:
raise ValueError('edits %d..%d and %d..%d overlap; merge them first' % (s1, e1, s2, e2))
return edits
for start, stop, text in assert_disjoint_replaces(my_edits):
rewriter.replace(start, stop, text) Try / catch
try:
out = rewriter.getText()
except ValueError as e:
if 'overlap' in str(e):
# fall back to per-pass programs so edits never co-reside
rewriter = TokenStreamRewriter(stream)
else:
raise Prevention
- Use a distinct program name per independent edit pass
- Sort and merge overlapping replaces before submitting them
- Call getText() between passes to roll the current program
When it happens
Trigger: Calling rewriter.replace() (or replaceRange/replaceRangeTokens) twice on the same program with ranges that partially overlap, e.g. replace(2,5,'a') then replace(4,7,'b'); nested ranges (prev fully inside new) and disjoint ranges are fine, partial overlap with non-None text is not.
Common situations: Applying many edits generated from different AST visits without sorting/merging; mixing inserts at the edge of a replaced range; using the default program name for edits intended for separate output passes.
Related errors
- replace: range invalid: {}..{}(size={})
- insert op {} within boundaries of previous {}
- should be only one op per index
- Invalid state number.
- delegates
AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14).
Data as JSON: /api/errors/44acb7d6ee7c0d53.
Report an issue: GitHub.