Class SymbolsAndScopes
Now also uses a transient CodeFlowAnalyzer to assess whether variables in instruction blocks have been initialised.
-
Constructor Summary
ConstructorsConstructorDescriptionSymbolsAndScopes(ParsedModule parsedModule, ScopeStack scopeStack) Create a new instance for symbol and scope management. -
Method Summary
Modifier and TypeMethodDescriptionvoiddefineScopedSymbol(IScopedSymbol symbol, org.antlr.v4.runtime.tree.ParseTree node) Record a new scoped symbol in the current scope on the stack.voidensureParameterisedTypeExpanded(Object typeOrScope, SemanticErrorReporter errorListener) THE pre-step of every phase-3 member resolution on a type: whentypeOrScopeis a PARAMETERISED type, resolve it here so its methods and operators are expanded - FULL_RESOLUTION cannot wait for the generic's own definition to be walked, because it may be referenced first.voidenterModuleScopedSymbol(IScopedSymbol symbol, org.antlr.v4.runtime.tree.ParseTree node, SymbolChecker symbolChecker) To be used when defining a high level symbol at module scope.voidenterNewConstant(ISymbol symbol, org.antlr.v4.runtime.tree.ParseTree node, SymbolChecker symbolChecker) Create a new constant as declared in the constants section and records the symbol.voidenterNewLiteral(ISymbol symbol, org.antlr.v4.runtime.tree.ParseTree node) For literals, we only record in the parsedModule.voidenterNewScope(IScope scope, org.antlr.v4.runtime.tree.ParseTree node) Enter a new scope and record in both the parsed module and push on to the working scope stack.voidenterNewScopedSymbol(IScopedSymbol symbol, org.antlr.v4.runtime.tree.ParseTree node) Enter a new scoped symbol and record in parsed module as a symbol and as a scope.voidenterNewSymbol(ISymbol symbol, org.antlr.v4.runtime.tree.ParseTree node) A new symbol has been encountered and is defined within the current scope in the scope stack and recorded in the parsedModule against the appropriate node.enterScope(org.antlr.v4.runtime.tree.ParseTree ctx) Ensure that the correct scope is pushed on to the stack.voidenterScope(IScope scope) To be used to ensure that a scope has been pushed on to the scopeStack.voidNormally called at the point where the parser listener exits a scope.Provide access to the set of code flow analyzers being used for flow analysis.getRecordedScope(org.antlr.v4.runtime.tree.ParseTree node) getRecordedSymbol(org.antlr.v4.runtime.tree.ParseTree node) Provides access to the top of the scope stack.getUninitialisedVariables(IScope inScope) booleanbooleanisErrorResultAccessSafe(ISymbol identifierSymbol) booleanbooleanisGetOptionalAccessSafe(ISymbol identifierSymbol) booleanisInitialiserReplaced(ISymbol returningVariable, IScope inScope) True when every path to the end of the scope has given the returning variable a new value - the definite assignment E08050 asks of a returning variable with no initialiser, asked of one that has an initialiser - so the value it was declared with cannot be the one returned.booleanisNextIteratorAccessSafe(ISymbol identifierSymbol) booleanisOkResultAccessSafe(ISymbol identifierSymbol) booleanisSymbolAccessSafe(ISymbol identifierSymbol) true if identifier is safe to access.booleanisSymbolAccessSafe(ISymbol identifierSymbol, IScope inScope) true if identifier is safe to access within the specified scope (rather than the current top scope).booleanisVariableInitialised(ISymbol identifierSymbol) booleanisVariableInitialised(ISymbol identifierSymbol, IScope inScope) booleanChecks if the current scope is within a loop (for or while).voidmarkErrorResultAccessSafe(ISymbol identifierSymbol, IScope inScope) voidmarkGetOptionalAccessSafe(ISymbol identifierSymbol, IScope inScope) voidmarkNextIteratorAccessSafe(ISymbol identifierSymbol, IScope inScope) voidmarkOkResultAccessSafe(ISymbol identifierSymbol, IScope inScope) voidmarkSymbolAccessSafe(ISymbol identifierSymbol, IScope inScope) Record an identifier as being safe to access.voidmarkSymbolAsInitialised(ISymbol identifierSymbol, IScope inScope) Record an identifier was initialised.voidrecordDeclarationOfVariableUsingIterator(ISymbol identifierSymbol) voidrecordDeclarationOfVariableUsingOptional(ISymbol identifierSymbol) voidrecordDeclarationOfVariableUsingResult(ISymbol identifierSymbol) voidrecordInitialisedReturningVariable(ISymbol returningVariable) Follow a returning variable that was declared WITH a value: from here onisInitialiserReplaced(ISymbol, IScope)says whether every path has since given it a new one.voidrecordInitialiserReplaced(ISymbol returningVariable) Record a plain assignment to a followed returning variable: on this path its declared value is replaced.voidrecordScopeForStackConsistency(IScope scope, org.antlr.v4.runtime.tree.ParseTree node) There are times in parsing/listening when symbols are already defined (due to a developer error).voidrecordSymbol(ISymbol symbol, org.antlr.v4.runtime.tree.ParseTree node) Record a symbol against a specific node (so it can be looked up later).voidrecordSymbolAssignment(ISymbol identifierSymbol) Record an identifier was assigned and therefore initialised.voidrecordSymbolDeclaration(ISymbol identifierSymbol) Records a variable against the appropriate scope.voidrecordSymbolDeclaration(ISymbol identifierSymbol, IScope inScope) Record variable declaration against a specific scope.voidRecord that the current block has just thrown, unconditionally: THE place a throw takes part in deciding whether a returning variable is always initialised (E08050) and whether a later read of a local may see it unset (E08020,phase5/IdentifierReadOrError) - one mark, read by both.resolveOrDefine(PossibleGenericSymbol parameterisedSymbol, SemanticErrorReporter errorListener) Resolve or define a parameterised type here.traverseBackUpStack(IScope.ScopeType scopeType) Navigates back up the scope stack to find the first match of the scope type passed in.traverseBackUpStackBeyond(IScope beyond, IScope.ScopeType scopeType) AstraverseBackUpStack(IScope.ScopeType), considering only the scopes outsidebeyond.Traversed back up the scope stack to try and locate the enclosing method (if there is one).AstraverseBackUpStackToNearestAggregate(), considering only the scopes outsidebeyond.
-
Constructor Details
-
SymbolsAndScopes
Create a new instance for symbol and scope management. If these types are not resolved then OK with a big exception and stop processing (so suppress get warning).
-
-
Method Details
-
isExternallyImplemented
public boolean isExternallyImplemented() -
isDevSource
public boolean isDevSource() -
getModuleScope
-
getEk9Types
-
getCodeFlowAnalyzers
Provide access to the set of code flow analyzers being used for flow analysis. -
getUninitialisedVariables
-
isVariableInitialised
-
isVariableInitialised
-
recordSymbolDeclaration
Records a variable against the appropriate scope. -
recordSymbolDeclaration
-
recordDeclarationOfVariableUsingResult
-
recordDeclarationOfVariableUsingOptional
-
recordDeclarationOfVariableUsingIterator
-
recordSymbolAssignment
Record an identifier was assigned and therefore initialised. -
recordInitialisedReturningVariable
Follow a returning variable that was declared WITH a value: from here onisInitialiserReplaced(ISymbol, IScope)says whether every path has since given it a new one. Records it against the current scope - the callable, switch, try or loop scope it is declared in. -
recordInitialiserReplaced
Record a plain assignment to a followed returning variable: on this path its declared value is replaced. -
isInitialiserReplaced
True when every path to the end of the scope has given the returning variable a new value - the definite assignment E08050 asks of a returning variable with no initialiser, asked of one that has an initialiser - so the value it was declared with cannot be the one returned. -
recordUnconditionalThrow
public void recordUnconditionalThrow()Record that the current block has just thrown, unconditionally: THE place a throw takes part in deciding whether a returning variable is always initialised (E08050) and whether a later read of a local may see it unset (E08020,phase5/IdentifierReadOrError) - one mark, read by both.A path that ends in a throw never continues, so it does not have to set a returning variable or a local (L-THROW, Steve 2026-10-08 for returning variables, 2026-10-11 for locals; Java's definite-assignment rule). Every such variable visible here - a local of any enclosing block, the enclosing function's, method's, operator's or dynamic function's returning variable, and that of any switch/try/while/for expression the throw is inside - is marked initialised in the throw's OWN block scope only. So an if/else or switch arm that throws counts as having set it when the arms are pulled up, a conditional throw does not (its block is one arm of an incomplete construct), and nothing outside that block learns anything.
The guard for the absence proof: a catch or finally block is a separate scope, so the mark never reaches it, and a read of the variable there is still E08020 / a returning variable's pull-up still E08050. Call this on EXIT of the throw statement, so the thrown expression's own reads are checked first.
-
markSymbolAsInitialised
-
markSymbolAccessSafe
-
isSymbolAccessSafe
true if identifier is safe to access. -
isSymbolAccessSafe
true if identifier is safe to access within the specified scope (rather than the current top scope). Used by the Walk-0 constructor-field-initialisation pre-pass to query a constructor's body scope at the constructor's exit (the field markings + branch-merges land in the body/instruction-block scope, not the already-popped-to method scope). -
markOkResultAccessSafe
-
isOkResultAccessSafe
-
markErrorResultAccessSafe
-
isErrorResultAccessSafe
-
markGetOptionalAccessSafe
-
isGetOptionalAccessSafe
-
markNextIteratorAccessSafe
-
isNextIteratorAccessSafe
-
enterScope
Ensure that the correct scope is pushed on to the stack. This method takes a context, retrieves the scope, checks for null and then pushes it.- Parameters:
ctx- The contact that must have a scope recorded.
-
enterScope
To be used to ensure that a scope has been pushed on to the scopeStack. -
exitScope
public void exitScope()Normally called at the point where the parser listener exits a scope. The scope will be popped off the scopeStack to reveal the parent scope. -
getTopScope
Provides access to the top of the scope stack. -
getParentOfTopScope
-
traverseBackUpStack
Navigates back up the scope stack to find the first match of the scope type passed in. -
traverseBackUpStackBeyond
AstraverseBackUpStack(IScope.ScopeType), considering only the scopes outsidebeyond. SeeScopeStack.traverseBackUpStackBeyond(IScope, IScope.ScopeType). -
traverseBackUpStackToEnclosingMethod
Traversed back up the scope stack to try and locate the enclosing method (if there is one). Clearly the scope might not be within a method and so Optional.empty will result.- Returns:
- The method and aggregate information if a method was located.
-
traverseBackUpStackToMethodOrFunction
-
traverseBackUpStackToNearestAggregate
-
traverseBackUpStackToNearestAggregateBeyond
AstraverseBackUpStackToNearestAggregate(), considering only the scopes outsidebeyond. SeeScopeStack.traverseBackUpStackToNearestAggregateBeyond(IScope). -
traverseBackUpStackToEnclosingNamedMethodOrFunction
-
isWithinLoopScope
public boolean isWithinLoopScope()Checks if the current scope is within a loop (for or while). Used to suppress "not referenced after assignment" errors for variables assigned inside loops, where the assignment will be re-evaluated on each iteration.- Returns:
- true if currently within a loop scope.
-
resolveOrDefine
public Optional<ISymbol> resolveOrDefine(PossibleGenericSymbol parameterisedSymbol, SemanticErrorReporter errorListener) Resolve or define a parameterised type here. When it is made inside a generic's body and is still conceptual in that generic's own type parameters (List(item)inferring aList of TinHolder of type T) it is recorded as a dependent of the generic - THE route for references found by inference, so every parameterisation of the generic gets its concrete counterpart (GenericBodyReference). -
ensureParameterisedTypeExpanded
public void ensureParameterisedTypeExpanded(Object typeOrScope, SemanticErrorReporter errorListener) THE pre-step of every phase-3 member resolution on a type: whentypeOrScopeis a PARAMETERISED type, resolve it here so its methods and operators are expanded - FULL_RESOLUTION cannot wait for the generic's own definition to be walked, because it may be referenced first. Anything else (a plain type, a function, null) is left alone. Three places wrote theinstanceof PossibleGenericSymbol && isParameterisedType()test and theresolveOrDefine(PossibleGenericSymbol, SemanticErrorReporter)call (front-end rationalisation B-15); a member lookup on a type calls this first. -
enterNewSymbol
A new symbol has been encountered and is defined within the current scope in the scope stack and recorded in the parsedModule against the appropriate node. -
getRecordedSymbol
-
getRecordedScope
-
recordSymbol
Record a symbol against a specific node (so it can be looked up later). -
enterNewLiteral
For literals, we only record in the parsedModule. -
enterNewConstant
public void enterNewConstant(ISymbol symbol, org.antlr.v4.runtime.tree.ParseTree node, SymbolChecker symbolChecker) Create a new constant as declared in the constants section and records the symbol. -
enterModuleScopedSymbol
public void enterModuleScopedSymbol(IScopedSymbol symbol, org.antlr.v4.runtime.tree.ParseTree node, SymbolChecker symbolChecker) To be used when defining a high level symbol at module scope. -
defineScopedSymbol
Record a new scoped symbol in the current scope on the stack. Also records the symbol as both a scope and a symbol in the parsedModule. -
enterNewScopedSymbol
Enter a new scoped symbol and record in parsed module as a symbol and as a scope. Then push the scope on to the working scope stack. -
enterNewScope
Enter a new scope and record in both the parsed module and push on to the working scope stack. -
recordScopeForStackConsistency
There are times in parsing/listening when symbols are already defined (due to a developer error). During this situation we cannot define a new node, but we can detect the duplicate (display errors). But so the scope stack does not get corrupted - it is important to still push the existing scope on to the stack. In this way when the stack is popped everything works out.
-