diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index fe8c8338b32..24c1e7d929e 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -93,6 +93,7 @@ impl<'context> Elaborator<'context> { self.interner, self.def_maps, self.usage_tracker, + self.crate_graph, self.crate_id, self.debug_comptime_in_file, self.interpreter_call_stack.clone(), diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 593ea6b20e8..c54f9355250 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -4,8 +4,8 @@ use std::{ }; use crate::{ - ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, node_interner::GlobalValue, - usage_tracker::UsageTracker, StructField, StructType, TypeBindings, + ast::ItemVisibility, graph::CrateGraph, hir_def::traits::ResolvedTraitBound, + node_interner::GlobalValue, usage_tracker::UsageTracker, StructField, StructType, TypeBindings, }; use crate::{ ast::{ @@ -97,6 +97,7 @@ pub struct Elaborator<'context> { pub(crate) interner: &'context mut NodeInterner, pub(crate) def_maps: &'context mut DefMaps, pub(crate) usage_tracker: &'context mut UsageTracker, + pub(crate) crate_graph: &'context CrateGraph, pub(crate) file: FileId, @@ -201,6 +202,7 @@ impl<'context> Elaborator<'context> { interner: &'context mut NodeInterner, def_maps: &'context mut DefMaps, usage_tracker: &'context mut UsageTracker, + crate_graph: &'context CrateGraph, crate_id: CrateId, debug_comptime_in_file: Option, interpreter_call_stack: im::Vector, @@ -211,6 +213,7 @@ impl<'context> Elaborator<'context> { interner, def_maps, usage_tracker, + crate_graph, file: FileId::dummy(), unsafe_block_status: UnsafeBlockStatus::NotInUnsafeBlock, nested_loops: 0, @@ -242,6 +245,7 @@ impl<'context> Elaborator<'context> { &mut context.def_interner, &mut context.def_maps, &mut context.usage_tracker, + &context.crate_graph, crate_id, debug_comptime_in_file, im::Vector::new(), @@ -1239,7 +1243,7 @@ impl<'context> Elaborator<'context> { self.file = unresolved.file_id; let old_generic_count = self.generics.len(); self.add_generics(generics); - self.declare_methods_on_struct(false, unresolved, *span); + self.declare_methods_on_struct(None, unresolved, *span); self.generics.truncate(old_generic_count); } } @@ -1269,7 +1273,7 @@ impl<'context> Elaborator<'context> { self.collect_trait_impl_methods(trait_id, trait_impl, &where_clause); let span = trait_impl.object_type.span; - self.declare_methods_on_struct(true, &mut trait_impl.methods, span); + self.declare_methods_on_struct(Some(trait_id), &mut trait_impl.methods, span); let methods = trait_impl.methods.function_ids(); for func_id in &methods { @@ -1328,7 +1332,7 @@ impl<'context> Elaborator<'context> { fn declare_methods_on_struct( &mut self, - is_trait_impl: bool, + trait_id: Option, functions: &mut UnresolvedFunctions, span: Span, ) { @@ -1342,7 +1346,7 @@ impl<'context> Elaborator<'context> { let struct_ref = struct_type.borrow(); // `impl`s are only allowed on types defined within the current crate - if !is_trait_impl && struct_ref.id.krate() != self.crate_id { + if trait_id.is_none() && struct_ref.id.krate() != self.crate_id { let type_name = struct_ref.name.to_string(); self.push_err(DefCollectorErrorKind::ForeignImpl { span, type_name }); return; @@ -1359,7 +1363,12 @@ impl<'context> Elaborator<'context> { // object types in each method overlap or not. If they do, we issue an error. // If not, that is specialization which is allowed. let name = method.name_ident().clone(); - if module.declare_function(name, method.def.visibility, *method_id).is_err() { + let result = if let Some(trait_id) = trait_id { + module.declare_trait_function(name, *method_id, trait_id) + } else { + module.declare_function(name, method.def.visibility, *method_id) + }; + if result.is_err() { let existing = module.find_func_with_name(method.name_ident()).expect( "declare_function should only error if there is an existing function", ); @@ -1374,14 +1383,14 @@ impl<'context> Elaborator<'context> { } // Trait impl methods are already declared in NodeInterner::add_trait_implementation - if !is_trait_impl { + if trait_id.is_none() { self.declare_methods(self_type, &function_ids); } // We can define methods on primitive types only if we're in the stdlib - } else if !is_trait_impl && *self_type != Type::Error { + } else if trait_id.is_none() && *self_type != Type::Error { if self.crate_id.is_stdlib() { // Trait impl methods are already declared in NodeInterner::add_trait_implementation - if !is_trait_impl { + if trait_id.is_none() { self.declare_methods(self_type, &function_ids); } } else { diff --git a/compiler/noirc_frontend/src/elaborator/path_resolution.rs b/compiler/noirc_frontend/src/elaborator/path_resolution.rs index 51673342fef..0cf53fad0e4 100644 --- a/compiler/noirc_frontend/src/elaborator/path_resolution.rs +++ b/compiler/noirc_frontend/src/elaborator/path_resolution.rs @@ -1,12 +1,14 @@ +use iter_extended::vecmap; use noirc_errors::{Location, Span}; -use crate::ast::{Path, PathKind, UnresolvedType}; -use crate::hir::def_map::{ModuleDefId, ModuleId}; +use crate::ast::{Ident, Path, PathKind, UnresolvedType}; +use crate::hir::def_map::{fully_qualified_module_path, ModuleData, ModuleDefId, ModuleId, PerNs}; use crate::hir::resolution::import::{resolve_path_kind, PathResolutionError}; use crate::hir::resolution::errors::ResolverError; use crate::hir::resolution::visibility::item_in_module_is_visible; +use crate::hir_def::traits::Trait; use crate::locations::ReferencesTracker; use crate::node_interner::{FuncId, GlobalId, StructId, TraitId, TypeAliasId}; use crate::{Shared, Type, TypeAlias}; @@ -86,6 +88,21 @@ enum IntermediatePathResolutionItem { pub(crate) type PathResolutionResult = Result; +enum StructMethodLookupResult { + /// The method could not be found. There might be trait methods that could be imported, + /// but none of them are. + NotFound(Vec), + /// Found a struct method. + FoundStructMethod(PerNs), + /// Found a trait method and it's currently in scope. + FoundTraitMethod(PerNs, TraitId), + /// There's only one trait method that matches, but it's not in scope + /// (we'll warn about this to avoid introducing a large breaking change) + FoundOneTraitMethodButNotInScope(PerNs, TraitId), + /// Multiple trait method matches were found and they are all in scope. + FoundMultipleTraitMethods(Vec), +} + impl<'context> Elaborator<'context> { pub(super) fn resolve_path_or_error( &mut self, @@ -195,7 +212,9 @@ impl<'context> Elaborator<'context> { last_segment.ident.is_self_type_name(), ); - (current_module_id, intermediate_item) = match typ { + let current_module_id_is_struct; + + (current_module_id, current_module_id_is_struct, intermediate_item) = match typ { ModuleDefId::ModuleId(id) => { if last_segment_generics.is_some() { errors.push(PathResolutionError::TurbofishNotAllowedOnItem { @@ -204,10 +223,11 @@ impl<'context> Elaborator<'context> { }); } - (id, IntermediatePathResolutionItem::Module(id)) + (id, false, IntermediatePathResolutionItem::Module(id)) } ModuleDefId::TypeId(id) => ( id.module_id(), + true, IntermediatePathResolutionItem::Struct( id, last_segment_generics.as_ref().map(|generics| Turbofish { @@ -224,6 +244,7 @@ impl<'context> Elaborator<'context> { ( module_id, + true, IntermediatePathResolutionItem::TypeAlias( id, last_segment_generics.as_ref().map(|generics| Turbofish { @@ -235,6 +256,7 @@ impl<'context> Elaborator<'context> { } ModuleDefId::TraitId(id) => ( id.0, + false, IntermediatePathResolutionItem::Trait( id, last_segment_generics.as_ref().map(|generics| Turbofish { @@ -263,7 +285,57 @@ impl<'context> Elaborator<'context> { current_module = self.get_module(current_module_id); // Check if namespace - let found_ns = current_module.find_name(current_ident); + let found_ns = if current_module_id_is_struct { + match self.resolve_struct_function(starting_module, current_module, current_ident) { + StructMethodLookupResult::NotFound(vec) => { + if vec.is_empty() { + return Err(PathResolutionError::Unresolved(current_ident.clone())); + } else { + let traits = vecmap(vec, |trait_id| { + let trait_ = self.interner.get_trait(trait_id); + self.fully_qualified_trait_path(trait_) + }); + return Err( + PathResolutionError::UnresolvedWithPossibleTraitsToImport { + ident: current_ident.clone(), + traits, + }, + ); + } + } + StructMethodLookupResult::FoundStructMethod(per_ns) => per_ns, + StructMethodLookupResult::FoundTraitMethod(per_ns, trait_id) => { + let trait_ = self.interner.get_trait(trait_id); + self.usage_tracker.mark_as_used(starting_module, &trait_.name); + per_ns + } + StructMethodLookupResult::FoundOneTraitMethodButNotInScope( + per_ns, + trait_id, + ) => { + let trait_ = self.interner.get_trait(trait_id); + let trait_name = self.fully_qualified_trait_path(trait_); + errors.push(PathResolutionError::TraitMethodNotInScope { + ident: current_ident.clone(), + trait_name, + }); + per_ns + } + StructMethodLookupResult::FoundMultipleTraitMethods(vec) => { + let traits = vecmap(vec, |trait_id| { + let trait_ = self.interner.get_trait(trait_id); + self.usage_tracker.mark_as_used(starting_module, &trait_.name); + self.fully_qualified_trait_path(trait_) + }); + return Err(PathResolutionError::MultipleTraitsInScope { + ident: current_ident.clone(), + traits, + }); + } + } + } else { + current_module.find_name(current_ident) + }; if found_ns.is_none() { return Err(PathResolutionError::Unresolved(current_ident.clone())); } @@ -307,6 +379,78 @@ impl<'context> Elaborator<'context> { None } } + + fn resolve_struct_function( + &self, + starting_module_id: ModuleId, + current_module: &ModuleData, + ident: &Ident, + ) -> StructMethodLookupResult { + // If the current module is a struct, next we need to find a function for it. + // The function could be in the struct itself, or it could be defined in traits. + let item_scope = current_module.scope(); + let Some(values) = item_scope.values().get(ident) else { + return StructMethodLookupResult::NotFound(vec![]); + }; + + // First search if the function is defined in the struct itself + if let Some(item) = values.get(&None) { + return StructMethodLookupResult::FoundStructMethod(PerNs { + types: None, + values: Some(*item), + }); + } + + // Otherwise, the function could be defined in zero, one or more traits. + let starting_module = self.get_module(starting_module_id); + + // Gather a list of items for which their trait is in scope. + let mut results = Vec::new(); + + for (trait_id, item) in values.iter() { + let trait_id = trait_id.expect("The None option was already considered before"); + let trait_ = self.interner.get_trait(trait_id); + let Some(map) = starting_module.scope().types().get(&trait_.name) else { + continue; + }; + let Some(imported_item) = map.get(&None) else { + continue; + }; + if imported_item.0 == ModuleDefId::TraitId(trait_id) { + results.push((trait_id, item)); + } + } + + if results.is_empty() { + if values.len() == 1 { + // This is the backwards-compatible case where there's a single trait method but it's not in scope + let (trait_id, item) = values.iter().next().expect("Expected an item"); + let trait_id = trait_id.expect("The None option was already considered before"); + let per_ns = PerNs { types: None, values: Some(*item) }; + return StructMethodLookupResult::FoundOneTraitMethodButNotInScope( + per_ns, trait_id, + ); + } else { + let trait_ids = vecmap(values, |(trait_id, _)| { + trait_id.expect("The none option was already considered before") + }); + return StructMethodLookupResult::NotFound(trait_ids); + } + } + + if results.len() > 1 { + let trait_ids = vecmap(results, |(trait_id, _)| trait_id); + return StructMethodLookupResult::FoundMultipleTraitMethods(trait_ids); + } + + let (trait_id, item) = results.remove(0); + let per_ns = PerNs { types: None, values: Some(*item) }; + StructMethodLookupResult::FoundTraitMethod(per_ns, trait_id) + } + + fn fully_qualified_trait_path(&self, trait_: &Trait) -> String { + fully_qualified_module_path(self.def_maps, self.crate_graph, &trait_.crate_id, trait_.id.0) + } } fn merge_intermediate_path_resolution_item_with_module_def_id( diff --git a/compiler/noirc_frontend/src/graph/mod.rs b/compiler/noirc_frontend/src/graph/mod.rs index 094594a50ac..c007d6792bd 100644 --- a/compiler/noirc_frontend/src/graph/mod.rs +++ b/compiler/noirc_frontend/src/graph/mod.rs @@ -113,6 +113,41 @@ pub struct CrateGraph { arena: FxHashMap, } +impl CrateGraph { + /// Tries to find the requested crate in the current one's dependencies, + /// otherwise walks down the crate dependency graph from crate_id until we reach it. + /// This is needed in case a library (lib1) re-export a structure defined in another library (lib2) + /// In that case, we will get [lib1,lib2] when looking for a struct defined in lib2, + /// re-exported by lib1 and used by the main crate. + /// Returns the path from crate_id to target_crate_id + pub(crate) fn find_dependencies( + &self, + crate_id: &CrateId, + target_crate_id: &CrateId, + ) -> Option> { + self[crate_id] + .dependencies + .iter() + .find_map(|dep| { + if &dep.crate_id == target_crate_id { + Some(vec![dep.name.to_string()]) + } else { + None + } + }) + .or_else(|| { + self[crate_id].dependencies.iter().find_map(|dep| { + if let Some(mut path) = self.find_dependencies(&dep.crate_id, target_crate_id) { + path.insert(0, dep.name.to_string()); + Some(path) + } else { + None + } + }) + }) + } +} + /// List of characters that are not allowed in a crate name /// For example, Hyphen(-) is disallowed as it is similar to underscore(_) /// and we do not want names that differ by a hyphen diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index d9d6e150a7a..ffb885cc121 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -1,4 +1,4 @@ -use crate::graph::CrateId; +use crate::graph::{CrateGraph, CrateId}; use crate::hir::def_collector::dc_crate::{CompilationError, DefCollector}; use crate::hir::Context; use crate::node_interner::{FuncId, GlobalId, NodeInterner, StructId}; @@ -314,6 +314,31 @@ impl CrateDefMap { } } +pub fn fully_qualified_module_path( + def_maps: &DefMaps, + crate_graph: &CrateGraph, + crate_id: &CrateId, + module_id: ModuleId, +) -> String { + let child_id = module_id.local_id.0; + + let def_map = + def_maps.get(&module_id.krate).expect("The local crate should be analyzed already"); + + let module = &def_map.modules()[module_id.local_id.0]; + + let module_path = def_map.get_module_path_with_separator(child_id, module.parent, "::"); + + if &module_id.krate == crate_id { + module_path + } else { + let crates = crate_graph + .find_dependencies(crate_id, &module_id.krate) + .expect("The module was supposed to be defined in a dependency"); + crates.join("::") + "::" + &module_path + } +} + /// Specifies a contract function and extra metadata that /// one can use when processing a contract function. /// diff --git a/compiler/noirc_frontend/src/hir/def_map/namespace.rs b/compiler/noirc_frontend/src/hir/def_map/namespace.rs index a600d98dd8b..255f5c14a84 100644 --- a/compiler/noirc_frontend/src/hir/def_map/namespace.rs +++ b/compiler/noirc_frontend/src/hir/def_map/namespace.rs @@ -2,7 +2,7 @@ use super::ModuleDefId; use crate::ast::ItemVisibility; // This works exactly the same as in r-a, just simplified -#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)] pub struct PerNs { pub types: Option<(ModuleDefId, ItemVisibility, bool)>, pub values: Option<(ModuleDefId, ItemVisibility, bool)>, diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index 2bd1a852f05..3342b3f8b50 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -14,7 +14,7 @@ use crate::parser::ParserError; use crate::usage_tracker::UsageTracker; use crate::{Generics, Kind, ParsedModule, ResolvedGeneric, TypeVariable}; use def_collector::dc_crate::CompilationError; -use def_map::{Contract, CrateDefMap}; +use def_map::{fully_qualified_module_path, Contract, CrateDefMap}; use fm::{FileId, FileManager}; use iter_extended::vecmap; use noirc_errors::Location; @@ -152,56 +152,7 @@ impl Context<'_, '_> { /// For example, if you project contains a `main.nr` and `foo.nr` and you provide the `main_crate_id` and the /// `bar_struct_id` where the `Bar` struct is inside `foo.nr`, this function would return `foo::Bar` as a [String]. pub fn fully_qualified_struct_path(&self, crate_id: &CrateId, id: StructId) -> String { - let module_id = id.module_id(); - let child_id = module_id.local_id.0; - let def_map = - self.def_map(&module_id.krate).expect("The local crate should be analyzed already"); - - let module = self.module(module_id); - - let module_path = def_map.get_module_path_with_separator(child_id, module.parent, "::"); - - if &module_id.krate == crate_id { - module_path - } else { - let crates = self - .find_dependencies(crate_id, &module_id.krate) - .expect("The Struct was supposed to be defined in a dependency"); - crates.join("::") + "::" + &module_path - } - } - - /// Tries to find the requested crate in the current one's dependencies, - /// otherwise walks down the crate dependency graph from crate_id until we reach it. - /// This is needed in case a library (lib1) re-export a structure defined in another library (lib2) - /// In that case, we will get [lib1,lib2] when looking for a struct defined in lib2, - /// re-exported by lib1 and used by the main crate. - /// Returns the path from crate_id to target_crate_id - fn find_dependencies( - &self, - crate_id: &CrateId, - target_crate_id: &CrateId, - ) -> Option> { - self.crate_graph[crate_id] - .dependencies - .iter() - .find_map(|dep| { - if &dep.crate_id == target_crate_id { - Some(vec![dep.name.to_string()]) - } else { - None - } - }) - .or_else(|| { - self.crate_graph[crate_id].dependencies.iter().find_map(|dep| { - if let Some(mut path) = self.find_dependencies(&dep.crate_id, target_crate_id) { - path.insert(0, dep.name.to_string()); - Some(path) - } else { - None - } - }) - }) + fully_qualified_module_path(&self.def_maps, &self.crate_graph, crate_id, id.module_id()) } pub fn function_meta(&self, func_id: &FuncId) -> &FuncMeta { diff --git a/compiler/noirc_frontend/src/hir/resolution/import.rs b/compiler/noirc_frontend/src/hir/resolution/import.rs index 376b85bfbd9..11b694aa61b 100644 --- a/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -1,3 +1,4 @@ +use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic, Span}; use thiserror::Error; @@ -48,6 +49,12 @@ pub enum PathResolutionError { TurbofishNotAllowedOnItem { item: String, span: Span }, #[error("{ident} is a {kind}, not a module")] NotAModule { ident: Ident, kind: &'static str }, + #[error("trait `{trait_name}` which provides `{ident}` is implemented but not in scope, please import it")] + TraitMethodNotInScope { ident: Ident, trait_name: String }, + #[error("Could not resolve '{ident}' in path")] + UnresolvedWithPossibleTraitsToImport { ident: Ident, traits: Vec }, + #[error("Multiple applicable items in scope")] + MultipleTraitsInScope { ident: Ident, traits: Vec }, } #[derive(Debug)] @@ -85,6 +92,28 @@ impl<'a> From<&'a PathResolutionError> for CustomDiagnostic { PathResolutionError::NotAModule { ident, kind: _ } => { CustomDiagnostic::simple_error(error.to_string(), String::new(), ident.span()) } + PathResolutionError::TraitMethodNotInScope { ident, .. } => { + CustomDiagnostic::simple_warning(error.to_string(), String::new(), ident.span()) + } + PathResolutionError::UnresolvedWithPossibleTraitsToImport { ident, traits } => { + let traits = vecmap(traits, |trait_name| format!("`{}`", trait_name)); + CustomDiagnostic::simple_error( + error.to_string(), + format!("The following traits which provide `{ident}` are implemented but not in scope: {}", traits.join(", ")), + ident.span(), + ) + } + PathResolutionError::MultipleTraitsInScope { ident, traits } => { + let traits = vecmap(traits, |trait_name| format!("`{}`", trait_name)); + CustomDiagnostic::simple_error( + error.to_string(), + format!( + "All these trait which provide `{ident}` are implemented and in scope: {}", + traits.join(", ") + ), + ident.span(), + ) + } } } } diff --git a/compiler/noirc_frontend/src/tests/traits.rs b/compiler/noirc_frontend/src/tests/traits.rs index 811a32bab86..a5d1591e012 100644 --- a/compiler/noirc_frontend/src/tests/traits.rs +++ b/compiler/noirc_frontend/src/tests/traits.rs @@ -1,5 +1,6 @@ use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::resolution::errors::ResolverError; +use crate::hir::resolution::import::PathResolutionError; use crate::hir::type_check::TypeCheckError; use crate::tests::{get_program_errors, get_program_with_maybe_parser_errors}; @@ -652,3 +653,197 @@ fn does_not_crash_on_as_trait_path_with_empty_path() { ); assert!(!errors.is_empty()); } + +#[test] +fn warns_if_trait_is_not_in_scope_for_function_call_and_there_is_only_one_trait_method() { + let src = r#" + fn main() { + let _ = Bar::foo(); + } + + pub struct Bar { + } + + mod private_mod { + pub trait Foo { + fn foo() -> i32; + } + + impl Foo for super::Bar { + fn foo() -> i32 { + 42 + } + } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::PathResolutionError( + PathResolutionError::TraitMethodNotInScope { ident, trait_name }, + )) = &errors[0].0 + else { + panic!("Expected a 'trait method not in scope' error"); + }; + assert_eq!(ident.to_string(), "foo"); + assert_eq!(trait_name, "private_mod::Foo"); +} + +#[test] +fn calls_trait_function_if_it_is_in_scope() { + let src = r#" + use private_mod::Foo; + + fn main() { + let _ = Bar::foo(); + } + + pub struct Bar { + } + + mod private_mod { + pub trait Foo { + fn foo() -> i32; + } + + impl Foo for super::Bar { + fn foo() -> i32 { + 42 + } + } + } + "#; + assert_no_errors(src); +} + +#[test] +fn calls_trait_function_it_is_only_candidate_in_scope() { + let src = r#" + use private_mod::Foo; + + fn main() { + let _ = Bar::foo(); + } + + pub struct Bar { + } + + mod private_mod { + pub trait Foo { + fn foo() -> i32; + } + + impl Foo for super::Bar { + fn foo() -> i32 { + 42 + } + } + + pub trait Foo2 { + fn foo() -> i32; + } + + impl Foo2 for super::Bar { + fn foo() -> i32 { + 42 + } + } + } + "#; + assert_no_errors(src); +} + +#[test] +fn errors_if_trait_is_not_in_scope_for_function_call_and_there_are_multiple_candidates() { + let src = r#" + fn main() { + let _ = Bar::foo(); + } + + pub struct Bar { + } + + mod private_mod { + pub trait Foo { + fn foo() -> i32; + } + + impl Foo for super::Bar { + fn foo() -> i32 { + 42 + } + } + + pub trait Foo2 { + fn foo() -> i32; + } + + impl Foo2 for super::Bar { + fn foo() -> i32 { + 42 + } + } + } + "#; + let mut errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::PathResolutionError( + PathResolutionError::UnresolvedWithPossibleTraitsToImport { ident, mut traits }, + )) = errors.remove(0).0 + else { + panic!("Expected a 'trait method not in scope' error"); + }; + assert_eq!(ident.to_string(), "foo"); + traits.sort(); + assert_eq!(traits, vec!["private_mod::Foo", "private_mod::Foo2"]); +} + +#[test] +fn errors_if_multiple_trait_methods_are_in_scope() { + let src = r#" + use private_mod::Foo; + use private_mod::Foo2; + + fn main() { + let _ = Bar::foo(); + } + + pub struct Bar { + } + + mod private_mod { + pub trait Foo { + fn foo() -> i32; + } + + impl Foo for super::Bar { + fn foo() -> i32 { + 42 + } + } + + pub trait Foo2 { + fn foo() -> i32; + } + + impl Foo2 for super::Bar { + fn foo() -> i32 { + 42 + } + } + } + "#; + let mut errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::PathResolutionError( + PathResolutionError::MultipleTraitsInScope { ident, mut traits }, + )) = errors.remove(0).0 + else { + panic!("Expected a 'trait method not in scope' error"); + }; + assert_eq!(ident.to_string(), "foo"); + traits.sort(); + assert_eq!(traits, vec!["private_mod::Foo", "private_mod::Foo2"]); +} diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index 0ad39c518c4..7aed5e6a0e4 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -620,6 +620,7 @@ mod bounded_vec_tests { mod trait_from { use crate::collections::bounded_vec::BoundedVec; + use crate::convert::From; #[test] fn simple() { diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index f2234300ab2..4edda9a3120 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -13,6 +13,8 @@ pub mod typed_expr; pub mod quoted; pub mod unresolved_type; +use crate::default::Default; + /// Calling unquote as a macro (via `unquote!(arg)`) will unquote /// its argument. Since this is the effect `!` already does, `unquote` /// itself does not need to do anything besides return its argument.