Bug
This should produce an "undefined local macro" warning for `merp' on first encounter (when merp has no prior definition), but it doesn't. The reference `merp' is on the RHS of the definition — semantically a use-before-definition — yet the analyzer treats it as defined because the definition and reference share the same line.
Root Cause
The forward-reference detection in the analyzer has two levels of granularity:
-
AST-level (preorder index) — used in the AST pass, but never fires here because the parser embeds `merp' as a string value in the MacroDefNode, not as a child MacroRefNode. So the AST pass never sees the reference.
-
Token-level (line number) — the token pass catches the MACRO_REF_LOCAL token for `merp', but calls is_macro_defined with reference_index = undefined, falling back to the line-number comparison:
if (macro.definition_line > reference_line) {
return false; // forward reference
}
Since both definition_line and reference_line equal the same line, the strict > evaluates to false, and the macro is considered defined.
Fix Options
Option A — Change > to >= for same-line + add self-definition exception:
Treat same-line as a forward reference, but exempt cases where a macro intentionally captures its previous value (e.g., local x `x' + 1 where x was defined earlier). This requires tracking whether the macro had a prior definition.
Option B — Column-level comparison in the token pass:
Compare the reference token's character offset against the definition's name-end position. If the reference appears in the RHS of the same local statement, it's a forward reference. More precise but requires plumbing column positions into is_macro_defined.
Option C — Emit child MacroRefNodes from parseMacroDefinition:
Have the parser produce reference nodes for macro references in definition values, so the AST pass (which has preorder index granularity) handles them. This is the most architecturally clean fix but requires parser changes.
Relevant Code
src/analyzer/index.ts — is_macro_defined (line-level comparison), check_token_macro_references (token pass)
src/parser/index.ts — parseMacroDefinition (embeds value as string, no child nodes)
Stata Semantics Note
In Stata, local merp `merp' captures the current (pre-assignment) value of merp. If merp was never defined, the RHS evaluates to empty string. So this is genuinely a use-before-definition pattern on first encounter and should warn.
Bug
local merp `merp'This should produce an "undefined local macro" warning for
`merp'on first encounter (whenmerphas no prior definition), but it doesn't. The reference`merp'is on the RHS of the definition — semantically a use-before-definition — yet the analyzer treats it as defined because the definition and reference share the same line.Root Cause
The forward-reference detection in the analyzer has two levels of granularity:
AST-level (preorder index) — used in the AST pass, but never fires here because the parser embeds
`merp'as a string value in theMacroDefNode, not as a childMacroRefNode. So the AST pass never sees the reference.Token-level (line number) — the token pass catches the
MACRO_REF_LOCALtoken for`merp', but callsis_macro_definedwithreference_index = undefined, falling back to the line-number comparison:Since both
definition_lineandreference_lineequal the same line, the strict>evaluates tofalse, and the macro is considered defined.Fix Options
Option A — Change
>to>=for same-line + add self-definition exception:Treat same-line as a forward reference, but exempt cases where a macro intentionally captures its previous value (e.g.,
local x `x' + 1wherexwas defined earlier). This requires tracking whether the macro had a prior definition.Option B — Column-level comparison in the token pass:
Compare the reference token's character offset against the definition's name-end position. If the reference appears in the RHS of the same
localstatement, it's a forward reference. More precise but requires plumbing column positions intois_macro_defined.Option C — Emit child
MacroRefNodes fromparseMacroDefinition:Have the parser produce reference nodes for macro references in definition values, so the AST pass (which has preorder index granularity) handles them. This is the most architecturally clean fix but requires parser changes.
Relevant Code
src/analyzer/index.ts—is_macro_defined(line-level comparison),check_token_macro_references(token pass)src/parser/index.ts—parseMacroDefinition(embeds value as string, no child nodes)Stata Semantics Note
In Stata,
local merp `merp'captures the current (pre-assignment) value ofmerp. Ifmerpwas never defined, the RHS evaluates to empty string. So this is genuinely a use-before-definition pattern on first encounter and should warn.