Skip to content

Commit eb79166

Browse files
kobyhallxjfecher
andauthored
feat(lsp): goto trait method declaration (#3991)
# Description ## Problem\* Resolves <!-- Link to GitHub Issue --> feat(lsp): goto declaration for trait methods #3724 ## Summary\* Goto trait method declaration from trait method implementation or trait method invocation. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[Exceptional Case]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: jfecher <jake@aztecprotocol.com>
1 parent 29f704f commit eb79166

10 files changed

Lines changed: 368 additions & 231 deletions

File tree

compiler/noirc_frontend/src/hir/resolution/traits.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,8 @@ fn resolve_trait_methods(
159159
functions.push(TraitFunction {
160160
name: name.clone(),
161161
typ: Type::Forall(generics, Box::new(function_type)),
162-
span: name.span(),
162+
location: Location::new(name.span(), unresolved_trait.file_id),
163163
default_impl,
164-
default_impl_file_id: unresolved_trait.file_id,
165164
default_impl_module_id: unresolved_trait.module_id,
166165
});
167166

compiler/noirc_frontend/src/hir_def/traits.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ use noirc_errors::{Location, Span};
1212
pub struct TraitFunction {
1313
pub name: Ident,
1414
pub typ: Type,
15-
pub span: Span,
15+
pub location: Location,
1616
pub default_impl: Option<Box<NoirFunction>>,
17-
pub default_impl_file_id: fm::FileId,
1817
pub default_impl_module_id: crate::hir::def_map::LocalModuleId,
1918
}
2019

compiler/noirc_frontend/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod lexer;
1616
pub mod monomorphization;
1717
pub mod node_interner;
1818
pub mod parser;
19+
pub mod resolve_locations;
1920

2021
pub mod hir;
2122
pub mod hir_def;

compiler/noirc_frontend/src/node_interner.rs

Lines changed: 6 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ type StructAttributes = Vec<SecondaryAttribute>;
3939
/// monomorphization - and it is not useful afterward.
4040
#[derive(Debug)]
4141
pub struct NodeInterner {
42-
nodes: Arena<Node>,
43-
func_meta: HashMap<FuncId, FuncMeta>,
42+
pub(crate) nodes: Arena<Node>,
43+
pub(crate) func_meta: HashMap<FuncId, FuncMeta>,
4444
function_definition_ids: HashMap<FuncId, DefinitionId>,
4545

4646
// For a given function ID, this gives the function's modifiers which includes
@@ -52,7 +52,7 @@ pub struct NodeInterner {
5252
function_modules: HashMap<FuncId, ModuleId>,
5353

5454
// Map each `Index` to it's own location
55-
id_to_location: HashMap<Index, Location>,
55+
pub(crate) id_to_location: HashMap<Index, Location>,
5656

5757
// Maps each DefinitionId to a DefinitionInfo.
5858
definitions: Vec<DefinitionInfo>,
@@ -85,14 +85,14 @@ pub struct NodeInterner {
8585
// Each trait definition is possibly shared across multiple type nodes.
8686
// It is also mutated through the RefCell during name resolution to append
8787
// methods from impls to the type.
88-
traits: HashMap<TraitId, Trait>,
88+
pub(crate) traits: HashMap<TraitId, Trait>,
8989

9090
// Trait implementation map
9191
// For each type that implements a given Trait ( corresponding TraitId), there should be an entry here
9292
// The purpose for this hashmap is to detect duplication of trait implementations ( if any )
9393
//
9494
// Indexed by TraitImplIds
95-
trait_implementations: Vec<Shared<TraitImpl>>,
95+
pub(crate) trait_implementations: Vec<Shared<TraitImpl>>,
9696

9797
/// Trait implementations on each type. This is expected to always have the same length as
9898
/// `self.trait_implementations`.
@@ -350,7 +350,7 @@ partialeq!(StmtId);
350350
/// This data structure is never accessed directly, so API wise there is no difference between using
351351
/// Multiple arenas and a single Arena
352352
#[derive(Debug, Clone)]
353-
enum Node {
353+
pub(crate) enum Node {
354354
Function(HirFunction),
355355
Statement(HirStatement),
356356
Expression(HirExpression),
@@ -463,31 +463,6 @@ impl NodeInterner {
463463
self.id_to_location.insert(expr_id.into(), Location::new(span, file));
464464
}
465465

466-
/// Scans the interner for the item which is located at that [Location]
467-
///
468-
/// The [Location] may not necessarily point to the beginning of the item
469-
/// so we check if the location's span is contained within the start or end
470-
/// of each items [Span]
471-
#[tracing::instrument(skip(self))]
472-
pub fn find_location_index(&self, location: Location) -> Option<impl Into<Index>> {
473-
let mut location_candidate: Option<(&Index, &Location)> = None;
474-
475-
// Note: we can modify this in the future to not do a linear
476-
// scan by storing a separate map of the spans or by sorting the locations.
477-
for (index, interned_location) in self.id_to_location.iter() {
478-
if interned_location.contains(&location) {
479-
if let Some(current_location) = location_candidate {
480-
if interned_location.span.is_smaller(&current_location.1.span) {
481-
location_candidate = Some((index, interned_location));
482-
}
483-
} else {
484-
location_candidate = Some((index, interned_location));
485-
}
486-
}
487-
}
488-
location_candidate.map(|(index, _location)| *index)
489-
}
490-
491466
/// Interns a HIR Function.
492467
pub fn push_fn(&mut self, func: HirFunction) -> FuncId {
493468
FuncId(self.nodes.insert(Node::Function(func)))
@@ -1146,7 +1121,6 @@ impl NodeInterner {
11461121
}
11471122

11481123
/// Adds a trait implementation to the list of known implementations.
1149-
#[tracing::instrument(skip(self))]
11501124
pub fn add_trait_implementation(
11511125
&mut self,
11521126
object_type: Type,
@@ -1276,82 +1250,6 @@ impl NodeInterner {
12761250
self.selected_trait_implementations.get(&ident_id).cloned()
12771251
}
12781252

1279-
/// Returns the [Location] of the definition of the given Ident found at [Span] of the given [FileId].
1280-
/// Returns [None] when definition is not found.
1281-
pub fn get_definition_location_from(&self, location: Location) -> Option<Location> {
1282-
self.find_location_index(location)
1283-
.and_then(|index| self.resolve_location(index))
1284-
.or_else(|| self.try_resolve_trait_impl_location(location))
1285-
}
1286-
1287-
/// For a given [Index] we return [Location] to which we resolved to
1288-
/// We currently return None for features not yet implemented
1289-
/// TODO(#3659): LSP goto def should error when Ident at Location could not resolve
1290-
fn resolve_location(&self, index: impl Into<Index>) -> Option<Location> {
1291-
let node = self.nodes.get(index.into())?;
1292-
1293-
match node {
1294-
Node::Function(func) => self.resolve_location(func.as_expr()),
1295-
Node::Expression(expression) => self.resolve_expression_location(expression),
1296-
_ => None,
1297-
}
1298-
}
1299-
1300-
/// Resolves the [Location] of the definition for a given [HirExpression]
1301-
///
1302-
/// Note: current the code returns None because some expressions are not yet implemented.
1303-
fn resolve_expression_location(&self, expression: &HirExpression) -> Option<Location> {
1304-
match expression {
1305-
HirExpression::Ident(ident) => {
1306-
let definition_info = self.definition(ident.id);
1307-
match definition_info.kind {
1308-
DefinitionKind::Function(func_id) => {
1309-
Some(self.function_meta(&func_id).location)
1310-
}
1311-
DefinitionKind::Local(_local_id) => Some(definition_info.location),
1312-
_ => None,
1313-
}
1314-
}
1315-
HirExpression::Constructor(expr) => {
1316-
let struct_type = &expr.r#type.borrow();
1317-
Some(struct_type.location)
1318-
}
1319-
HirExpression::MemberAccess(expr_member_access) => {
1320-
self.resolve_struct_member_access(expr_member_access)
1321-
}
1322-
HirExpression::Call(expr_call) => {
1323-
let func = expr_call.func;
1324-
self.resolve_location(func)
1325-
}
1326-
1327-
_ => None,
1328-
}
1329-
}
1330-
1331-
/// Resolves the [Location] of the definition for a given [crate::hir_def::expr::HirMemberAccess]
1332-
/// This is used to resolve the location of a struct member access.
1333-
/// For example, in the expression `foo.bar` we want to resolve the location of `bar`
1334-
/// to the location of the definition of `bar` in the struct `foo`.
1335-
fn resolve_struct_member_access(
1336-
&self,
1337-
expr_member_access: &crate::hir_def::expr::HirMemberAccess,
1338-
) -> Option<Location> {
1339-
let expr_lhs = &expr_member_access.lhs;
1340-
let expr_rhs = &expr_member_access.rhs;
1341-
1342-
let lhs_self_struct = match self.id_type(expr_lhs) {
1343-
Type::Struct(struct_type, _) => struct_type,
1344-
_ => return None,
1345-
};
1346-
1347-
let struct_type = lhs_self_struct.borrow();
1348-
let field_names = struct_type.field_names();
1349-
1350-
field_names.iter().find(|field_name| field_name.0 == expr_rhs.0).map(|found_field_name| {
1351-
Location::new(found_field_name.span(), struct_type.location.file)
1352-
})
1353-
}
1354-
13551253
/// Retrieves the trait id for a given binary operator.
13561254
/// All binary operators correspond to a trait - although multiple may correspond
13571255
/// to the same trait (such as `==` and `!=`).
@@ -1436,24 +1334,6 @@ impl NodeInterner {
14361334
pub(crate) fn ordering_type(&self) -> Type {
14371335
self.ordering_type.clone().expect("Expected ordering_type to be set in the NodeInterner")
14381336
}
1439-
1440-
/// Attempts to resolve [Location] of [Trait] based on [Location] of [TraitImpl]
1441-
/// This is used by LSP to resolve the location of a trait based on the location of a trait impl.
1442-
///
1443-
/// Example:
1444-
/// impl Foo for Bar { ... } -> trait Foo { ... }
1445-
fn try_resolve_trait_impl_location(&self, location: Location) -> Option<Location> {
1446-
self.trait_implementations
1447-
.iter()
1448-
.find(|shared_trait_impl| {
1449-
let trait_impl = shared_trait_impl.borrow();
1450-
trait_impl.file == location.file && trait_impl.ident.span().contains(&location.span)
1451-
})
1452-
.and_then(|shared_trait_impl| {
1453-
let trait_impl = shared_trait_impl.borrow();
1454-
self.traits.get(&trait_impl.trait_id).map(|trait_| trait_.location)
1455-
})
1456-
}
14571337
}
14581338

14591339
impl Methods {
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
use arena::Index;
2+
use noirc_errors::Location;
3+
4+
use crate::hir_def::expr::HirExpression;
5+
use crate::hir_def::types::Type;
6+
7+
use crate::node_interner::{DefinitionKind, Node, NodeInterner};
8+
9+
impl NodeInterner {
10+
/// Scans the interner for the item which is located at that [Location]
11+
///
12+
/// The [Location] may not necessarily point to the beginning of the item
13+
/// so we check if the location's span is contained within the start or end
14+
/// of each items [Span]
15+
pub fn find_location_index(&self, location: Location) -> Option<impl Into<Index>> {
16+
let mut location_candidate: Option<(&Index, &Location)> = None;
17+
18+
// Note: we can modify this in the future to not do a linear
19+
// scan by storing a separate map of the spans or by sorting the locations.
20+
for (index, interned_location) in self.id_to_location.iter() {
21+
if interned_location.contains(&location) {
22+
if let Some(current_location) = location_candidate {
23+
if interned_location.span.is_smaller(&current_location.1.span) {
24+
location_candidate = Some((index, interned_location));
25+
}
26+
} else {
27+
location_candidate = Some((index, interned_location));
28+
}
29+
}
30+
}
31+
location_candidate.map(|(index, _location)| *index)
32+
}
33+
34+
/// Returns the [Location] of the definition of the given Ident found at [Span] of the given [FileId].
35+
/// Returns [None] when definition is not found.
36+
pub fn get_definition_location_from(&self, location: Location) -> Option<Location> {
37+
self.find_location_index(location)
38+
.and_then(|index| self.resolve_location(index))
39+
.or_else(|| self.try_resolve_trait_impl_location(location))
40+
.or_else(|| self.try_resolve_trait_method_declaration(location))
41+
}
42+
43+
pub fn get_declaration_location_from(&self, location: Location) -> Option<Location> {
44+
self.try_resolve_trait_method_declaration(location).or_else(|| {
45+
self.find_location_index(location)
46+
.and_then(|index| self.resolve_location(index))
47+
.and_then(|found_impl_location| {
48+
self.try_resolve_trait_method_declaration(found_impl_location)
49+
})
50+
})
51+
}
52+
53+
/// For a given [Index] we return [Location] to which we resolved to
54+
/// We currently return None for features not yet implemented
55+
/// TODO(#3659): LSP goto def should error when Ident at Location could not resolve
56+
fn resolve_location(&self, index: impl Into<Index>) -> Option<Location> {
57+
let node = self.nodes.get(index.into())?;
58+
59+
match node {
60+
Node::Function(func) => self.resolve_location(func.as_expr()),
61+
Node::Expression(expression) => self.resolve_expression_location(expression),
62+
_ => None,
63+
}
64+
}
65+
66+
/// Resolves the [Location] of the definition for a given [HirExpression]
67+
///
68+
/// Note: current the code returns None because some expressions are not yet implemented.
69+
fn resolve_expression_location(&self, expression: &HirExpression) -> Option<Location> {
70+
match expression {
71+
HirExpression::Ident(ident) => {
72+
let definition_info = self.definition(ident.id);
73+
match definition_info.kind {
74+
DefinitionKind::Function(func_id) => {
75+
Some(self.function_meta(&func_id).location)
76+
}
77+
DefinitionKind::Local(_local_id) => Some(definition_info.location),
78+
_ => None,
79+
}
80+
}
81+
HirExpression::Constructor(expr) => {
82+
let struct_type = &expr.r#type.borrow();
83+
Some(struct_type.location)
84+
}
85+
HirExpression::MemberAccess(expr_member_access) => {
86+
self.resolve_struct_member_access(expr_member_access)
87+
}
88+
HirExpression::Call(expr_call) => {
89+
let func = expr_call.func;
90+
self.resolve_location(func)
91+
}
92+
93+
_ => None,
94+
}
95+
}
96+
97+
/// Resolves the [Location] of the definition for a given [crate::hir_def::expr::HirMemberAccess]
98+
/// This is used to resolve the location of a struct member access.
99+
/// For example, in the expression `foo.bar` we want to resolve the location of `bar`
100+
/// to the location of the definition of `bar` in the struct `foo`.
101+
fn resolve_struct_member_access(
102+
&self,
103+
expr_member_access: &crate::hir_def::expr::HirMemberAccess,
104+
) -> Option<Location> {
105+
let expr_lhs = &expr_member_access.lhs;
106+
let expr_rhs = &expr_member_access.rhs;
107+
108+
let lhs_self_struct = match self.id_type(expr_lhs) {
109+
Type::Struct(struct_type, _) => struct_type,
110+
_ => return None,
111+
};
112+
113+
let struct_type = lhs_self_struct.borrow();
114+
let field_names = struct_type.field_names();
115+
116+
field_names.iter().find(|field_name| field_name.0 == expr_rhs.0).map(|found_field_name| {
117+
Location::new(found_field_name.span(), struct_type.location.file)
118+
})
119+
}
120+
121+
/// Attempts to resolve [Location] of [Trait] based on [Location] of [TraitImpl]
122+
/// This is used by LSP to resolve the location of a trait based on the location of a trait impl.
123+
///
124+
/// Example:
125+
/// impl Foo for Bar { ... } -> trait Foo { ... }
126+
fn try_resolve_trait_impl_location(&self, location: Location) -> Option<Location> {
127+
self.trait_implementations
128+
.iter()
129+
.find(|shared_trait_impl| {
130+
let trait_impl = shared_trait_impl.borrow();
131+
trait_impl.file == location.file && trait_impl.ident.span().contains(&location.span)
132+
})
133+
.and_then(|shared_trait_impl| {
134+
let trait_impl = shared_trait_impl.borrow();
135+
self.traits.get(&trait_impl.trait_id).map(|trait_| trait_.location)
136+
})
137+
}
138+
139+
/// Attempts to resolve [Location] of [Trait]'s [TraitFunction] declaration based on [Location] of [TraitFunction] call.
140+
///
141+
/// This is used by LSP to resolve the location.
142+
///
143+
/// ### Example:
144+
/// ```nr
145+
/// trait Fieldable {
146+
/// fn to_field(self) -> Field;
147+
/// ^------------------------------\
148+
/// } |
149+
/// |
150+
/// fn main_func(x: u32) { |
151+
/// assert(x.to_field() == 15); |
152+
/// \......................./
153+
/// }
154+
/// ```
155+
///
156+
fn try_resolve_trait_method_declaration(&self, location: Location) -> Option<Location> {
157+
self.func_meta
158+
.iter()
159+
.find(|(_, func_meta)| func_meta.location.contains(&location))
160+
.and_then(|(func_id, _func_meta)| {
161+
let (_, trait_id) = self.get_function_trait(func_id)?;
162+
163+
let mut methods = self.traits.get(&trait_id)?.methods.iter();
164+
let method =
165+
methods.find(|method| method.name.0.contents == self.function_name(func_id));
166+
method.map(|method| method.location)
167+
})
168+
}
169+
}

0 commit comments

Comments
 (0)