Skip to content

Undefined local diagnostic missed when macro is referenced in its own definition on the same line #97

@jbearak

Description

@jbearak

Bug

local merp `merp'

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:

  1. 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.

  2. 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.tsis_macro_defined (line-level comparison), check_token_macro_references (token pass)
  • src/parser/index.tsparseMacroDefinition (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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions