{"record":{"id":"5115bf78623a7d45","repo":"antlr/antlr4","slug":"replace-op-boundaries-of-rop-overlap-with-previo","errorCode":null,"errorMessage":"replace op boundaries of {rop} overlap with previous {prevRop}","messagePattern":"replace op boundaries of (.+?) overlap with previous (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"runtime/Java/src/org/antlr/v4/runtime/TokenStreamRewriter.java","lineNumber":522,"sourceCode":"\t\t\t\tif ( prevRop.index>=rop.index && prevRop.lastIndex <= rop.lastIndex ) {\n\t\t\t\t\t// delete replace as it's a no-op.\n\t\t\t\t\trewrites.set(prevRop.instructionIndex, null);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// throw exception unless disjoint or identical\n\t\t\t\tboolean disjoint =\n\t\t\t\t\tprevRop.lastIndex<rop.index || prevRop.index > rop.lastIndex;\n\t\t\t\t// Delete special case of replace (text==null):\n\t\t\t\t// D.i-j.u D.x-y.v\t| boundaries overlap\tcombine to max(min)..max(right)\n\t\t\t\tif ( prevRop.text==null && rop.text==null && !disjoint ) {\n\t\t\t\t\t//System.out.println(\"overlapping deletes: \"+prevRop+\", \"+rop);\n\t\t\t\t\trewrites.set(prevRop.instructionIndex, null); // kill first delete\n\t\t\t\t\trop.index = Math.min(prevRop.index, rop.index);\n\t\t\t\t\trop.lastIndex = Math.max(prevRop.lastIndex, rop.lastIndex);\n\t\t\t\t\tSystem.out.println(\"new rop \"+rop);\n\t\t\t\t}\n\t\t\t\telse if ( !disjoint ) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"replace op boundaries of \"+rop+\" overlap with previous \"+prevRop);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// WALK INSERTS\n\t\tfor (int i = 0; i < rewrites.size(); i++) {\n\t\t\tRewriteOperation op = rewrites.get(i);\n\t\t\tif ( op==null ) continue;\n\t\t\tif ( !(op instanceof InsertBeforeOp) ) continue;\n\t\t\tInsertBeforeOp iop = (InsertBeforeOp)rewrites.get(i);\n\t\t\t// combine current insert with prior if any at same index\n\t\t\tList<? extends InsertBeforeOp> prevInserts = getKindOfOps(rewrites, InsertBeforeOp.class, i);\n\t\t\tfor (InsertBeforeOp prevIop : prevInserts) {\n\t\t\t\tif ( prevIop.index==iop.index ) {\n\t\t\t\t\tif ( InsertAfterOp.class.isInstance(prevIop) ) {\n\t\t\t\t\t\tiop.text = catOpText(prevIop.text, iop.text);\n\t\t\t\t\t\trewrites.set(prevIop.instructionIndex, null);\n\t\t\t\t\t}","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/antlr/antlr4/blob/7d5770395bb7b02eb56e7c62662cb1d7c08f42a3/runtime/Java/src/org/antlr/v4/runtime/TokenStreamRewriter.java#L504-L540","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nrewriter.replace(0, 5, \"A\");\nrewriter.replace(3, 8, \"B\"); // overlaps 0..5 -> throws at getText()\n\n// after\nSet<Integer> claimed = new HashSet<>();\nvoid replaceDisjoint(int from, int to, String text) {\n    for (int i = from; i <= to; i++)\n        if (!claimed.add(i)) throw new IllegalStateException(\"overlap at \" + i);\n    rewriter.replace(from, to, text);\n}","handlingStrategy":"validation","validationCode":"static boolean overlaps(int aFrom, int aTo, int bFrom, int bTo) {\n    return !(aTo < bFrom || bTo < aFrom);\n}\n// before each replace(from,to):\nif (claimedRanges.stream().anyMatch(r -> overlaps(r[0], r[1], from, to))) {\n    // merge or skip instead of queuing\n}","typeGuard":null,"tryCatchPattern":"try { rewriter.getText(); } // validation happens lazily at getText\ncatch (IllegalArgumentException e) { /* e mentions 'overlap': merge/split the offending ops and rebuild the program */ }","preventionTips":["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"],"tags":["antlr","token-stream-rewriter","overlap","rewrite"],"backgroundTag":null,"analyzedSha":"7d5770395bb7b02eb56e7c62662cb1d7c08f42a3","analyzedAt":"2026-08-14T14:47:56.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}