Skip to content

Commit de0c541

Browse files
ggiraldezteofr
andauthored
Retain references to ImportDeconstructionSymbols instead of following to the aliased definition (#1510)
If an identifier resolves lexically to an `ImportDeconstructionSymbol` definition, register that instead of skipping it and registering to the actual aliased definition. This allows keeping better track of referenced imports, which would otherwise appear to have no references pointing to them. The drawback is that we need to explicitly follow through the aliases whenever we need access to the actual definition, eg. when resolving types, members, etc. --------- Co-authored-by: Teodoro Freund <tfreund95@gmail.com>
1 parent 20ad6ff commit de0c541

File tree

39 files changed

+372
-120
lines changed

39 files changed

+372
-120
lines changed

crates/solidity/outputs/cargo/crate/src/backend/binder/mod.rs

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -481,11 +481,14 @@ impl Binder {
481481
}
482482
}
483483

484-
fn resolve_in_scope_internal(&self, scope_id: ScopeId, symbol: &str) -> Resolution {
484+
// This function attempts to lexically resolve `symbol` starting from the
485+
// given scope. Certain scopes can delegate to their "parent" scopes if
486+
// the symbol is not found there.
487+
pub(crate) fn resolve_in_scope(&self, scope_id: ScopeId, symbol: &str) -> Resolution {
485488
let scope = self.get_scope_by_id(scope_id);
486489
match scope {
487490
Scope::Block(block_scope) => block_scope.definitions.get(symbol).copied().map_or_else(
488-
|| self.resolve_in_scope_internal(block_scope.parent_scope_id, symbol),
491+
|| self.resolve_in_scope(block_scope.parent_scope_id, symbol),
489492
Resolution::Definition,
490493
),
491494
Scope::Contract(contract_scope) => {
@@ -496,7 +499,7 @@ impl Binder {
496499
)
497500
.or_else(|| {
498501
// Otherwise, delegate to the containing file scope.
499-
self.resolve_in_scope_internal(contract_scope.file_scope_id, symbol)
502+
self.resolve_in_scope(contract_scope.file_scope_id, symbol)
500503
})
501504
}
502505
Scope::Enum(enum_scope) => enum_scope.definitions.get(symbol).into(),
@@ -506,16 +509,16 @@ impl Binder {
506509
.get(symbol)
507510
.copied()
508511
.map_or_else(
509-
|| self.resolve_in_scope_internal(function_scope.parameters_scope_id, symbol),
512+
|| self.resolve_in_scope(function_scope.parameters_scope_id, symbol),
510513
Resolution::Definition,
511514
)
512-
.or_else(|| self.resolve_in_scope_internal(function_scope.parent_scope_id, symbol)),
515+
.or_else(|| self.resolve_in_scope(function_scope.parent_scope_id, symbol)),
513516
Scope::Modifier(modifier_scope) => {
514517
if symbol == "_" {
515518
Resolution::BuiltIn(BuiltIn::ModifierUnderscore)
516519
} else {
517520
modifier_scope.definitions.get(symbol).copied().map_or_else(
518-
|| self.resolve_in_scope_internal(modifier_scope.parent_scope_id, symbol),
521+
|| self.resolve_in_scope(modifier_scope.parent_scope_id, symbol),
519522
Resolution::Definition,
520523
)
521524
}
@@ -534,7 +537,7 @@ impl Binder {
534537
.get(symbol)
535538
.copied()
536539
.map_or_else(
537-
|| self.resolve_in_scope_internal(yul_block_scope.parent_scope_id, symbol),
540+
|| self.resolve_in_scope(yul_block_scope.parent_scope_id, symbol),
538541
Resolution::Definition,
539542
),
540543
Scope::YulFunction(yul_function_scope) => {
@@ -545,40 +548,33 @@ impl Binder {
545548
.get(symbol)
546549
.copied()
547550
.map_or_else(
548-
|| {
549-
self.resolve_in_scope_internal(
550-
yul_function_scope.parent_scope_id,
551-
symbol,
552-
)
553-
},
551+
|| self.resolve_in_scope(yul_function_scope.parent_scope_id, symbol),
554552
Resolution::Definition,
555553
)
556554
}
557555
}
558556
}
559557

560-
// This will attempt to lexically resolve `symbol` starting from the given
561-
// scope. This means that scopes can delegate to their "parent" scopes if
562-
// the symbol is not found there, and also that imported symbols are
563-
// followed recursively. This function handles the latter (following
564-
// imported symbols recursively), while `resolve_in_scope_internal`
565-
// delegates to parent or otherwise linked scopes.
566-
pub(crate) fn resolve_in_scope(&self, scope_id: ScopeId, symbol: &str) -> Resolution {
558+
// If the resolution points to definitions that are symbols aliases (eg.
559+
// import deconstruction symbols) this function will recursively follow them
560+
// and return an appropriate resulting resolution. Ambiguities and missing
561+
// definitions can occur along the followed aliases, so there is no
562+
// guarantee that a `Resolved` resolution will yield another `Resolved`
563+
// resolution.
564+
pub(crate) fn follow_symbol_aliases(&self, resolution: &Resolution) -> Resolution {
565+
match resolution {
566+
Resolution::Unresolved | Resolution::BuiltIn(_) => return resolution.clone(),
567+
_ => {}
568+
}
569+
567570
// TODO: since this function uses the results from other resolution
568571
// functions, we making more allocations than necessary; it may be worth
569572
// it to try and avoid them by returning iterators from the delegated
570573
// resolution functions
571574
let mut found_ids = Vec::new();
572-
let mut working_set = Vec::new();
573575
let mut seen_ids = HashSet::new();
576+
let mut working_set = resolution.get_definition_ids();
574577

575-
let initial_resolution = self.resolve_in_scope_internal(scope_id, symbol);
576-
match initial_resolution {
577-
Resolution::Unresolved | Resolution::BuiltIn(_) => return initial_resolution,
578-
_ => {}
579-
}
580-
581-
working_set.extend(initial_resolution.get_definition_ids().iter().rev());
582578
while let Some(definition_id) = working_set.pop() {
583579
if !seen_ids.insert(definition_id) {
584580
// we already processed this definition
@@ -597,16 +593,17 @@ impl Binder {
597593
continue;
598594
};
599595
working_set.extend(
600-
self.resolve_in_scope_internal(scope_id, &imported_symbol.symbol)
601-
.get_definition_ids()
602-
.iter()
603-
.rev(),
596+
self.resolve_in_scope(scope_id, &imported_symbol.symbol)
597+
.get_definition_ids(),
604598
);
605599
} else {
606600
found_ids.push(definition_id);
607601
}
608602
}
609603

604+
// reverse the result to maintain the original ordering, since we
605+
// resolved from last to first in the loop above
606+
found_ids.reverse();
610607
Resolution::from(found_ids)
611608
}
612609

crates/solidity/outputs/cargo/crate/src/backend/ir/ast/node_extensions/identifiers.rs

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,26 @@ impl IdentifierStruct {
3939
.is_some()
4040
}
4141

42-
// only makes sense if `is_reference()` is true
42+
/// Attempts to resolve the identifier to a definition, following symbol
43+
/// aliases (import deconstructions). Returns `None` if the identifier is
44+
/// not acting as a reference, or the reference cannot be resolved.
4345
pub fn resolve_to_definition(&self) -> Option<Definition> {
44-
let reference = self
46+
let definition_id = self
4547
.semantic
46-
.binder()
47-
.find_reference_by_identifier_node_id(self.ir_node.id())?;
48-
let definition_id = reference.resolution.as_definition_id()?;
48+
.resolve_reference_identifier_to_definition_id(self.ir_node.id())?;
49+
Definition::try_create(definition_id, &self.semantic)
50+
}
51+
52+
/// Attempts to resolve the identifier to an immediate definition, without
53+
/// following symbol aliases, possibly returning import deconstruction
54+
/// symbols. If the identifier refers to a direct definition, this is
55+
/// equivalent to `resolve_to_definition()`. Returns `None` if the
56+
/// identifier is not acting as a reference, or the reference cannot be
57+
/// resolved.
58+
pub fn resolve_to_immediate_definition(&self) -> Option<Definition> {
59+
let definition_id = self
60+
.semantic
61+
.resolve_reference_identifier_to_immediate_definition_id(self.ir_node.id())?;
4962
Definition::try_create(definition_id, &self.semantic)
5063
}
5164

@@ -137,6 +150,14 @@ impl Reference {
137150
}
138151
}
139152
}
153+
154+
pub fn resolve_to_immediate_definition(&self) -> Option<Definition> {
155+
match self {
156+
Reference::Identifier(identifier) | Reference::YulIdentifier(identifier) => {
157+
identifier.resolve_to_immediate_definition()
158+
}
159+
}
160+
}
140161
}
141162

142163
impl SemanticAnalysis {
@@ -152,6 +173,25 @@ impl SemanticAnalysis {
152173
})
153174
.collect()
154175
}
176+
177+
fn resolve_reference_identifier_to_definition_id(&self, node_id: NodeId) -> Option<NodeId> {
178+
let reference = self
179+
.binder()
180+
.find_reference_by_identifier_node_id(node_id)?;
181+
self.binder()
182+
.follow_symbol_aliases(&reference.resolution)
183+
.as_definition_id()
184+
}
185+
186+
fn resolve_reference_identifier_to_immediate_definition_id(
187+
&self,
188+
node_id: NodeId,
189+
) -> Option<NodeId> {
190+
let reference = self
191+
.binder()
192+
.find_reference_by_identifier_node_id(node_id)?;
193+
reference.resolution.as_definition_id()
194+
}
155195
}
156196

157197
impl IdentifierPathStruct {
@@ -163,13 +203,25 @@ impl IdentifierPathStruct {
163203
.join(".")
164204
}
165205

206+
/// Attempts to resolve the identifier path to a definition, following
207+
/// symbol aliases (import deconstructions).
166208
pub fn resolve_to_definition(&self) -> Option<Definition> {
167209
let ir_node = self.ir_nodes.last()?;
168-
let reference = self
210+
let definition_id = self
169211
.semantic
170-
.binder()
171-
.find_reference_by_identifier_node_id(ir_node.id())?;
172-
let definition_id = reference.resolution.as_definition_id()?;
212+
.resolve_reference_identifier_to_definition_id(ir_node.id())?;
213+
Definition::try_create(definition_id, &self.semantic)
214+
}
215+
216+
/// Attempts to resolve the identifier path to an immediate definition,
217+
/// without following symbol aliases, possibly returning import
218+
/// deconstruction symbols. If the path refers to a direct definition, this
219+
/// is equivalent to `resolve_to_definition`.
220+
pub fn resolve_to_immediate_definition(&self) -> Option<Definition> {
221+
let ir_node = self.ir_nodes.last()?;
222+
let definition_id = self
223+
.semantic
224+
.resolve_reference_identifier_to_immediate_definition_id(ir_node.id())?;
173225
Definition::try_create(definition_id, &self.semantic)
174226
}
175227
}

crates/solidity/outputs/cargo/crate/src/backend/passes/p3_linearise_contracts/mod.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,26 @@ impl<'a> Pass<'a> {
122122
} else {
123123
Resolution::Unresolved
124124
};
125-
let definition_id = resolution.as_definition_id();
126125

127126
let reference = Reference::new(Rc::clone(identifier), resolution.clone());
128127
self.binder.insert_reference(reference);
129128

129+
// Unless we used namespace resolution and in order to continue
130+
// resolving the identifier path, we should ensure we've followed
131+
// through any symbol alias (ie. import deconstruction symbol). This
132+
// is not needed for namespaced resolution because there cannot be
133+
// import directives inside contracts, interfaces or libraries which
134+
// changes the lookup mode (see below).
135+
let resolution = if use_lexical_resolution {
136+
self.binder.follow_symbol_aliases(&resolution)
137+
} else {
138+
resolution
139+
};
140+
130141
// recurse into file scopes pointed by the resolved definition
131142
// to resolve the next identifier in the path
132-
scope_id = definition_id
143+
scope_id = resolution
144+
.as_definition_id()
133145
.and_then(|node_id| self.binder.find_definition_by_id(node_id))
134146
.and_then(|definition| match definition {
135147
Definition::Import(ImportDefinition {

crates/solidity/outputs/cargo/crate/src/backend/passes/p4_type_definitions/resolution.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,26 @@ impl Pass<'_> {
3232
} else {
3333
Resolution::Unresolved
3434
};
35-
let definition_id = resolution.as_definition_id();
3635

3736
let reference = Reference::new(Rc::clone(identifier), resolution.clone());
3837
self.binder.insert_reference(reference);
3938

39+
// Unless we used namespace resolution and in order to continue
40+
// resolving the identifier path, we should ensure we've followed
41+
// through any symbol alias (ie. import deconstruction symbol). This
42+
// is not needed for namespaced resolution because there cannot be
43+
// import directives inside contracts, interfaces or libraries which
44+
// changes the lookup mode (see below).
45+
let resolution = if use_lexical_resolution {
46+
self.binder.follow_symbol_aliases(&resolution)
47+
} else {
48+
resolution
49+
};
50+
4051
// recurse into file scopes pointed by the resolved definition
4152
// to resolve the next identifier in the path
42-
scope_id = definition_id
53+
scope_id = resolution
54+
.as_definition_id()
4355
.and_then(|node_id| self.binder.find_definition_by_id(node_id))
4456
.and_then(|definition| match definition {
4557
Definition::Import(ImportDefinition {
@@ -147,7 +159,13 @@ impl Pass<'_> {
147159
self.binder
148160
.find_reference_by_identifier_node_id(identifier.id())
149161
})
150-
.and_then(|reference| reference.resolution.as_definition_id())?;
162+
.and_then(|reference| {
163+
// reference may resolve to an imported library, so we need to
164+
// follow aliases
165+
self.binder
166+
.follow_symbol_aliases(&reference.resolution)
167+
.as_definition_id()
168+
})?;
151169

152170
let Some(Definition::Library(_)) = self.binder.find_definition_by_id(definition_id) else {
153171
// the referenced definition is not a library

crates/solidity/outputs/cargo/crate/src/backend/passes/p4_type_definitions/typing.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ impl Pass<'_> {
1616
self.binder
1717
.find_reference_by_identifier_node_id(identifier.id())
1818
})
19-
.and_then(|reference| reference.resolution.as_definition_id())
19+
.and_then(|reference| {
20+
// We should follow symbol aliases here. This is only relevant
21+
// if the path has a single element, as in other cases symbols
22+
// cannot be aliased.
23+
self.binder
24+
.follow_symbol_aliases(&reference.resolution)
25+
.as_definition_id()
26+
})
2027
.and_then(|node_id| self.type_of_definition(node_id, data_location))
2128
}
2229

crates/solidity/outputs/cargo/crate/src/backend/passes/p4_type_definitions/visitor.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,22 @@ impl Visitor for Pass<'_> {
8787

8888
fn enter_import_deconstruction(&mut self, node: &input_ir::ImportDeconstruction) -> bool {
8989
for symbol in &node.symbols {
90-
let target_symbol = if let Some(alias) = &symbol.alias {
91-
alias
92-
} else {
93-
&symbol.name
90+
// find the associated definition to get the imported file ID
91+
let Some(Definition::ImportedSymbol(imported_symbol)) =
92+
self.binder.find_definition_by_id(symbol.node_id)
93+
else {
94+
unreachable!("expected to find definition associated to imported symbol");
9495
};
95-
let resolution = self.binder.resolve_in_scope(
96-
self.current_contract_or_file_scope_id(),
97-
&target_symbol.unparse(),
98-
);
96+
// now we can get the target scope ID
97+
let scope_id = imported_symbol
98+
.resolved_file_id
99+
.as_ref()
100+
.and_then(|file_id| self.binder.scope_id_for_file_id(file_id));
101+
102+
let resolution = scope_id.map_or(Resolution::Unresolved, |scope_id| {
103+
self.binder
104+
.resolve_in_scope(scope_id, &symbol.name.unparse())
105+
});
99106
let reference = Reference::new(Rc::clone(&symbol.name), resolution);
100107
self.binder.insert_reference(reference);
101108
}

crates/solidity/outputs/cargo/crate/src/backend/passes/p5_resolve_references/resolution.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,10 @@ impl Pass<'_> {
411411
resolution
412412
};
413413

414-
scope_id = resolution
414+
// NOTE: we need to follow symbol aliases to resolve the next scope to use
415+
scope_id = self
416+
.binder
417+
.follow_symbol_aliases(&resolution)
415418
.as_definition_id()
416419
.and_then(|definition_id| self.binder.scope_id_for_node_id(definition_id));
417420

crates/solidity/outputs/cargo/crate/src/backend/passes/p5_resolve_references/typing.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,14 @@ impl Pass<'_> {
139139
}
140140

141141
fn typing_of_identifier(&self, identifier: &Rc<TerminalNode>) -> Typing {
142-
let resolution = self
142+
let resolution = &self
143143
.binder
144144
.find_reference_by_identifier_node_id(identifier.id())
145145
.unwrap()
146-
.resolution
147-
.clone();
146+
.resolution;
147+
// The resolution may point to an imported symbol, so we need to follow
148+
// through in order to get to the actual typing
149+
let resolution = self.binder.follow_symbol_aliases(resolution);
148150
self.typing_of_resolution(&resolution)
149151
}
150152

crates/solidity/outputs/cargo/crate/src/backend/passes/p5_resolve_references/visitor.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,13 @@ impl Visitor for Pass<'_> {
635635
let definition_id = self
636636
.binder
637637
.find_reference_by_identifier_node_id(node.error.last().unwrap().id())
638-
.and_then(|reference| reference.resolution.as_definition_id());
638+
.and_then(|reference| {
639+
// Follow symbol aliases as the error type argument to
640+
// revert could be an imported symbol
641+
self.binder
642+
.follow_symbol_aliases(&reference.resolution)
643+
.as_definition_id()
644+
});
639645
self.resolve_named_arguments(named_arguments, definition_id);
640646
}
641647
}

0 commit comments

Comments
 (0)