-
Notifications
You must be signed in to change notification settings - Fork 130
ECJ Lookups
< JDT Core Programmer Guide | ECJ
During name resolving, several structures are used to identify the binding referenced by a given name.
Throughout the following lookup structures a convention exists that
methods getPackage0()
, getType0
and a few more like this will only
check, if a sought element already exists in one of the caches (call
them "passive"). Typically, a sibling method exists without the 0
suffix, which in case of a cache miss will actively search for the
sought element ("active").
To record the fact that an element was not found in a particular
location of the tree, special instances TheNotFoundModule
,
TheNotFoundPackage
, TheNotFoundType
are inserted into the tree to
avoid repeated, unsuccesful resolution attempts.
The central place where the following bindings are globally stored:
- ModuleBinding (
knownModules
) - PackageBinding (
knownPackages
, recursively)- TypeBinding (
knownTypes
)
- TypeBinding (
Note, that the structure below knownPackages
forms a tree, where
each edge corresponds to a simple name segment. Based on this most
lookup is done incrementally, i.e., we resolve one simple name at a
time, relative to the result so far. The only exception to this rule is
field ModuleBinding.declaredPackages
(see below).
Since Java 9, different instances of LookupEnvironment are used, to implement the perspective of one module each. In this situation
- the root lookup environment represents the unnamed module
- module-specific lookup environments are found via the
environment
field of elements ofknownModules
- each lookup environment has a back link
root
to the root environment - fields of LookupEnvironment that are not module specific are documented as either ROOT_ONLY or SHARED (see javadoc of LookupEnvironment#root).
The root lookup environment also links a few unique instances:
-
globalOptions
: all compiler options in effect -
nameEnvironment
: sometimes called the oracle, since it's an external/opaque entity that can answer sought types and packages. -
typeRequestor
: callback for newly discovered types -
typeSystem
: manages variants of known type bindings (parameterizations, arrays, annotations) -
verifier
: -
problemReporter
: a fall-back problem reporter, which is used for reporting errors that cannot be associated to a particular AST node. -
classFilePool
:
TBC
Since Java 9 also module bindings act as lookups.
The current design has been developed in bug 547181:
Each module binding stores a flat map of declaredPackages
.
- This map is indexed by qualified names
- Values in this map are
PlainPackageBinding
The map represents exactly those packages that are declared in this
module. It does not take into account possible name clashes with
packages declared in another module (SplitPackageBinding
, see below).
-
NB: Much of the complexity of package lookup is necessary to admit and distinguish same-named packages in different modules. Interestingly, the JVM will not be able to handle such programs unless a custom
Layer
implementation is provided.
During compilation, the map declaredPackages
is eagerly filled with
all packages mentioned in exports and opens directives. This is
done as to avoid re-entrant package lookup as we observed before bug 547181 due to
crazy traversal of the module graph etc. For automatic modules, which
have no such declarations, the structure is filled using a lazy,
one-time scan using IModuleAwareNameEnvironment.listPackages()
.
For explicit modules additional (non-exported) packages may be added lazily, but those have no impact on resolving of references outside this module.
Additionally, two methods of name getVisiblePackage
exist, which will
search the current module and all other modules read by it. If more than
one visible module declares the requested package, the result will be
represented as a SplitPackageBinding
. JLS distinguishes visibility and
accessibility. The former is defined just by the module graph, whereas
the latter, stronger form also takes exports declarations into
account. To query accessibility, the method canAccess(PackageBinding)
can be used.
As mentioned, package bindings are organized as a tree below
LookupEnvironment.knownPackages
. Packages bindings can be used to look
up child packages (getPackage()
) and types (getType
). For qualified
names of unknown kind method getTypeOrPackage()
does all the hard
work.
Since Java 9 all such lookup should pass the client module (where the reference happens), so that lookup can obey to the rules of visibility and accessibility of JPMS.
Java has a bit of a split mind as to whether or not "empty packages"
"exist". We have this tree of packages where leaf packages are
"children" of some "parent" package. Many of these "parent" packages are
empty. OTOH, some parts of JLS only consider packages that "exist" in
terms of being declared by a compilation unit. In that sense those empty
"parent" packages do not "exist". Note, that strictly speaking, a
package containing a non-Java resource, does not exist either.
PackageBinding sports a method hasCompilationUnit
to check for the
stricter forms of existence. When passing true
for the parameter
checkCUs
the batch compiler will actually parse any found .java file,
to check if it indeed declares the expected package (which is of course
a bit expensive).
In a way this particular strange guy acts as a lookup too: It represents a package with incarnations in different modules and allows:
- navigating to a child package (given a module perspective), which may or may not again be a SplitPackageBinding
- extracting a specific package incarnation corresponding to to the slice of this package within a particular module
At the level below packages, ReferenceBinding
and subclasses represent
all classes and interfaces.
Lookup of fields and memberTypes works just as expected, because still here names are unique.
Only when diving into methods, a lot more work is needed to consider type inference and overload resolution, so that's a separate story to be told another time.
A special caveat on methods unResolvedFields
and unResolvedMethods
:
initially these have been implemented with the intention to access known
members without triggering type resolution (which can cause
detrimental re-entrance in some situations). Unfortunately, the picture
is blurred here: in particular the implementation in ReferenceBinding
simply delegates to the other getMethods()
which may trigger type
resolution!
In addition to the global lookup (which serves source and binary
elements), the following AST nodes have their dedicated Scope
which
performs the initial, location-aware part of name lookup:
- CompilationUnitDeclaration -> CompilationUnitScope
- ModuleDeclaration -> ModuleScope
- TypeDeclaration -> ClassScope
- AbstractMethodDeclaration -> MethodScope
- Block -> BlockScope (also used by block-like nodes like ForeachStatement)
A TypeDeclaration may hold additional scopes initializerScope
and
staticInitializerScope
for resolving field initializers
(implementation uses MethodScope also here).
Each scope has a parent link to support inside-out searches during resolution. TBC