Copy repeated childless IR nodes one kind at a time (#1467)

This commit is contained in:
Chris Tate
2026-10-19 23:49:58 -05:00
committed by GitHub
+67 -3
View File
@@ -42,14 +42,15 @@ export function unshareNodes(mod: IrModule): IrModule {
}
if (affected.size === 0) return mod;
// Rebuilding a function copies every node that has children; a repeated
// childless node reads back as a fresh object at each of its sites there.
// childless node gets its own shallow copy at each of its sites there.
// Both kinds of copy keep sharing the type and location objects.
const expr = (node: IrExpr): IrExpr => {
const copy = mapExprChildren(node, expr, stmt);
return copy !== node || !repeated.has(node) ? copy : reparseExpr(node);
return copy !== node || !repeated.has(node) ? copy : copyChildlessExpr(node);
};
const stmt = (node: IrStmt): IrStmt => {
const copy = mapStmtChildren(node, expr, stmt);
return copy !== node || !repeated.has(node) ? copy : reparseStmt(node);
return copy !== node || !repeated.has(node) ? copy : copyChildlessStmt(node);
};
return {
...mod,
@@ -58,3 +59,66 @@ export function unshareNodes(mod: IrModule): IrModule {
),
};
}
/** A shallow copy of a node that mapExprChildren returns unchanged. Each
* case spreads one node kind, so the copy is a plain record copy of that
* kind. Any other kind is copied through the encoding. */
function copyChildlessExpr(node: IrExpr): IrExpr {
switch (node.kind) {
case "varRef":
return { ...node };
case "strLit":
return { ...node };
case "numLit":
return { ...node };
case "boolLit":
return { ...node };
case "unitLit":
return { ...node };
case "closure":
return { ...node };
case "moduleNsRef":
return { ...node };
case "incDec":
return { ...node };
case "chainRecv":
return { ...node };
case "regexLit":
return { ...node };
case "templateStrings":
return { ...node };
case "setNew":
return { ...node };
case "mapNew":
return { ...node };
case "dynObjLit":
return { ...node };
case "selfRef":
return { ...node };
case "promiseWithResolvers":
return { ...node };
case "classRef":
return { ...node };
case "fieldAbsent":
return { ...node };
default:
return reparseExpr(node);
}
}
/** A shallow copy of a statement that mapStmtChildren returns unchanged,
* one case per kind as in copyChildlessExpr. */
function copyChildlessStmt(node: IrStmt): IrStmt {
switch (node.kind) {
case "break":
return { ...node };
case "continue":
return { ...node };
case "runtimeFence":
return { ...node };
case "rethrow":
return { ...node };
default:
return reparseStmt(node);
}
}