Skip to content

False warnings for token references in imported grammar files #329

Description

@BrentRector

Problem

The extension analyzes each .g4 file independently. When an imported grammar fragment (one that is import-ed by a master grammar) is opened, the extension cannot resolve token references because it doesn't inherit the importing grammar's tokenVocab. Every token reference is flagged as "Unknown token reference" — dozens to hundreds of false warnings per file.

The ANTLR4 tool handles this correctly: it resolves token references in imported grammars within the context of the master grammar's tokenVocab. The extension should do the same.

Minimal Reproduction

Create three files in the same directory:

Lexer.g4:

lexer grammar Lexer;
PLUS : '+' ;
NUM  : [0-9]+ ;
WS   : [ \t\r\n]+ -> skip ;

ExprRules.g4:

parser grammar ExprRules;
expr : NUM (PLUS NUM)* ;

Parser.g4:

parser grammar Parser;
options { tokenVocab = Lexer; }
import ExprRules;
program : expr EOF ;

Open ExprRules.g4 in VSCode with the ANTLR4 extension installed.

  • Expected: No warnings — NUM and PLUS resolve via the import chain through Parser.g4's tokenVocab.
  • Actual: Warnings on NUM and PLUS as unknown token references.

Impact

For large grammars split across many files (e.g., a COBOL grammar with 7 imported fragments and 300+ token types), the false warnings make the diagnostics panel unusable. The same issue affects cross-rule references between sibling imported files — if FileA.g4 and FileB.g4 are both imported by the same master, rules defined in one are not visible to the other.

Root Cause

Dependency resolution in addAsReferenceTo() (src/backend/SourceContext.ts:905-922) is one-directional. When grammar A imports grammar B:

context.references.push(this);                          // B records that A references it
this.symbolTable.addDependencies(context.symbolTable);  // A's symbol table gets B's symbols

A gets access to B's symbols, but B never gets access to A's dependencies (including the lexer via tokenVocab). When SemanticListener runs on B, checkSymbolExistence() searches B's symbol table and its (empty) dependencies, so all token lookups fail.

The references field already tracks which grammars import a given grammar, but this reverse lookup is only used for reference counting and getAllSymbols — it is not consulted during semantic analysis.

Possible Approaches

I see three ways to fix this and would appreciate your guidance on which you'd prefer:

Option 1: Propagate dependencies downward in addAsReferenceTo()

When the import relationship is established, also add the importing grammar's dependencies to the imported grammar's symbol table. This makes the dependency graph match ANTLR4's resolution semantics: an imported grammar inherits its importer's token vocabulary and sibling imports.

Option 2: Search upward through references during semantic analysis

Modify symbolExistsInGroup() or the SemanticListener to also search through the referencing grammars' dependency chains when a symbol isn't found locally. This avoids mutating the dependency graph.

Option 3: Inject dependencies at the SemanticListener level

Before running semantic analysis on an imported grammar, temporarily add the importing grammar's dependencies to the symbol table. This keeps the change localized to the analysis phase.

I'm happy to submit a PR for whichever approach you prefer. Option 1 seems cleanest to me but I wanted to check whether there are edge cases or design considerations I'm missing (e.g., grammars imported by multiple masters with different tokenVocab settings, or performance/lifecycle concerns with bidirectional dependencies).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions