Records now hoist their fields if they reference other fields so we can have circular

This commit is contained in:
Dustin Swan 2026-04-01 19:43:45 -06:00
parent a76f4cc332
commit d3248be4d9
No known key found for this signature in database
GPG key ID: 30D46587E2100467
2 changed files with 75 additions and 48 deletions

View file

@ -69,11 +69,36 @@ export function compile(ast: AST, ctx: CompileCtx = defaultCtx): string {
return `${compile(ast.func, ctx)}(${args})`;
case 'record': {
const fieldNames = new Set(
ast.entries.filter(e => e.kind === 'field').map(e => e.key)
);
// Check if any field references another
const needsHoist = ast.entries.some(entry =>
entry.kind === 'field' &&
[...freeVars(entry.value)].some(v =>
fieldNames.has(v) && v !== entry.key && !ctx.bound.has(v) && !ctx.topLevel.has(v)
)
);
if (needsHoist) {
const bindings: string[] = [];
const names: string[] = [];
for (const entry of ast.entries) {
if (entry.kind === 'spread') continue;
const safe = sanitizeName(entry.key);
bindings.push(`const ${safe} = ${compile(entry.value, { ...ctx, bound: new Set([...ctx.bound, ...fieldNames]) })};`);
names.push(`${JSON.stringify(entry.key)}: ${safe}`);
}
return `(() => { ${bindings.join(' ')} return {${names.join(', ')}}; })()`;
}
// Simple record, no hoisting
const parts = ast.entries.map(entry =>
entry.kind === 'spread'
? `...${compile(entry.expr, ctx)}`
: `${JSON.stringify(entry.key)}: ${compile(entry.value, ctx)}`
)
entry.kind === 'spread'
? `...${compile(entry.expr, ctx)}`
: `${JSON.stringify(entry.key)}: ${compile(entry.value, ctx)}`
)
return `({${parts.join(', ')}})`;
}