antlr/antlr4 · error · IllegalArgumentException
replace op boundaries of {rop} overlap with previous {prevRo
Error message
replace op boundaries of {rop} overlap with previous {prevRop} What it means
During getText()/reduceToSingleOperationPerIndex, TokenStreamRewriter detects two queued ReplaceOps (or a replace plus a delete, i.e. text==null) whose token ranges are not disjoint. Overlapping deletes are merged automatically, but any other overlap is rejected because the output text would be ambiguous. The message prints both operations with their ranges.
Source
Thrown at runtime/Java/src/org/antlr/v4/runtime/TokenStreamRewriter.java:522
if ( prevRop.index>=rop.index && prevRop.lastIndex <= rop.lastIndex ) {
// delete replace as it's a no-op.
rewrites.set(prevRop.instructionIndex, null);
continue;
}
// throw exception unless disjoint or identical
boolean disjoint =
prevRop.lastIndex<rop.index || prevRop.index > rop.lastIndex;
// Delete special case of replace (text==null):
// D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right)
if ( prevRop.text==null && rop.text==null && !disjoint ) {
//System.out.println("overlapping deletes: "+prevRop+", "+rop);
rewrites.set(prevRop.instructionIndex, null); // kill first delete
rop.index = Math.min(prevRop.index, rop.index);
rop.lastIndex = Math.max(prevRop.lastIndex, rop.lastIndex);
System.out.println("new rop "+rop);
}
else if ( !disjoint ) {
throw new IllegalArgumentException("replace op boundaries of "+rop+" overlap with previous "+prevRop);
}
}
}
// WALK INSERTS
for (int i = 0; i < rewrites.size(); i++) {
RewriteOperation op = rewrites.get(i);
if ( op==null ) continue;
if ( !(op instanceof InsertBeforeOp) ) continue;
InsertBeforeOp iop = (InsertBeforeOp)rewrites.get(i);
// combine current insert with prior if any at same index
List<? extends InsertBeforeOp> prevInserts = getKindOfOps(rewrites, InsertBeforeOp.class, i);
for (InsertBeforeOp prevIop : prevInserts) {
if ( prevIop.index==iop.index ) {
if ( InsertAfterOp.class.isInstance(prevIop) ) {
iop.text = catOpText(prevIop.text, iop.text);
rewrites.set(prevIop.instructionIndex, null);
}View on GitHub (pinned to 7d5770395b)
Solutions
- Make the ranges disjoint: track which token indexes you have already rewritten and skip/split overlapping operations
- Merge the two operations yourself into one replace spanning min(from)..max(to) with the combined text
- Use separate program names and call getText once per program if the edits are intentionally independent passes
Example fix
// before
rewriter.replace(0, 5, "A");
rewriter.replace(3, 8, "B"); // overlaps 0..5 -> throws at getText()
// after
Set<Integer> claimed = new HashSet<>();
void replaceDisjoint(int from, int to, String text) {
for (int i = from; i <= to; i++)
if (!claimed.add(i)) throw new IllegalStateException("overlap at " + i);
rewriter.replace(from, to, text);
} Defensive patterns
Strategy: validation
Validate before calling
static boolean overlaps(int aFrom, int aTo, int bFrom, int bTo) {
return !(aTo < bFrom || bTo < aFrom);
}
// before each replace(from,to):
if (claimedRanges.stream().anyMatch(r -> overlaps(r[0], r[1], from, to))) {
// merge or skip instead of queuing
} Try / catch
try { rewriter.getText(); } // validation happens lazily at getText
catch (IllegalArgumentException e) { /* e mentions 'overlap': merge/split the offending ops and rebuild the program */ } Prevention
- Track claimed token ranges per program and merge overlapping edits eagerly
- One owner per token range: coordinate visitors so ranges never intersect
- Call getText() early in tests to surface overlaps before production
When it happens
Trigger: Queuing replace(0,5,...) then replace(3,8,...) on the same program; mixing insert-driven replaces that end up overlapping; accumulating rewrites from multiple rules/visitors that touch adjacent or nested token ranges without coordination.
Common situations: Source-transformation passes where several visitors each rewrite parts of the tree and their token ranges intersect; incremental edits applied on top of an earlier edit program; replacing a statement that another rule already replaced.
Related errors
- replace op boundaries of {rop} overlap with previous {prevRo
- insert op {iop} within boundaries of previous {rop}
- insert op {iop} within boundaries of previous {rop}
- replace: range invalid: {from}..{to}(size={size})
- FileStream is only available when running in Node!
AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14).
Data as JSON: /api/errors/5115bf78623a7d45.
Report an issue: GitHub.