2.3.1.3 Name Resolution
Name resolution binds an identifier name, as seen from a scope, to the Symbol it refers to. The static semantics (RD-VBAL §5.0 Semantics) and the simple name expression (RD-VBAL §5.6.10 Simple Name Expressions) resolve names as this section specifies.
The name-resolution algorithm is a separate concern from the workspace's ordered reference list,
IRuntimeSession.References (RD-VBAL §2.3.1.2 Session Services).
ISymbolResolver
The static and runtime semantic layers read symbols through ISymbolResolver:
| Member | Description |
|---|---|
ResolveValue |
Resolves a specified identifier name in the default binding context, as seen from the scope that the symbol at a specified handle Uri belongs to, to a SymbolResolutionResult. |
ResolveType |
Resolves a specified identifier name in the type binding context, as seen from the scope that the symbol at a specified handle Uri belongs to, to a SymbolResolutionResult. |
ResolveQualifier |
Resolves a specified identifier name as the qualifier of a qualified type name (the A in A.B). It binds the project, or a procedural or class module, and never a user-defined type or an Enum type. |
GetValue |
Gets the IBindingHandle currently bound to a specified Symbol. |
TryRead |
Gets the IBindingHandle held at a specified MemoryAddress, if any. |
TryGetAddress |
Resolves the address that a ByRef parameter binding aliases (RD-VBAL §5.3.1.11 Procedure Invocation Argument Processing). |
TryAllocate |
Allocates a Static local's own module-extent storage, in the session's module-level heap (RD-VBAL §5.4.3.1 Local Variable Declarations). |
The handle Uri of a symbol is a semantic ID that uniquely identifies the symbol across an entire workspace
(RD-VBAL §2.5.1 Runtime Entities).
TryGetAddress and TryAllocate apply the read-only SDK-interface / Runtime-implementation split to name
resolution (RD-VBAL §3.5.5 Placement and Licensing).
TryAllocate mutates state: it allocates a Static local's storage, unlike every other SDK-interface member
listed in §3.5.5.
| Resolver | Kind | TryGetAddress / TryAllocate |
|---|---|---|
CallStackAwareSymbolResolver (RDCore.Runtime) |
Runtime | Resolves the address; allocates the storage. |
RuntimeSymbolResolver (RDCore.Runtime) |
Runtime | Resolves the address; allocates the storage. |
| CompositeSymbolResolver | Compile-time only | Returns false for both. |
| ScopeTreeSymbolResolver | Compile-time only | Returns false for both. |
IntrinsicSymbolResolver |
Compile-time only | Returns false for both. |
CallStackAwareSymbolResolver and RuntimeSymbolResolver are the only two resolvers that resolve an address for
TryGetAddress and allocate storage for TryAllocate. A compile-time-only resolver returns false for both, as it
does for TryRead.
Binding contexts
Which ISymbolResolver lookup a name is resolved through is decided by the node being evaluated, never by a
parameter (RD-VBAL §3.0.3 Binding Contexts;
MS-VBAL §5.6.4 Expression Binding Contexts):
| Node | Binding context | Resolver call |
|---|---|---|
| A simple name expression (SimpleNameExpressionNode) | Default | ResolveValue |
A type name: an As clause |
Type | ResolveType |
A type name: the operand of New (NewExpressionNode) |
Type | ResolveType |
| The part of a qualified type name that precedes a dot | Namespace (qualifier) | ResolveQualifier, instead of ResolveType |
ISessionSymbols mirrors the ResolveValue / ResolveType pair as TryResolveValue and TryResolveType
(RD-VBAL §2.3.1.2 Session Services).
Candidates
ResolveValue and ResolveType bind different candidates
(MS-VBAL §5.6.10 Simple Name Expressions).
ResolveQualifier is ResolveType without the user-defined types and Enum types: only the enclosing project, or a
procedural or class module, is a candidate.
| Candidate | ResolveValue |
ResolveType |
ResolveQualifier |
|---|---|---|---|
| Variable (including a local or a parameter) | Yes | No | No |
| Constant | Yes | No | No |
| Enum type | Yes | Yes | No |
| Enum member | Yes | No | No |
| Property, function, subroutine | Yes | No | No |
| User-defined type | No | Yes | No |
| Class module | No; only through its predeclared instance | Yes | Yes |
| Procedural module | Yes | Yes | Yes |
| Project | Yes | Yes | Yes |
ResolveType applies the order of precedence user-defined type, Enum type, class or procedural module, project.
It starts its lookup from the enclosing module. Because of this, a local, parameter or constant can neither be
bound by ResolveType nor hide the type it shadows.
Qualified type names
In a qualified type name A.B, the lookup is positional
(RD-VBAL §3.0.3 Binding Contexts):
| Part | Bound as | Resolver call |
|---|---|---|
B, the last part |
Like a bare name, in the type binding context. | ResolveType |
A, the qualifier |
A namespace: the project, or a procedural or class module. | ResolveQualifier |
Neither a user-defined type nor an Enum type is a candidate for the qualifier, because neither can contain a type.
Class names and predeclared instances
In the default binding context (ResolveValue), a class is a value only through its predeclared instance
(RD-VBAL §3.1.1.6 VB_PredeclaredId;
MS-VBAL §5.2.4.1.2 Default Instance Variables Static Semantics):
| Name | Binding context | Binds to |
|---|---|---|
| The name of a predeclared class | Default (ResolveValue) |
The class module's predeclared instance variable. |
| The name of a class that is not predeclared, used in an expression | Default (ResolveValue) |
Nothing: it is an undefined variable. |
| A class name | Type (ResolveType: an As clause or New) |
The class. |
Other than through its predeclared instance, a class module is never a name in the default binding context. A local
variable or field that is itself named Widget hides the default instance of class Widget, and is an ordinary
Set target.
Resolution results
ResolveValue and ResolveType each return a SymbolResolutionResult:
| Result | Meaning |
|---|---|
The bound Symbol |
The name binds to that symbol. |
| Unbound | The name is declared nowhere visible. |
| VBC09303 Duplicate declaration | A compile-time error: the name is declared more than once within one module or procedure. The colliding declarations are attached. |
| VBC09301 Ambiguous name | A compile-time error: the name resolves in more than one enclosing scope (members promoted from different modules or references), and the reference must qualify the name. The colliding declarations are attached. |
The symbol resolver reports the error kind. The caller, which knows where the reference is, builds the located diagnostic (RD-VBAL §2.6.2 Semantic Compilation Errors).
Lookup order
The correctly-scoped allocation of all symbols upon their definition should suffice to make symbol resolution follow the MS-VBAL order in which an identifier name is resolved (MS-VBAL §5.6.10), provided that lookups are done in the specified order.
Identifier name lookups are done in this order (the heaps are described in RD-VBAL §2.3.1.2 Session Services):
| Order | A name that refers to a symbol defined in | Resolves to a symbol that is |
|---|---|---|
| 1 | The local stack frame | Locally scoped. |
| 2 | The static locals heap | Locally scoped, but preserves its value between calls (RD-VBAL §5.4.3.1 Local Variable Declarations). |
| 3 | The workspace heap | Workspace-scoped. |
| 4 | The global heap | Globally-scoped. |
Scope tree
The mechanism behind the ordered lookup is a ScopeTree. A ScopeTreeBuilder folds the composed symbols into a tree of LexicalScopes, of the kinds in LexicalScopeKind:
LexicalScopeKind |
Scopes | Position in the tree |
|---|---|---|
Global |
The global scope. | The root. |
Project |
The project scope. | Beneath the global scope. |
Module |
One scope per module. | Beneath the project scope. |
Procedure |
One scope per procedure body. | Beneath its module's scope. |
The project scope is an ancestor of a module's own scope and of nothing else.
Placement
Each symbol is placed structurally in the ScopeTree, from its ParentUri, its concrete type, and its access
modifier.
A standard module's non-Private members (an explicit Public / Global / Friend, or an implicit
procedure-like member,
MS-VBAL §5.2.3 Module Declarations)
are also declared in the project scope. Because of this, a sibling module resolves them without qualification. A
bare Err, for example, yields the error object in either shape
(RD-VBAL §6.1.3.2 Err Class).
An enum constant (Enum member) symbol parents to its Enum rather than to a scope, and ScopeTreeBuilder resolves
an Enum member's scope placement through its Enum
(RD-VBAL §5.2.3 Module Declarations):
- An
Enum's members are lexically scoped like the Enum type itself: accessible within the enclosing project, or within the enclosing module. - An Enum member takes its visibility from its Enum's own access modifier.
- An Enum member such as
vbSundayis therefore a name on its own, resolvable without qualification.
A public Enum, and a public user-defined type, declared in a class module reach the project scope.
Procedure locals
A procedure's parameters and its own Dim / Static / Const locals are carried on the member symbol, not
registered as separate entries. This is a design principle of the scope tree:
VBProcedureMemberSymbol.LocalsandVBReturningMemberSymbol.Localslist everyDim,StaticandConstdeclared in the procedure body (RD-VBAL §5.4.3.1 Local Variable Declarations).ScopeTreeBuilder(RDCore.SDK) extracts a procedure symbol'sLocalsfor name resolution, the same way it extracts itsParameters.- A local therefore never needs a second, flat registration of its own to resolve by name.
Walking the tree
Resolving a name from a scope walks SelfAndAncestors() outward. The first scope that declares the name binds it.
A name declared more than once in a single scope is one of the error cases of
Duplicate and ambiguous names, below. The exception is a property's Get / Let /
Set accessors: they share one name by design and resolve as a group, raising VBC09320 or VBC09321 only when they
do not form a valid property (see ScopeTreeSymbolResolver).
Module directives
A module's LexicalScope carries its ModuleDirectives.
ModuleDirectives holds the module-level facts a static semantics rule needs:
| Member | Description |
|---|---|
Explicit |
Whether the module declares Option Explicit (RD-VBAL §5.2.1 Option Directives). |
Compare |
The module's Option Compare mode. |
Strict |
Reserved for RD-VBA's '@OptionStrict annotation. No symbol provider sets it, so it is always false. |
ModuleDirectives is reachable from any scope nested under the module, via
LexicalScope.EnclosingModuleDirectives().
The static-semantics layer consumes ModuleDirectives to decide what an unresolved simple name is:
Module declares Option Explicit |
An unresolved simple name is |
|---|---|
| No | A deferred VBUnknownType (RD-VBAL §2.4.4 Deferred Types). |
| Yes | A VBC09302 Variable not defined compile-time error. |
Without a qualifier, a deferred symbol is deemed to be an undeclared local variable, as per MS-VBAL scoping rules. If a global-scope deferred symbol with the same identifier name exists, such an unqualified deferred symbol should resolve to the global-scope deferred symbol (RD-VBAL §2.4.4 Deferred Types).
How a SimpleNameExpression's declared type is determined from a ResolveValue outcome is specified in
RD-VBAL §5.6.10 Simple Name Expressions.
Duplicate and ambiguous names
| Condition | Compile-time error |
|---|---|
| Multiple symbols match a specified name within one module or procedure scope. | VBC09303 Duplicate declaration |
| Multiple symbols match a specified name across the project or global scope (members promoted from different modules or references). | VBC09301 Ambiguous name: the reference must qualify the name. |
An appropriate compile-time error (VBCompileErrorId) should be issued for a duplicate declaration and for an ambiguous name (RD-VBAL §2.6.2 Semantic Compilation Errors).
SimpleNameExpressionStaticSemantics
(RD-VBAL §5.6.10 Simple Name Expressions) consumes ResolveValue's
error outcomes, and reports an ambiguous or duplicate name as a coded compile-time error: the simple name expression
yields an Error carrying AmbiguousName or DuplicateDeclaration.
Reference priority
When multiple symbols match a specified name within the global scope, the name is disambiguated using the
reference priority order of the referenced library each matching symbol is defined in. Name resolution across
referenced projects and libraries shall consult the IRuntimeSession.References ordering to disambiguate a
global-scope name (RD-VBAL §2.3.1.2 Session Services). A referenced library's own
members are contributed by an ISymbolProvider and resolved through ISymbolResolver, not from that list.
Reference priority is determined by the order in which project references appear in the .rdproj file of a
workspace folder (RD-VBAL §2.2.3 ProjectFile).
The VBA standard library always has the lowest reference priority: it always appears first in the reference
order. Any other project reference that defines an identically-named class type or public/global member always
shadows the VBA library definition.
Shadowing of VBA library definitions should be detected in the semantic layer and reported through semantic
flags (RD-VBAL §1.1.3 Core Semantic Flags), so that
RDCore.Diagnostics can issue shadowed declaration diagnostics
(RD-VBAL §2.6 Diagnostics).
Note
Not implemented. Reference-priority ordering within the global scope is not implemented. The ordering is
carried on IRuntimeSession.References, but nothing consults it. A name that matches symbols from more than one
reference is reported as VBC09301 Ambiguous name (see Duplicate and ambiguous names).
Resolver implementations
ScopeTreeSymbolResolver
The compile-time implementation of ISymbolResolver is ScopeTreeSymbolResolver. It walks the ScopeTree and
binds names only.
ScopeTreeSymbolResolver.GetValue throws, and ScopeTreeSymbolResolver.TryRead returns false: a
ScopeTreeSymbolResolver holds no run-time bindings.
The session exposes a ScopeTreeSymbolResolver over its own symbols as ISessionSymbols.Resolver.
ISessionSymbols.Resolver is rebuilt as symbols are defined.
ScopeTreeSymbolResolver raises two property-accessor diagnostics:
- VBC09320 Inconsistent property accessors, where the property accessors are resolved as a group;
- VBC09321 Argument required for Property Let or Property Set.
Design-time resolver composition
A design-time host composes its own symbol resolver the same way, in two passes:
- The first pass extracts every parsed module's declarations with an intrinsic-only resolver. This is enough to know which types, classes and enums the workspace declares.
- The second pass extracts every parsed module's declarations again, through a resolver over the first pass's declarations. After the second pass, every declared type name (a field's, a local's, a parameter's, a function's return type) binds, in the type binding context, to the workspace type it names.
A CompositeSymbolResolver then layers a ScopeTreeSymbolResolver over the second pass's symbols in front of the
intrinsic resolver. With the CompositeSymbolResolver, a module's As SomeType binds to a sibling module's Type or Enum, or to a
class, not only to a reserved data-type name.
A type is a reference to its declaration. A member access reads the members of a class or user-defined type from the type's declaration, by the type's own identity (RD-VBAL §5.6.12 Member Access Expressions). A class whose member is typed as the class itself therefore resolves through any number of member-access hops.
CallStackAwareSymbolResolver
CallStackAwareSymbolResolver (RDCore.Runtime) falls through to session-level storage for any Local-scoped
symbol that the current frame does not itself declare
(RD-VBAL §5.4.3.1 Local Variable Declarations).
⏮️ RD-VBAL §2.3.1.2 Session Services | ⏭️ RD-VBAL §2.3.2 Mode / State