Skip to content
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

feat: lsp "go to definition" for modules #5406

Merged
merged 11 commits into from
Jul 4, 2024
6 changes: 3 additions & 3 deletions compiler/noirc_frontend/src/elaborator/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ impl<'context> Elaborator<'context> {
let path_resolution;

if self.interner.track_references {
let mut dependencies: Vec<ReferenceId> = Vec::new();
let mut references: Vec<ReferenceId> = Vec::new();
path_resolution =
resolver.resolve(self.def_maps, path.clone(), &mut Some(&mut dependencies))?;
resolver.resolve(self.def_maps, path.clone(), &mut Some(&mut references))?;

for (referenced, ident) in dependencies.iter().zip(path.segments) {
for (referenced, ident) in references.iter().zip(path.segments) {
let reference = ReferenceId::Variable(Location::new(ident.span(), self.file));
self.interner.add_reference(*referenced, reference);
}
Expand Down
23 changes: 22 additions & 1 deletion compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,28 @@ impl DefCollector {
// Resolve unresolved imports collected from the crate, one by one.
for collected_import in std::mem::take(&mut def_collector.imports) {
let module_id = collected_import.module_id;
match resolve_import(crate_id, &collected_import, &context.def_maps, &mut None) {
let resolved_import = if context.def_interner.track_references {
let mut references: Vec<ReferenceId> = Vec::new();
let resolved_import = resolve_import(
crate_id,
&collected_import,
&context.def_maps,
&mut Some(&mut references),
);

let current_def_map = context.def_maps.get(&crate_id).unwrap();
let file_id = current_def_map.file_id(module_id);

for (referenced, ident) in references.iter().zip(&collected_import.path.segments) {
let reference = ReferenceId::Variable(Location::new(ident.span(), file_id));
context.def_interner.add_reference(*referenced, reference);
}

resolved_import
} else {
resolve_import(crate_id, &collected_import, &context.def_maps, &mut None)
};
match resolved_import {
Ok(resolved_import) => {
if let Some(error) = resolved_import.error {
errors.push((
Expand Down
70 changes: 51 additions & 19 deletions compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use acvm::{AcirField, FieldElement};
use fm::{FileId, FileManager, FILE_EXTENSION};
use noirc_errors::Location;
use noirc_errors::{Location, Span};
use num_bigint::BigUint;
use num_traits::Num;
use rustc_hash::FxHashMap as HashMap;
Expand Down Expand Up @@ -283,12 +283,18 @@
);

// Create the corresponding module for the struct namespace
let id = match self.push_child_module(&name, self.file_id, false, false) {
Ok(local_id) => context.def_interner.new_struct(
let id = match self.push_child_module(
context,
&name,
Location::new(name.span(), self.file_id),
false,
false,
) {
Ok(module_id) => context.def_interner.new_struct(
&unresolved,
resolved_generics,
krate,
local_id,
module_id.local_id,
self.file_id,
),
Err(error) => {
Expand Down Expand Up @@ -377,8 +383,14 @@
let name = trait_definition.name.clone();

// Create the corresponding module for the trait namespace
let trait_id = match self.push_child_module(&name, self.file_id, false, false) {
Ok(local_id) => TraitId(ModuleId { krate, local_id }),
let trait_id = match self.push_child_module(
context,
&name,
Location::new(name.span(), self.file_id),
false,
false,
) {
Ok(module_id) => TraitId(ModuleId { krate, local_id: module_id.local_id }),
Err(error) => {
errors.push((error.into(), self.file_id));
continue;
Expand Down Expand Up @@ -490,7 +502,7 @@
}
}
TraitItem::Type { name } => {
// TODO(nickysn or alexvitkov): implement context.def_interner.push_empty_type_alias and get an id, instead of using TypeAliasId::dummy_id()

Check warning on line 505 in compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (nickysn)

Check warning on line 505 in compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (alexvitkov)
if let Err((first_def, second_def)) = self.def_collector.def_map.modules
[trait_id.0.local_id.0]
.declare_type_alias(name.clone(), TypeAliasId::dummy_id())
Expand Down Expand Up @@ -535,13 +547,19 @@
) -> Vec<(CompilationError, FileId)> {
let mut errors: Vec<(CompilationError, FileId)> = vec![];
for submodule in submodules {
match self.push_child_module(&submodule.name, file_id, true, submodule.is_contract) {
match self.push_child_module(
context,
&submodule.name,
Location::new(submodule.name.span(), file_id),
true,
submodule.is_contract,
) {
Ok(child) => {
errors.extend(collect_defs(
self.def_collector,
submodule.contents,
file_id,
child,
child.local_id,
crate_id,
context,
macro_processors,
Expand Down Expand Up @@ -620,13 +638,24 @@
);

// Add module into def collector and get a ModuleId
match self.push_child_module(&mod_decl.ident, child_file_id, true, false) {
match self.push_child_module(
context,
&mod_decl.ident,
Location::new(Span::empty(0), child_file_id),
true,
false,
) {
Ok(child_mod_id) => {
// Track that the "foo" in `mod foo;` points to the module "foo"
let referenced = ReferenceId::Module(child_mod_id);
let reference = ReferenceId::Variable(location);
context.def_interner.add_reference(referenced, reference);

errors.extend(collect_defs(
self.def_collector,
ast,
child_file_id,
child_mod_id,
child_mod_id.local_id,
crate_id,
context,
macro_processors,
Expand All @@ -643,13 +672,14 @@
/// On error this returns None and pushes to `errors`
fn push_child_module(
&mut self,
context: &mut Context,
mod_name: &Ident,
file_id: FileId,
mod_location: Location,
add_to_parent_scope: bool,
is_contract: bool,
) -> Result<LocalModuleId, DefCollectorErrorKind> {
) -> Result<ModuleId, DefCollectorErrorKind> {
let parent = Some(self.module_id);
let location = Location::new(mod_name.span(), file_id);
let location = Location::new(mod_name.span(), mod_location.file);
asterite marked this conversation as resolved.
Show resolved Hide resolved
let new_module = ModuleData::new(parent, location, is_contract);
let module_id = self.def_collector.def_map.modules.insert(new_module);

Expand All @@ -658,6 +688,11 @@
// Update the parent module to reference the child
modules[self.module_id.0].children.insert(mod_name.clone(), LocalModuleId(module_id));

let mod_id = ModuleId {
krate: self.def_collector.def_map.krate,
local_id: LocalModuleId(module_id),
};

// Add this child module into the scope of the parent module as a module definition
// module definitions are definitions which can only exist at the module level.
// ModuleDefinitionIds can be used across crates since they contain the CrateId
Expand All @@ -666,11 +701,6 @@
// to a child module containing its methods) since the module name should not shadow
// the struct name.
if add_to_parent_scope {
let mod_id = ModuleId {
krate: self.def_collector.def_map.krate,
local_id: LocalModuleId(module_id),
};

if let Err((first_def, second_def)) =
modules[self.module_id.0].declare_child_module(mod_name.to_owned(), mod_id)
{
Expand All @@ -681,9 +711,11 @@
};
return Err(err);
}

context.def_interner.add_module_location(mod_id, mod_location);
}

Ok(LocalModuleId(module_id))
Ok(mod_id)
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/locations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

impl LocationIndices {
pub(crate) fn add_location(&mut self, location: Location, node_index: PetGraphIndex) {
// Some location spans are empty: maybe they are from ficticious nodes?

Check warning on line 16 in compiler/noirc_frontend/src/locations.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (ficticious)
if location.span.start() == location.span.end() {
return;
}
Expand All @@ -31,7 +31,7 @@
impl NodeInterner {
pub fn reference_location(&self, reference: ReferenceId) -> Location {
match reference {
ReferenceId::Module(_) => todo!(),
ReferenceId::Module(id) => self.module_location(&id),
ReferenceId::Function(id) => self.function_modifiers(&id).name_location,
ReferenceId::Struct(id) => self.struct_location(&id),
ReferenceId::Trait(_) => todo!(),
Expand Down
12 changes: 12 additions & 0 deletions compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
// Contains the source module each function was defined in
function_modules: HashMap<FuncId, ModuleId>,

// The location of each module
module_locations: HashMap<ModuleId, Location>,

// The location of each struct name
struct_name_locations: HashMap<StructId, Location>,

Expand Down Expand Up @@ -182,7 +185,7 @@
/// Stores the [Location] of a [Type] reference
pub(crate) type_ref_locations: Vec<(Type, Location)>,

/// In Noir's metaprogramming, a noir type has the type `Type`. When these are spliced

Check warning on line 188 in compiler/noirc_frontend/src/node_interner.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (metaprogramming)
/// into `quoted` expressions, we preserve the original type by assigning it a unique id
/// and creating a `Token::QuotedType(id)` from this id. We cannot create a token holding
/// the actual type since types do not implement Send or Sync.
Expand Down Expand Up @@ -540,6 +543,7 @@
function_definition_ids: HashMap::new(),
function_modifiers: HashMap::new(),
function_modules: HashMap::new(),
module_locations: HashMap::new(),
struct_name_locations: HashMap::new(),
func_id_to_trait: HashMap::new(),
dependency_graph: petgraph::graph::DiGraph::new(),
Expand Down Expand Up @@ -980,6 +984,14 @@
self.struct_name_locations[struct_id]
}

pub fn add_module_location(&mut self, module_id: ModuleId, location: Location) {
self.module_locations.insert(module_id, location);
}

pub fn module_location(&self, module_id: &ModuleId) -> Location {
self.module_locations[module_id]
}

pub fn global_attributes(&self, global_id: &GlobalId) -> &[SecondaryAttribute] {
&self.global_attributes[global_id]
}
Expand Down
103 changes: 86 additions & 17 deletions tooling/lsp/src/requests/goto_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ mod goto_definition_tests {

use super::*;

async fn expect_goto(directory: &str, name: &str, definition_index: usize) {
async fn expect_goto_for_all_references(directory: &str, name: &str, definition_index: usize) {
let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await;

let ranges = search_in_file(noir_text_document.path(), name);
Expand Down Expand Up @@ -90,21 +90,20 @@ mod goto_definition_tests {
}
}

#[test]
async fn goto_from_function_location_to_declaration() {
expect_goto("go_to_definition", "another_function", 0).await;
}

#[test]
async fn goto_from_use_as() {
let (mut state, noir_text_document) = test_utils::init_lsp_server("go_to_definition").await;
async fn expect_goto(
directory: &str,
position: Position,
expected_file: &str,
expected_range: Range,
) {
let (mut state, noir_text_document) = test_utils::init_lsp_server(directory).await;

let params = GotoDefinitionParams {
text_document_position_params: lsp_types::TextDocumentPositionParams {
text_document: lsp_types::TextDocumentIdentifier {
uri: noir_text_document.clone(),
},
position: Position { line: 7, character: 29 }, // The word after `as`
position,
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
Expand All @@ -116,15 +115,85 @@ mod goto_definition_tests {
.unwrap_or_else(|| panic!("Didn't get a goto definition response"));

if let GotoDefinitionResponse::Scalar(location) = response {
assert_eq!(
location.range,
Range {
start: Position { line: 1, character: 11 },
end: Position { line: 1, character: 27 }
}
);
assert!(location.uri.to_string().ends_with(expected_file));
assert_eq!(location.range, expected_range);
} else {
panic!("Expected a scalar response");
};
}

#[test]
async fn goto_from_function_location_to_declaration() {
expect_goto_for_all_references("go_to_definition", "another_function", 0).await;
}

#[test]
async fn goto_from_use_as() {
expect_goto(
"go_to_definition",
Position { line: 7, character: 29 }, // The word after `as`,
"src/main.nr",
Range {
start: Position { line: 1, character: 11 },
end: Position { line: 1, character: 27 },
},
)
.await;
}

#[test]
async fn goto_module_from_call_path() {
expect_goto(
"go_to_definition",
Position { line: 17, character: 4 }, // "bar" in "bar::baz()"
"src/bar.nr",
Range {
start: Position { line: 0, character: 0 },
end: Position { line: 0, character: 0 },
},
)
.await;
}

#[test]
async fn goto_inline_module_from_call_path() {
expect_goto(
"go_to_definition",
Position { line: 18, character: 9 }, // "inline" in "bar::inline::qux()"
"src/bar.nr",
Range {
start: Position { line: 2, character: 4 },
end: Position { line: 2, character: 10 },
},
)
.await;
}

#[test]
async fn goto_module_from_use_path() {
expect_goto(
"go_to_definition",
Position { line: 6, character: 4 }, // "foo" in "use foo::another_function;"
"src/main.nr",
Range {
start: Position { line: 0, character: 4 },
end: Position { line: 0, character: 7 },
},
)
.await;
}

#[test]
async fn goto_module_from_mod() {
expect_goto(
"go_to_definition",
Position { line: 9, character: 4 }, // "bar" in "mod bar;"
"src/bar.nr",
Range {
start: Position { line: 0, character: 0 },
end: Position { line: 0, character: 0 },
},
)
.await;
}
}
5 changes: 5 additions & 0 deletions tooling/lsp/test_programs/go_to_definition/src/bar.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub fn baz() {}

mod inline {
pub fn qux() {}
}
4 changes: 4 additions & 0 deletions tooling/lsp/test_programs/go_to_definition/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ mod foo {
use foo::another_function;
use foo::another_function as aliased_function;

mod bar;

fn some_function() -> Field {
1 + 2
}

fn main() {
let _ = another_function();
bar::baz();
bar::inline::qux();
}
Loading