{"record":{"id":"0ac091f48dbb74f2","repo":"antlr/antlr4","slug":"insert-op-iop-within-boundaries-of-previous-rop","errorCode":null,"errorMessage":"insert op {iop} within boundaries of previous {rop}","messagePattern":"insert op (.+?) within boundaries of previous (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"runtime/Java/src/org/antlr/v4/runtime/TokenStreamRewriter.java","lineNumber":559,"sourceCode":"\t\t\t\t\telse if ( InsertBeforeOp.class.isInstance(prevIop) ) { // combine objects\n\t\t\t\t\t\t// convert to strings...we're in process of toString'ing\n\t\t\t\t\t\t// whole token buffer so no lazy eval issue with any templates\n\t\t\t\t\t\tiop.text = catOpText(iop.text, prevIop.text);\n\t\t\t\t\t\t// delete redundant prior insert\n\t\t\t\t\t\trewrites.set(prevIop.instructionIndex, null);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// look for replaces where iop.index is in range; error\n\t\t\tList<? extends ReplaceOp> prevReplaces = getKindOfOps(rewrites, ReplaceOp.class, i);\n\t\t\tfor (ReplaceOp rop : prevReplaces) {\n\t\t\t\tif ( iop.index == rop.index ) {\n\t\t\t\t\trop.text = catOpText(iop.text,rop.text);\n\t\t\t\t\trewrites.set(i, null);\t// delete current insert\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ( iop.index >= rop.index && iop.index <= rop.lastIndex ) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"insert op \"+iop+\" within boundaries of previous \"+rop);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// System.out.println(\"rewrites after=\"+rewrites);\n\t\tMap<Integer, RewriteOperation> m = new HashMap<Integer, RewriteOperation>();\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; // ignore deleted ops\n\t\t\tif ( m.get(op.index)!=null ) {\n\t\t\t\tthrow new Error(\"should only be one op per index\");\n\t\t\t}\n\t\t\tm.put(op.index, op);\n\t\t}\n\t\t//System.out.println(\"index to op: \"+m);\n\t\treturn m;\n\t}\n\n\tprotected String catOpText(Object a, Object b) {","sourceCodeStart":541,"sourceCodeEnd":577,"githubUrl":"https://github.com/antlr/antlr4/blob/7d5770395bb7b02eb56e7c62662cb1d7c08f42a3/runtime/Java/src/org/antlr/v4/runtime/TokenStreamRewriter.java#L541-L577","documentation":"TokenStreamRewriter rejects an InsertBeforeOp whose token index falls strictly inside a previously queued ReplaceOp's range (index > rop.index && index <= rop.lastIndex). Inserting text into the middle of a region that will be wholly replaced has no well-defined meaning, so the rewriter fails fast. Note insert-at-index == replace-index is legal and is folded into the replace text.","triggerScenarios":"rewriter.insertBefore(t, text) or insertAfter(t, text) where t's index is within (from, to] of an earlier replace(from, to, ...); insertAfter a replaced token (index from+..to) since insertAfter maps to insertBefore of index+1.","commonSituations":"Transformation pipelines that replace an expression but also try to insert a token inside it; visitors adding separators or annotations into ranges another rule already rewrote.","solutions":["Move the inserted text outside the replaced range, or prepend/append it to the replace text yourself","Check queued replaces first: only insert when the index is not inside any (from, to] replace range","Restructure the pass so one visitor owns a token range: either it replaces it or it inserts into it, never both"],"exampleFix":"// before\nrewriter.replace(5, 10, \"newExpr\");\nrewriter.insertAfter(tokens.get(7), \";\"); // index 8 inside (5,10] -> throws\n\n// after\nrewriter.replace(5, 10, \"newExpr;\"); // fold the insertion into the replacement","handlingStrategy":"validation","validationCode":"static boolean insertSafe(int idx, List<int[]> replaces) {\n    for (int[] r : replaces)\n        if (idx > r[0] && idx <= r[1]) return false; // strictly inside a replaced range\n    return true;\n}\nif (insertSafe(t.getTokenIndex(), claimedReplaces)) rewriter.insertBefore(t, text);","typeGuard":null,"tryCatchPattern":"try { rewriter.getText(); }\ncatch (IllegalArgumentException e) { /* fold the insert text into the covering replace op and re-run */ }","preventionTips":["Prefer folding insertions into the replace text of the owning rule","Remember insertAfter(n) becomes insertBefore(n+1) and can land inside a replace range","Insert at index == replace.from is legal (prepended), use it as the escape hatch"],"tags":["antlr","token-stream-rewriter","insert","overlap"],"backgroundTag":null,"analyzedSha":"7d5770395bb7b02eb56e7c62662cb1d7c08f42a3","analyzedAt":"2026-08-14T14:47:56.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}