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): auto-import trait reexport if trait is not visible #7079

Merged
merged 2 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::errors::{DefCollectorErrorKind, DuplicateType};
use crate::elaborator::Elaborator;
use crate::graph::CrateId;
use crate::hir::comptime::InterpreterError;
use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId};
use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId};
use crate::hir::resolution::errors::ResolverError;
use crate::hir::type_check::TypeCheckError;
use crate::locations::ReferencesTracker;
Expand Down Expand Up @@ -412,13 +412,24 @@ impl DefCollector {
visibility,
);

if visibility != ItemVisibility::Private {
if context.def_interner.is_in_lsp_mode()
&& visibility != ItemVisibility::Private
{
context.def_interner.register_name_for_auto_import(
name.to_string(),
module_def_id,
visibility,
Some(defining_module),
);

if let ModuleDefId::TraitId(trait_id) = module_def_id {
context.def_interner.add_trait_reexport(
trait_id,
defining_module,
name.clone(),
visibility,
);
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,12 @@
interned_statement_kinds: noirc_arena::Arena<StatementKind>,

// Interned `UnresolvedTypeData`s during comptime code.
interned_unresolved_type_datas: noirc_arena::Arena<UnresolvedTypeData>,

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

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)

// Interned `Pattern`s during comptime code.
interned_patterns: noirc_arena::Arena<Pattern>,

/// Determins whether to run in LSP mode. In LSP mode references are tracked.

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

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Determins)
pub(crate) lsp_mode: bool,

/// Store the location of the references in the graph.
Expand Down Expand Up @@ -267,6 +267,11 @@

/// Captures the documentation comments for each module, struct, trait, function, etc.
pub(crate) doc_comments: HashMap<ReferenceId, Vec<String>>,

/// Only for LSP: a map of trait ID to each module that pub or pub(crate) exports it.
/// In LSP this is used to offer importing the trait via one of these exports if
/// the trait is not visible where it's defined.
trait_reexports: HashMap<TraitId, Vec<(ModuleId, Ident, ItemVisibility)>>,
}

/// A dependency in the dependency graph may be a type or a definition.
Expand Down Expand Up @@ -669,7 +674,7 @@
quoted_types: Default::default(),
interned_expression_kinds: Default::default(),
interned_statement_kinds: Default::default(),
interned_unresolved_type_datas: Default::default(),

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

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
interned_patterns: Default::default(),
lsp_mode: false,
location_indices: LocationIndices::default(),
Expand All @@ -680,6 +685,7 @@
comptime_scopes: vec![HashMap::default()],
trait_impl_associated_types: HashMap::default(),
doc_comments: HashMap::default(),
trait_reexports: HashMap::default(),
}
}
}
Expand Down Expand Up @@ -2134,11 +2140,11 @@
&mut self,
typ: UnresolvedTypeData,
) -> InternedUnresolvedTypeData {
InternedUnresolvedTypeData(self.interned_unresolved_type_datas.insert(typ))

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

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
}

pub fn get_unresolved_type_data(&self, id: InternedUnresolvedTypeData) -> &UnresolvedTypeData {
&self.interned_unresolved_type_datas[id.0]

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

View workflow job for this annotation

GitHub Actions / Code

Unknown word (datas)
}

/// Returns the type of an operator (which is always a function), along with its return type.
Expand Down Expand Up @@ -2256,6 +2262,20 @@
_ => None,
}
}

pub fn add_trait_reexport(
&mut self,
trait_id: TraitId,
module_id: ModuleId,
name: Ident,
visibility: ItemVisibility,
) {
self.trait_reexports.entry(trait_id).or_default().push((module_id, name, visibility));
}

pub fn get_trait_reexports(&self, trait_id: TraitId) -> &[(ModuleId, Ident, ItemVisibility)] {
self.trait_reexports.get(&trait_id).map_or(&[], |exports| exports)
}
}

