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).
Problem
The extension analyzes each
.g4file independently. When an imported grammar fragment (one that isimport-ed by a master grammar) is opened, the extension cannot resolve token references because it doesn't inherit the importing grammar'stokenVocab. 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:
ExprRules.g4:
Parser.g4:
Open
ExprRules.g4in VSCode with the ANTLR4 extension installed.NUMandPLUSresolve via the import chain throughParser.g4'stokenVocab.NUMandPLUSas 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.g4andFileB.g4are 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:A gets access to B's symbols, but B never gets access to A's dependencies (including the lexer via
tokenVocab). WhenSemanticListenerruns on B,checkSymbolExistence()searches B's symbol table and its (empty) dependencies, so all token lookups fail.The
referencesfield already tracks which grammars import a given grammar, but this reverse lookup is only used for reference counting andgetAllSymbols— 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
referencesduring semantic analysisModify
symbolExistsInGroup()or theSemanticListenerto 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
SemanticListenerlevelBefore 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
tokenVocabsettings, or performance/lifecycle concerns with bidirectional dependencies).