-
Notifications
You must be signed in to change notification settings - Fork 187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Name resolution #873
Name resolution #873
Conversation
cb90f20
to
3fcb28c
Compare
enable use of `Context` in tests and misc test refactoring
b5b84b5
to
b3e63d7
Compare
(cherry picked from commit 2a1cd2aa974570ec86314700ec09feba26d0f6b6)
8b6bfa9
to
2d39be3
Compare
…rage_method Fix issue where compiler doesn't reject method call on storage struct
3bffc14
to
f66ddab
Compare
190d823
to
6351304
Compare
…o inappropriate scope
@Y-Nak all three cycle examples result in an infinite loop when used as tests (the code blocks in |
Hmm, I broke something in resolving conflicts or the refactoring process. I'll check it and add tests for the cases. |
@sbillig I fixed it and added tests for them in Fix a bug that import resolver falls into infinite loop. |
BTW, the first example of the circular imports section describes an error, but I meant to write valid circular imports. |
@sbillig Oops, the example in |
@@ -530,6 +531,8 @@ impl<'db> ImportResolver<'db> { | |||
} | |||
|
|||
fn register_error(&mut self, i_use: &IntermediateUse, err: NameResolutionError) { | |||
self.suspicious_imports.remove(&i_use.use_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the use
from the suspicious import set when it turns out to cause another error other than the import-specific ambiguous error that is described in 7. Import Specific Ambiguity
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ The main change in Remove duplicated error messages by cleaning suspicious import set.
Oh, I realized that we need to add some edge kinds, e.g., to represent methods defined in struct Foo {};
impl Foo {
pub fn foo() {}
pub fn bar(f: foo) {}
} But I guess it'd be ok to postpone it since it doesn't affect the name resolution algorithm itself. |
I refactored the @micahscopes Please use the Improve parsing recovery policy in function parsing, sorry for the inconvenience. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fucking dope
@sbillig Thanks for reviewing this huge PR. |
Name Resolution and Import Resolution
This PR implements import resolution and early path resolution. Accompanying these main features, this PR also introduces the
Visitor
andScopeGraph
at the HIR level and theAnalysisPass
trait for guaranteeing the orthogonality of diagnostics as a public API. As this is a substantially large PR, I'll break down and explain the overview of the language specification about name resolution and public API and provide a summarized description of the implementation details for each item.Name Resolution Specification
General Concept
1. Scope
Name resolution is determined by the relation between the scope where a name is defined and the scope referencing the name.
A new scope is created for each item (e.g.,
fn
ormod
) or Block Expression ({}
) 1.If the scopes differ, definitions with the same names are treated as independent and will not collide.
Example:
2. NameDomain
In Fe, the definitions that are targeted for name resolution are classified into the following three domains:
Type Domain
: Types/Traits, Modules, Generic ParamsValue Domain
: Functions, Constants, Enum VariantsField Domain
: Struct FieldsDefinitions belonging to different domains can have the same name even within the same scope without collision.
Example:
Also, definitions belonging to different domains can be imported into the same scope.
Example:
3. Shadowing
The following ordered elements affect shadowing in name references:
u32
ori64
)Smaller numbers have higher priority. i.e., 1 > 2 > 3 > 4 > 5 > 6. e.g., 1 shadows 2, 3, 4, 5, and 6.
Example:
NOTE: Modules work as boundaries for lexical scopes. Therefore, to reference outside of a module, you need to use
super
or an absolute path.Example:
4. Visibility
Currently, Visibility exists only as
public
andprivate
2. To specify visibility aspublic
,pub
keyword can be used at the definition site.If there is no
pub
keyword, the item is treated asprivate
.Example:
Public
definitions can be referenced from all scopes.Private
definitions can only be referenced from the scope where they are defined and their child scopes.Example:
As an exception,
fields
, even if they areprivate
, can be referenced from the scope that contains there parent struct.Example:
NOTE: Definition visibility introduced into a scope with
use
is overwritten by the visibility ofuse
.Example:
Import Resolution Specification
1. Named Import
A
Named Import
is a standard import that imports a single definition from a certain scope into the scope.Example:
2. Glob Import
A
Glob Import
introduces all visible definitions from a certain scope into the scope.Example:
3. Importability
Definitions belonging to the
Type Domain
andValue Domain
can be imported usinguse
.NOTE:
Enum Variant
belongs to theValue Domain
, hence can be imported usinguse
.Example:
However, types that are defined additively, such as associated types, can not be imported using
use
.Example:
4. Use Aliases
Use Alias
allows users to define an alias name for an imported definition.The
as
keyword is used for this purpose.NOTE: aliases can only be defined for
Named Imports
.Example:
5. Non-Cyclic Dependency between Imports
A definition imported by
use
can be used by anotheruse
in the same scope.Example:
6. Circular Imports
In Fe, modules can reference each other.
Example:
Circular imports using glob are also possible.
Example:
However, if a cycle occurs between
Named Imports
, an error occurs.Example:
7. Import Specific Ambiguity
The algorithm used in import resolution could introduce ambiguity errors specific to
use
.This specific ambiguity error occurs when the following two conditions are met:
Glob Import
is referenced by a Named Import, andExample:
Public API for LSP
PathResolution
1.
ScopeId
[
fe_hir::hir::scope_graph::ScopeId
] is a reference to [fe_hir::hir::scope_graph::Scope
], which is uniquely defined for a scope. In name resolution, the information about the reference scope is always necessary, thusScopeId
is always passed to the name resolution solver. Since allHir
items can be embedded intoScopeId
, it is possible to obtainScopeId
fromHir::ItemKind
via theScopeId::from_item
method.2.
NameRes
andNameResBucket
hir_analysis::name_resolution::NameRes
andhir_analysis::name_resolution::NameResBucket
are types used to represent the result of name resolution. The definitions ofNameRes
and its related types are as follows.NameResBucket
is a type that collectsNameRes
for each domain. The result of name resolution is held for each domain, soNameResolutionResult<NameRes>
is defined for each domain.NameResBucket
has several utility methods defined. For more details, please refer to their doc comments.3. Path Resolution
Currently, the defined entry points for name resolution/path resolution are the following functions defined in
hir_analysis::name_resolution::mod.rs
.The
early
postfix indicates that this is a name resolution at an early stage before resolving type associated items. Name resolution related to types requires context for trait resolution and type inference, but the execution of trait resolution and type inference requires early name resolution. Therefore, the return type of these functions,EarlyResolvedPath
, is defined as a union type as follows.AnalysisPass
hir::analysis_pass::AnalysisPass
is a trait for marking Analysis that could potentially generate Diagnostics, while maintaining the orthogonality of Diagnostics.This can also be considered a thin wrapper to address the problem arising from the fact that the semantic boundaries of each Diagnostics and the boundaries of salsa-tracked functions do not necessarily coincide.
For example,
hir_analysis::PathAnalysisPass
andhir_analysis::DefConflictAnalysisPass
are different AnalysisPasses, but their internal implementations invoke the same salsa-tracked function.These
AnalysisPasses
can be managed as follows byhir::analysis_pass::AnalysisPassManager
.Visitor
hir::visitor
is an implementation of theVisitor Pattern
for visiting HIR nodes.For more about
Visitor Pattern
, please refer to Rust Design Pattern or here.1. VisitorCtxt
One difference between the common
Visitor
implementation and theVisitor
implemented in HIR is that allvisit_*
methods andwalk_*
functions takeVisitorCtxt
as an argument.VisitorCtxt
manages theLazySpan
andScopeId
of the nodes being visited. AsVisitorCtxt
is automatically updated withinwalk_*
, users do not need to explicitly updateVisitorCtxt
.However, users need to inject
VisitorCtxt
from the outside to initiate the Visitor's traversal.VisitorCtxt
can be constructed from allItem
orExpr
/Stmt
/Pat
nodes.Example:
Implementation Details
ScopeGraph
hir::hir_def::scope_graph::ScopeGraph
is built for eachTopLevelModule
, which corresponds to (.fe
file), representing relationships between HIR nodes.Please refer to A Theory of Name Resolution for an overview of
ScopeGraph
.NOTE: The name resolution algorithm introduced by this paper is different from the name resolution algorithm in Fe. Please refer only to the structure of the scope graph.
In name resolution, besides relationships between lexical scopes, dependencies between modules introduced by keywords such as
super
need to be considered. Therefore, the following types of edges are defined inScopeGraph
.EdgeKind
is used by theNameResolver
and is also used when traversing HIR nodes.NOTE: By decoupling the ScopeGraph from the definition of HIR nodes, the definition of HIR nodes themselves are scope-agnostic.
It's possible to visualize
ScopeGraph
with the following command.cargo run --bin fe-driver2 [fe-file.fe] --dump-scope-graph
Here is an example of a ScopeGraph for the following Fe file.
Name Resolver
hir_analysis::name_resolution::name_resolver::NameResolver
is the core implementation for name resolution.The entry point for
NameResolver
isNameResolver::resolve_query(&mut self, query: NameQuery) -> NameResBucket
.NameQuery
is defined as follows.QueryDirective
is a type to specify the policy for the search in the name resolution engine and is defined as follows.NOTE: Note that
NameResolver
itself does not have the concept ofPath
as can be seen from the implementation ofresolve_query
.The behavior of
NameResolver
is very simple.It identifies the destination
Scope
for the query by askingQueryPropagator
if the query should be propagated to the next scope or not.QueryPropagator
is a trait that is implemented for eachEdgeKind
type.The definition of
QueryPropagator
is as follows.NOTE: Imported resolutions are not reflected in the
ScopeGraph
. Therefore,NameResolver
also need to useImporter
to reflect them in name resolution.For more information about the
Importer
, please refer to the section onImport Resolution
Import Resolution
In Fe v2, since
use
is allowed to be interdependent,ImportResolver
uses fixed-point computation to repeatedly perform name resolution until the set ofImport
ed resolutions converges. Specifically, it applies import resolution repeatedly untilScopeState
, defined in each scope, changes to theClosed
state in all scopes, or there are no changes in the iteration of import resolution. TheImport
resolutions in each iteration are added to theIntermediateResolvedImport
set. This set is passed to theNameResolver
, allowing the use path segment to be resolved with the latest imported set.IntermediateResolvedImport
is abstracted by implementing theImporter
trait, soNameResolver
is independent of whether theQuery
to be resolved is in the context of import resolution.NOTE: To ensure reaching a fixed point,
IntermediateResolvedImport
does not allow operations other than the insertion to the set.Proc macro for keywords prefilling
The
fe-macro
crate is the implementation of the procedural macros used infe-v2
. Currently, only thedefine_keywords
macro for prefilling keyword identifiers into the DB is implemented. Identifiers pre-filled by thedefine_keywords
macro can be used as constants. Please refer tohir::hir_def::IdentId
.In the future, test-related macros are also planned to be implemented in this crate.
uitest
The
uitest
crate is a crate for conducting integration tests related to the Diagnostics offe-v2
. All test cases included here cause compiler errors and are intended to verify whether errors are correctly reported to users. Currently, we are usinginsta
to manage snapshots of test cases, but in the future, we want to provide functionality equivalent touitest
as implemented inrustc
.Footnotes
Implementation-wise, scopes are defined at a more granular level (for instance, Generic Parameters have their own scope), but this does not affect the language specification. ↩
In the future, other visibility options such as
crate
orsuper
may be introduced. ↩