impl Methods {
Expand Down
221 changes: 190 additions & 31 deletions tooling/lsp/src/requests/code_action/import_trait.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
use std::collections::HashSet;

use noirc_errors::Location;
use noirc_frontend::{
ast::MethodCallExpression,
hir::{def_map::ModuleDefId, resolution::visibility::trait_member_is_visible},
hir_def::traits::Trait,
node_interner::ReferenceId,
hir::def_map::ModuleDefId,
node_interner::{ReferenceId, TraitId},
};

use crate::{
modules::relative_module_full_path,
modules::{relative_module_full_path, relative_module_id_path},
requests::TraitReexport,
use_segment_positions::{
use_completion_item_additional_text_edits, UseCompletionItemAdditionTextEditsRequest,
},
visibility::module_def_id_is_visible,
};

use super::CodeActionFinder;
Expand All @@ -29,16 +32,7 @@ impl<'a> CodeActionFinder<'a> {

let trait_impl = self.interner.get_trait_implementation(trait_impl_id);
let trait_id = trait_impl.borrow().trait_id;
let trait_ = self.interner.get_trait(trait_id);

// Check if the trait is currently imported. If so, no need to suggest anything
let module_data =
&self.def_maps[&self.module_id.krate].modules()[self.module_id.local_id.0];
if !module_data.scope().find_name(&trait_.name).is_none() {
return;
}

self.push_import_trait_code_action(trait_);
self.import_trait(trait_id);
return;
}

Expand All @@ -48,33 +42,86 @@ impl<'a> CodeActionFinder<'a> {
return;
};

for (func_id, trait_id) in
self.interner.lookup_trait_methods(&typ, &method_call.method_name.0.contents, true)
{
let visibility = self.interner.function_modifiers(&func_id).visibility;
if !trait_member_is_visible(trait_id, visibility, self.module_id, self.def_maps) {
continue;
}
let trait_methods =
self.interner.lookup_trait_methods(&typ, &method_call.method_name.0.contents, true);
let trait_ids: HashSet<_> = trait_methods.iter().map(|(_, trait_id)| *trait_id).collect();

let trait_ = self.interner.get_trait(trait_id);
self.push_import_trait_code_action(trait_);
for trait_id in trait_ids {
self.import_trait(trait_id);
}
}

fn push_import_trait_code_action(&mut self, trait_: &Trait) {
let trait_id = trait_.id;

fn import_trait(&mut self, trait_id: TraitId) {
// First check if the trait is visible
let trait_ = self.interner.get_trait(trait_id);
let visibility = trait_.visibility;
let module_def_id = ModuleDefId::TraitId(trait_id);
let current_module_parent_id = self.module_id.parent(self.def_maps);
let Some(module_full_path) = relative_module_full_path(
let mut trait_reexport = None;

if !module_def_id_is_visible(
module_def_id,
self.module_id,
current_module_parent_id,
visibility,
None,
self.interner,
) else {
self.def_maps,
) {
// If it's not, try to find a visible reexport of the trait
// that is visible from the current module
let Some((visible_module_id, name, _)) =
self.interner.get_trait_reexports(trait_id).iter().find(
|(module_id, _, visibility)| {
module_def_id_is_visible(
module_def_id,
self.module_id,
*visibility,
Some(*module_id),
self.interner,
self.def_maps,
)
},
)
else {
return;
};
trait_reexport = Some(TraitReexport { module_id: visible_module_id, name });
}

let trait_name = if let Some(trait_reexport) = &trait_reexport {
trait_reexport.name
} else {
&trait_.name
};

// Check if the trait is currently imported. If yes, no need to suggest anything
let module_data =
&self.def_maps[&self.module_id.krate].modules()[self.module_id.local_id.0];
if !module_data.scope().find_name(trait_name).is_none() {
return;
}

let module_def_id = ModuleDefId::TraitId(trait_id);
let current_module_parent_id = self.module_id.parent(self.def_maps);
let module_full_path = if let Some(trait_reexport) = &trait_reexport {
relative_module_id_path(
*trait_reexport.module_id,
&self.module_id,
current_module_parent_id,
self.interner,
)
} else {
let Some(path) = relative_module_full_path(
module_def_id,
self.module_id,
current_module_parent_id,
self.interner,
) else {
return;
};
path
};
let full_path = format!("{}::{}", module_full_path, trait_.name);

let full_path = format!("{}::{}", module_full_path, trait_name);

let title = format!("Import {}", full_path);

Expand Down Expand Up @@ -242,6 +289,118 @@ mod moo {
}
}

fn main() {
let x: Field = 1;
x.foobar();
}"#;

assert_code_action(title, src, expected).await;
}

#[test]
async fn test_import_trait_in_method_call_when_one_option_but_not_in_scope_via_reexport() {
let title = "Import moo::Bar";

let src = r#"mod moo {
mod nested {
pub trait Foo {
fn foobar(self);
}

impl Foo for Field {
fn foobar(self) {}
}
}

pub use nested::Foo as Bar;
}

fn main() {
let x: Field = 1;
x.foo>|<bar();
}"#;

let expected = r#"use moo::Bar;

mod moo {
mod nested {
pub trait Foo {
fn foobar(self);
}

impl Foo for Field {
fn foobar(self) {}
}
}

pub use nested::Foo as Bar;
}

fn main() {
let x: Field = 1;
x.foobar();
}"#;

assert_code_action(title, src, expected).await;
}

#[test]
async fn test_import_trait_in_method_call_when_multiple_via_reexport() {
let title = "Import moo::Baz";

let src = r#"mod moo {
mod nested {
pub trait Foo {
fn foobar(self);
}

impl Foo for Field {
fn foobar(self) {}
}

pub trait Bar {
fn foobar(self);
}

impl Bar for Field {
fn foobar(self) {}
}
}

pub use nested::Foo as Baz;
pub use nested::Foo as Qux;
}

fn main() {
let x: Field = 1;
x.foo>|<bar();
}"#;

let expected = r#"use moo::Baz;

mod moo {
mod nested {
pub trait Foo {
fn foobar(self);
}

impl Foo for Field {
fn foobar(self) {}
}

pub trait Bar {
fn foobar(self);
}

impl Bar for Field {
fn foobar(self) {}
}
}

pub use nested::Foo as Baz;
pub use nested::Foo as Qux;
}

fn main() {
let x: Field = 1;
x.foobar();
Expand Down
Loading
Loading