diff --git a/crates/analyzer/src/context.rs b/crates/analyzer/src/context.rs index 3b5d3a37a3..1d8602f552 100644 --- a/crates/analyzer/src/context.rs +++ b/crates/analyzer/src/context.rs @@ -52,8 +52,13 @@ impl Analysis { pub trait AnalyzerContext { fn resolve_name(&self, name: &str, span: Span) -> Result, IncompleteItem>; + /// Resolves the given path and registers all errors fn resolve_path(&self, path: &ast::Path, span: Span) -> Result; - fn maybe_resolve_path(&self, path: &ast::Path) -> Option; + /// Resolves the given path only if it is visible. Does not register any errors + fn resolve_visible_path(&self, path: &ast::Path) -> Option; + /// Resolves the given path. Does not register any errors + fn resolve_any_path(&self, path: &ast::Path) -> Option; + fn add_diagnostic(&self, diag: Diagnostic); fn db(&self) -> &dyn AnalyzerDb; @@ -319,7 +324,11 @@ impl AnalyzerContext for TempContext { panic!("TempContext can't resolve paths") } - fn maybe_resolve_path(&self, _path: &ast::Path) -> Option { + fn resolve_visible_path(&self, _path: &ast::Path) -> Option { + panic!("TempContext can't resolve paths") + } + + fn resolve_any_path(&self, _path: &ast::Path) -> Option { panic!("TempContext can't resolve paths") } diff --git a/crates/analyzer/src/db/queries/module.rs b/crates/analyzer/src/db/queries/module.rs index 761cf1e8d2..db6db2839e 100644 --- a/crates/analyzer/src/db/queries/module.rs +++ b/crates/analyzer/src/db/queries/module.rs @@ -441,6 +441,14 @@ pub fn module_used_item_map( diagnostics.extend(items.diagnostics.iter().cloned()); for (name, (name_span, item)) in items.value.iter() { + if !item.is_public(db) { + diagnostics.push(errors::error( + &format!("{} {} is private", item.item_kind_display_name(), name,), + *name_span, + name.as_str(), + )); + } + if let Some((other_name_span, other_item)) = accum.insert(name.clone(), (*name_span, *item)) { diff --git a/crates/analyzer/src/namespace/items.rs b/crates/analyzer/src/namespace/items.rs index 220e02ddf1..f45e52cce1 100644 --- a/crates/analyzer/src/namespace/items.rs +++ b/crates/analyzer/src/namespace/items.rs @@ -824,7 +824,7 @@ impl TypeDef { } TypeDef::Enum(val) => val.pure_functions_as_items(db), TypeDef::Contract(val) => val.pure_functions_as_items(db), - _ => todo!("cannot access items in types yet"), + _ => Rc::new(indexmap! {}), } } @@ -1803,6 +1803,40 @@ impl ImplId { vec![], )); } + + if impl_fn.takes_self(db) != trait_fn.takes_self(db) { + let ((selfy_thing, selfy_span), (non_selfy_thing, non_selfy_span)) = + if impl_fn.takes_self(db) { + ( + ("impl", impl_fn.name_span(db)), + ("trait", trait_fn.name_span(db)), + ) + } else { + ( + ("trait", trait_fn.name_span(db)), + ("impl", impl_fn.name_span(db)), + ) + }; + sink.push(&errors::fancy_error( + format!( + "method `{}` has a `self` declaration in the {}, but not in the `{}`", + impl_fn.name(db), + selfy_thing, + non_selfy_thing + ), + vec![ + Label::primary( + selfy_span, + format!("`self` declared on the `{}`", selfy_thing), + ), + Label::primary( + non_selfy_span, + format!("no `self` declared on the `{}`", non_selfy_thing), + ), + ], + vec![], + )); + } } else { sink.push(&errors::fancy_error( format!( @@ -1903,19 +1937,9 @@ impl TraitId { } pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) { - db.trait_all_functions(*self).iter().for_each(|id| { - if !id.takes_self(db) { - sink.push(&errors::fancy_error( - "associated functions aren't yet supported in traits", - vec![Label::primary( - id.data(db).ast.span, - "function doesn't take `self`", - )], - vec!["Hint: add a `self` param to this function".into()], - )); - } - id.sink_diagnostics(db, sink) - }); + db.trait_all_functions(*self) + .iter() + .for_each(|id| id.sink_diagnostics(db, sink)); } } diff --git a/crates/analyzer/src/namespace/scopes.rs b/crates/analyzer/src/namespace/scopes.rs index 154f3a63ff..3036b7a9b9 100644 --- a/crates/analyzer/src/namespace/scopes.rs +++ b/crates/analyzer/src/namespace/scopes.rs @@ -141,7 +141,7 @@ impl<'a> AnalyzerContext for ItemScope<'a> { } } - fn maybe_resolve_path(&self, path: &ast::Path) -> Option { + fn resolve_visible_path(&self, path: &ast::Path) -> Option { let resolved = self.module.resolve_path_internal(self.db(), path); if resolved.diagnostics.len() > 0 { @@ -156,6 +156,16 @@ impl<'a> AnalyzerContext for ItemScope<'a> { } } + fn resolve_any_path(&self, path: &ast::Path) -> Option { + let resolved = self.module.resolve_path_internal(self.db(), path); + + if resolved.diagnostics.len() > 0 { + return None; + } + + resolved.value + } + fn add_diagnostic(&self, diag: Diagnostic) { self.diagnostics.borrow_mut().push(diag) } @@ -384,7 +394,7 @@ impl<'a> AnalyzerContext for FunctionScope<'a> { } } - fn maybe_resolve_path(&self, path: &ast::Path) -> Option { + fn resolve_visible_path(&self, path: &ast::Path) -> Option { let resolved = self .function .module(self.db()) @@ -402,6 +412,19 @@ impl<'a> AnalyzerContext for FunctionScope<'a> { } } + fn resolve_any_path(&self, path: &ast::Path) -> Option { + let resolved = self + .function + .module(self.db()) + .resolve_path_internal(self.db(), path); + + if resolved.diagnostics.len() > 0 { + return None; + } + + resolved.value + } + fn get_context_type(&self) -> Option { if let Ok(Some(NamedThing::Item(Item::Type(TypeDef::Struct(id))))) = self.resolve_name("Context", Span::dummy()) @@ -530,8 +553,12 @@ impl AnalyzerContext for BlockScope<'_, '_> { self.root.resolve_path(path, span) } - fn maybe_resolve_path(&self, path: &ast::Path) -> Option { - self.root.maybe_resolve_path(path) + fn resolve_visible_path(&self, path: &ast::Path) -> Option { + self.root.resolve_visible_path(path) + } + + fn resolve_any_path(&self, path: &ast::Path) -> Option { + self.root.resolve_any_path(path) } fn add_diagnostic(&self, diag: Diagnostic) { @@ -634,7 +661,7 @@ impl OptionExt for Option { /// Check an item visibility and sink diagnostics if an item is invisible from /// the scope. -fn check_visibility(context: &dyn AnalyzerContext, named_thing: &NamedThing, span: Span) { +pub fn check_visibility(context: &dyn AnalyzerContext, named_thing: &NamedThing, span: Span) { if let NamedThing::Item(item) = named_thing { let item_module = item .module(context.db()) diff --git a/crates/analyzer/src/namespace/types.rs b/crates/analyzer/src/namespace/types.rs index ec714b2b0c..a11920cdca 100644 --- a/crates/analyzer/src/namespace/types.rs +++ b/crates/analyzer/src/namespace/types.rs @@ -2,7 +2,9 @@ use crate::context::AnalyzerContext; use crate::display::DisplayWithDb; use crate::display::Displayable; use crate::errors::TypeError; -use crate::namespace::items::{ContractId, EnumId, FunctionSigId, ImplId, StructId, TraitId}; +use crate::namespace::items::{ + ContractId, EnumId, FunctionId, FunctionSigId, ImplId, Item, StructId, TraitId, +}; use crate::AnalyzerDb; use fe_common::impl_intern_key; @@ -55,6 +57,9 @@ pub enum Type { SPtr(TypeId), Mut(TypeId), } + +type TraitFunctionLookup = (Vec<(FunctionId, ImplId)>, Vec<(FunctionId, ImplId)>); + #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct TypeId(pub(crate) u32); impl_intern_key!(TypeId); @@ -162,8 +167,41 @@ impl TypeId { db.impl_for(*self, trait_) } - // Signature for the function with the given name defined directly on the type. - // Does not consider trait impls. + /// Looks up all possible candidates of the given function name that are implemented via traits. + /// Groups results in two lists, the first contains all theoretical possible candidates and + /// the second contains only those that are actually callable because the trait is in scope. + pub fn trait_function_candidates( + &self, + context: &mut dyn AnalyzerContext, + fn_name: &str, + ) -> TraitFunctionLookup { + let candidates = context + .db() + .all_impls(*self) + .iter() + .cloned() + .filter_map(|_impl| { + _impl + .function(context.db(), fn_name) + .map(|fun| (fun, _impl)) + }) + .collect::>(); + + let in_scope_candidates = candidates + .iter() + .cloned() + .filter(|(_, _impl)| { + context + .module() + .is_in_scope(context.db(), Item::Trait(_impl.trait_id(context.db()))) + }) + .collect::>(); + + (candidates, in_scope_candidates) + } + + /// Signature for the function with the given name defined directly on the type. + /// Does not consider trait impls. pub fn function_sig(&self, db: &dyn AnalyzerDb, name: &str) -> Option { match self.typ(db) { Type::SPtr(inner) => inner.function_sig(db, name), diff --git a/crates/analyzer/src/traversal/expressions.rs b/crates/analyzer/src/traversal/expressions.rs index 41aa120d7d..e2d89badee 100644 --- a/crates/analyzer/src/traversal/expressions.rs +++ b/crates/analyzer/src/traversal/expressions.rs @@ -5,7 +5,7 @@ use crate::errors::{self, FatalError, IndexingError, TypeCoercionError}; use crate::namespace::items::{ EnumVariantId, EnumVariantKind, FunctionId, FunctionSigId, ImplId, Item, StructId, TypeDef, }; -use crate::namespace::scopes::BlockScopeType; +use crate::namespace::scopes::{check_visibility, BlockScopeType}; use crate::namespace::types::{Array, Base, FeString, Integer, Tuple, Type, TypeDowncast, TypeId}; use crate::operations; use crate::traversal::call_args::{validate_arg_count, validate_named_args}; @@ -979,8 +979,143 @@ fn expr_call_path( generic_args: &Option>>, args: &Node>>, ) -> Result<(ExpressionAttributes, CallType), FatalError> { - let named_thing = context.resolve_path(path, func.span)?; - expr_call_named_thing(context, named_thing, func, generic_args, args) + match context.resolve_visible_path(path) { + Some(named_thing) => { + check_visibility(context, &named_thing, func.span); + validate_has_no_conflicting_trait_in_scope(context, &named_thing, path, func)?; + expr_call_named_thing(context, named_thing, func, generic_args, args) + } + // If we we can't resolve a call to a path e.g. `foo::Bar::do_thing()` there is a chance that `do_thing` + // still exists as as a trait associated function for `foo::Bar`. + None => expr_call_trait_associated_function(context, path, func, generic_args, args), + } +} + +fn validate_has_no_conflicting_trait_in_scope( + context: &mut dyn AnalyzerContext, + named_thing: &NamedThing, + path: &fe::Path, + func: &Node, +) -> Result<(), FatalError> { + let fn_name = &path.segments.last().unwrap().kind; + let parent_path = path.remove_last(); + let parent_thing = context.resolve_path(&parent_path, func.span)?; + + if let NamedThing::Item(Item::Type(val)) = parent_thing { + let type_id = val.type_id(context.db())?; + let (_, in_scope_candidates) = type_id.trait_function_candidates(context, fn_name); + + if !in_scope_candidates.is_empty() { + let labels = vec![Label::primary( + named_thing.name_span(context.db()).unwrap(), + format!( + "candidate 1 is defined here on the {}", + parent_thing.item_kind_display_name(), + ), + )] + .into_iter() + .chain( + in_scope_candidates + .iter() + .enumerate() + .map(|(idx, (fun, _impl))| { + Label::primary( + fun.name_span(context.db()), + format!( + "candidate #{} is defined here on trait `{}`", + idx + 2, + _impl.trait_id(context.db()).name(context.db()) + ), + ) + }), + ) + .collect::>(); + + return Err(FatalError::new(context.fancy_error( + "multiple applicable items in scope", + labels, + vec![ + "Hint: Rename one of the methods or make sure only one of them is in scope" + .into(), + ], + ))); + } + } + + Ok(()) +} + +fn expr_call_trait_associated_function( + context: &mut dyn AnalyzerContext, + path: &fe::Path, + func: &Node, + generic_args: &Option>>, + args: &Node>>, +) -> Result<(ExpressionAttributes, CallType), FatalError> { + let fn_name = &path.segments.last().unwrap().kind; + let parent_path = path.remove_last(); + let parent_thing = context.resolve_path(&parent_path, func.span)?; + + if let NamedThing::Item(Item::Type(val)) = parent_thing { + let type_id = val.type_id(context.db())?; + + let (candidates, in_scope_candidates) = type_id.trait_function_candidates(context, fn_name); + + if in_scope_candidates.len() > 1 { + context.fancy_error( + "multiple applicable items in scope", + in_scope_candidates + .iter() + .enumerate() + .map(|(idx, (fun, _impl))| { + Label::primary( + fun.name_span(context.db()), + format!( + "candidate #{} is defined here on trait `{}`", + idx + 1, + _impl.trait_id(context.db()).name(context.db()) + ), + ) + }) + .collect(), + vec![ + "Hint: Rename one of the methods or make sure only one of them is in scope" + .into(), + ], + ); + // We arbitrarily carry on with the first candidate since the error doesn't need to be fatal + let (fun, _) = in_scope_candidates[0]; + return expr_call_pure(context, fun, func.span, generic_args, args); + } else if in_scope_candidates.is_empty() && !candidates.is_empty() { + context.fancy_error( + "Applicable items exist but are not in scope", + candidates.iter().enumerate().map(|(idx, (fun, _impl ))| { + Label::primary(fun.name_span(context.db()), format!( + "candidate #{} is defined here on trait `{}`", + idx + 1, + _impl.trait_id(context.db()).name(context.db()) + )) + }).collect(), + vec!["Hint: Bring one of these candidates in scope via `use module_name::trait_name`".into()], + ); + // We arbitrarily carry on with an applicable candidate since the error doesn't need to be fatal + let (fun, _) = candidates[0]; + return expr_call_pure(context, fun, func.span, generic_args, args); + } else if in_scope_candidates.len() == 1 { + let (fun, _) = in_scope_candidates[0]; + return expr_call_pure(context, fun, func.span, generic_args, args); + } + } + + // At this point, we will have an error so we run `resolve_path` to register any errors that we + // did not report yet + context.resolve_path(path, func.span)?; + + Err(FatalError::new(context.error( + "unresolved path item", + func.span, + "not found", + ))) } fn expr_call_named_thing( diff --git a/crates/analyzer/src/traversal/functions.rs b/crates/analyzer/src/traversal/functions.rs index c5f0980634..b87746c26f 100644 --- a/crates/analyzer/src/traversal/functions.rs +++ b/crates/analyzer/src/traversal/functions.rs @@ -208,7 +208,7 @@ fn match_pattern( tuple_pattern(scope, elts, &expected_elts, pat.span, None) } - Pattern::Path(path) => match scope.maybe_resolve_path(&path.kind) { + Pattern::Path(path) => match scope.resolve_visible_path(&path.kind) { Some(NamedThing::EnumVariant(variant)) => { let db = scope.db(); let parent_type = variant.parent(db).as_type(db); diff --git a/crates/analyzer/src/traversal/pattern_analysis.rs b/crates/analyzer/src/traversal/pattern_analysis.rs index 565bdca3c7..907a519fec 100644 --- a/crates/analyzer/src/traversal/pattern_analysis.rs +++ b/crates/analyzer/src/traversal/pattern_analysis.rs @@ -638,7 +638,7 @@ fn simplify_pattern( } } - Pattern::Path(path) => match scope.maybe_resolve_path(&path.kind) { + Pattern::Path(path) => match scope.resolve_visible_path(&path.kind) { Some(NamedThing::EnumVariant(variant)) => SimplifiedPatternKind::Constructor { kind: ConstructorKind::Enum(variant), fields: vec![], @@ -650,7 +650,7 @@ fn simplify_pattern( }, Pattern::PathTuple(path, elts) => { - let variant = match scope.maybe_resolve_path(&path.kind).unwrap() { + let variant = match scope.resolve_visible_path(&path.kind).unwrap() { NamedThing::EnumVariant(variant) => variant, _ => unreachable!(), }; @@ -668,7 +668,7 @@ fn simplify_pattern( fields: pat_fields, .. } => { - let (sid, ctor_kind) = match scope.maybe_resolve_path(&path.kind).unwrap() { + let (sid, ctor_kind) = match scope.resolve_visible_path(&path.kind).unwrap() { NamedThing::Item(Item::Type(TypeDef::Struct(sid))) => { (sid, ConstructorKind::Struct(sid)) } diff --git a/crates/analyzer/tests/errors.rs b/crates/analyzer/tests/errors.rs index 58e109a8a1..d0bbdb3add 100644 --- a/crates/analyzer/tests/errors.rs +++ b/crates/analyzer/tests/errors.rs @@ -209,7 +209,11 @@ test_stmt! { invert_non_numeric, "~true" } test_file! { ambiguous_traits } test_file! { ambiguous_traits2 } +test_file! { ambiguous_traits3 } +test_file! { ambiguous_traits4 } test_ingot! { trait_not_in_scope } +test_ingot! { trait_not_in_scope2 } +test_ingot! { call_trait_assoc_fn_on_invisible_type } test_file! { bad_enums } test_file! { enum_match } test_file! { enum_name_conflict } @@ -302,7 +306,6 @@ test_file! { struct_call_bad_args } test_file! { struct_call_without_kw_args } test_file! { struct_recursive_cycles } test_file! { trait_impl_mismatch } -test_file! { trait_fn_without_self } test_file! { trait_fn_with_generic_params } test_file! { traits_as_fields } test_file! { trait_conflicting_impls } diff --git a/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap b/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap index 6421b59799..cbd7e50d0d 100644 --- a/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap +++ b/crates/analyzer/tests/snapshots/analysis__basic_ingot.snap @@ -313,8 +313,8 @@ note: note: ┌─ ingots/basic_ingot/src/ding/dang.fe:1:1 │ -1 │ type Dang = Array - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Array +1 │ pub type Dang = Array + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Array note: diff --git a/crates/analyzer/tests/snapshots/errors__ambiguous_traits3.snap b/crates/analyzer/tests/snapshots/errors__ambiguous_traits3.snap new file mode 100644 index 0000000000..374e91ed1b --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__ambiguous_traits3.snap @@ -0,0 +1,17 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: multiple applicable items in scope + ┌─ compile_errors/ambiguous_traits3.fe:11:6 + │ +11 │ fn do() { + │ ^^ candidate #1 is defined here on trait `DoThing` + · +16 │ fn do() { + │ ^^ candidate #2 is defined here on trait `DoOtherThing` + │ + = Hint: Rename one of the methods or make sure only one of them is in scope + + diff --git a/crates/analyzer/tests/snapshots/errors__ambiguous_traits4.snap b/crates/analyzer/tests/snapshots/errors__ambiguous_traits4.snap new file mode 100644 index 0000000000..f8770289c5 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__ambiguous_traits4.snap @@ -0,0 +1,17 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: "error_string(&path, test_files::fixture(path))" + +--- +error: multiple applicable items in scope + ┌─ compile_errors/ambiguous_traits4.fe:6:10 + │ + 6 │ pub fn do() { + │ ^^ candidate 1 is defined here on the struct + · +12 │ fn do() { + │ ^^ candidate #2 is defined here on trait `DoThing` + │ + = Hint: Rename one of the methods or make sure only one of them is in scope + + diff --git a/crates/analyzer/tests/snapshots/errors__bad_ingot.snap b/crates/analyzer/tests/snapshots/errors__bad_ingot.snap index 349bd3bb75..df0b26d3a6 100644 --- a/crates/analyzer/tests/snapshots/errors__bad_ingot.snap +++ b/crates/analyzer/tests/snapshots/errors__bad_ingot.snap @@ -94,10 +94,10 @@ error: the struct `Foo` is private = `Foo` can only be used within `foo` = Hint: use `pub` to make `Foo` visible from outside of `foo` -error: incorrect type for `Foo` argument `my_num` - ┌─ compile_errors/bad_ingot/src/main.fe:8:33 +error: unresolved path item + ┌─ compile_errors/bad_ingot/src/main.fe:8:16 │ 8 │ return foo::Foo(my_num: true) - │ ^^^^ this has type `bool`; expected type `u256` + │ ^^^^^^^^ not found diff --git a/crates/analyzer/tests/snapshots/errors__bad_visibility.snap b/crates/analyzer/tests/snapshots/errors__bad_visibility.snap index 25663d9fe7..91343304c1 100644 --- a/crates/analyzer/tests/snapshots/errors__bad_visibility.snap +++ b/crates/analyzer/tests/snapshots/errors__bad_visibility.snap @@ -3,11 +3,47 @@ source: crates/analyzer/tests/errors.rs expression: error_string_ingot(&path) --- -error: unresolved path item +error: type MyInt is private + ┌─ compile_errors/bad_visibility/src/main.fe:1:11 + │ +1 │ use foo::{MyInt, MY_CONST, MyStruct, MyTrait, my_func, MyContract, MyEnum } + │ ^^^^^ MyInt + +error: constant MY_CONST is private + ┌─ compile_errors/bad_visibility/src/main.fe:1:18 + │ +1 │ use foo::{MyInt, MY_CONST, MyStruct, MyTrait, my_func, MyContract, MyEnum } + │ ^^^^^^^^ MY_CONST + +error: struct MyStruct is private + ┌─ compile_errors/bad_visibility/src/main.fe:1:28 + │ +1 │ use foo::{MyInt, MY_CONST, MyStruct, MyTrait, my_func, MyContract, MyEnum } + │ ^^^^^^^^ MyStruct + +error: trait MyTrait is private + ┌─ compile_errors/bad_visibility/src/main.fe:1:38 + │ +1 │ use foo::{MyInt, MY_CONST, MyStruct, MyTrait, my_func, MyContract, MyEnum } + │ ^^^^^^^ MyTrait + +error: function my_func is private + ┌─ compile_errors/bad_visibility/src/main.fe:1:47 + │ +1 │ use foo::{MyInt, MY_CONST, MyStruct, MyTrait, my_func, MyContract, MyEnum } + │ ^^^^^^^ my_func + +error: type MyContract is private + ┌─ compile_errors/bad_visibility/src/main.fe:1:56 + │ +1 │ use foo::{MyInt, MY_CONST, MyStruct, MyTrait, my_func, MyContract, MyEnum } + │ ^^^^^^^^^^ MyContract + +error: type MyEnum is private ┌─ compile_errors/bad_visibility/src/main.fe:1:68 │ 1 │ use foo::{MyInt, MY_CONST, MyStruct, MyTrait, my_func, MyContract, MyEnum } - │ ^^^^^^ not found + │ ^^^^^^ MyEnum error: the type `MyInt` is private ┌─ compile_errors/bad_visibility/src/main.fe:7:33 @@ -101,10 +137,24 @@ error: the function `my_func` is private = `my_func` can only be used within `foo` = Hint: use `pub` to make `my_func` visible from outside of `foo` -error: the type `MyContract` is private +error: the type `MyEnum` is private ┌─ compile_errors/bad_visibility/src/main.fe:25:16 │ -25 │ let _: MyContract = MyContract(addr) +25 │ let e: MyEnum = MyEnum::Some + │ ^^^^^^ this type is not `pub` + │ + ┌─ compile_errors/bad_visibility/src/foo.fe:17:6 + │ +17 │ enum MyEnum { + │ ------ `MyEnum` is defined here + │ + = `MyEnum` can only be used within `foo` + = Hint: use `pub` to make `MyEnum` visible from outside of `foo` + +error: the type `MyContract` is private + ┌─ compile_errors/bad_visibility/src/main.fe:29:16 + │ +29 │ let _: MyContract = MyContract(addr) │ ^^^^^^^^^^ this type is not `pub` │ ┌─ compile_errors/bad_visibility/src/foo.fe:13:10 @@ -116,9 +166,9 @@ error: the type `MyContract` is private = Hint: use `pub` to make `MyContract` visible from outside of `foo` error: the type `MyContract` is private - ┌─ compile_errors/bad_visibility/src/main.fe:25:29 + ┌─ compile_errors/bad_visibility/src/main.fe:29:29 │ -25 │ let _: MyContract = MyContract(addr) +29 │ let _: MyContract = MyContract(addr) │ ^^^^^^^^^^ this type is not `pub` │ ┌─ compile_errors/bad_visibility/src/foo.fe:13:10 @@ -130,9 +180,9 @@ error: the type `MyContract` is private = Hint: use `pub` to make `MyContract` visible from outside of `foo` error: the type `MyContract` is private - ┌─ compile_errors/bad_visibility/src/main.fe:26:9 + ┌─ compile_errors/bad_visibility/src/main.fe:30:9 │ -26 │ MyContract.create(ctx, 1) +30 │ MyContract.create(ctx, 1) │ ^^^^^^^^^^ this type is not `pub` │ ┌─ compile_errors/bad_visibility/src/foo.fe:13:10 diff --git a/crates/analyzer/tests/snapshots/errors__call_trait_assoc_fn_on_invisible_type.snap b/crates/analyzer/tests/snapshots/errors__call_trait_assoc_fn_on_invisible_type.snap new file mode 100644 index 0000000000..40dced16b0 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__call_trait_assoc_fn_on_invisible_type.snap @@ -0,0 +1,20 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: error_string_ingot(&path) + +--- +error: the struct `Bar` is private + ┌─ compile_errors/call_trait_assoc_fn_on_invisible_type/src/main.fe:5:5 + │ +5 │ foo::Bar::do() + │ ^^^^^^^^^^^^ this struct is not `pub` + │ + ┌─ compile_errors/call_trait_assoc_fn_on_invisible_type/src/foo.fe:5:8 + │ +5 │ struct Bar {} + │ --- `Bar` is defined here + │ + = `Bar` can only be used within `foo` + = Hint: use `pub` to make `Bar` visible from outside of `foo` + + diff --git a/crates/analyzer/tests/snapshots/errors__emittable_not_implementable.snap b/crates/analyzer/tests/snapshots/errors__emittable_not_implementable.snap index 44f3cee7b5..2e1b9f098d 100644 --- a/crates/analyzer/tests/snapshots/errors__emittable_not_implementable.snap +++ b/crates/analyzer/tests/snapshots/errors__emittable_not_implementable.snap @@ -3,6 +3,12 @@ source: crates/analyzer/tests/errors.rs expression: "error_string(&path, test_files::fixture(path))" --- +error: struct OutOfReachMarker is private + ┌─ compile_errors/emittable_not_implementable.fe:1:31 + │ +1 │ use std::context::{Emittable, OutOfReachMarker} + │ ^^^^^^^^^^^^^^^^ OutOfReachMarker + error: the struct `OutOfReachMarker` is private ┌─ compile_errors/emittable_not_implementable.fe:6:24 │ diff --git a/crates/analyzer/tests/snapshots/errors__trait_impl_mismatch.snap b/crates/analyzer/tests/snapshots/errors__trait_impl_mismatch.snap index feb3959fe5..eb7e04823a 100644 --- a/crates/analyzer/tests/snapshots/errors__trait_impl_mismatch.snap +++ b/crates/analyzer/tests/snapshots/errors__trait_impl_mismatch.snap @@ -9,7 +9,7 @@ error: method `this_has_wrong_args_in_impl` has incompatible parameters for `thi 3 │ fn this_has_wrong_args_in_impl(self, val: u8, val2: bool); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ signature of method in trait `Foo` · -11 │ fn this_has_wrong_args_in_impl(self, val: u16, val2: Array) {} +13 │ fn this_has_wrong_args_in_impl(self, val: u16, val2: Array) {} │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ signature of method in `impl` block error: method `this_has_wrong_return_type_in_impl` has an incompatible return type for `this_has_wrong_return_type_in_impl` of trait `Foo` @@ -18,13 +18,31 @@ error: method `this_has_wrong_return_type_in_impl` has an incompatible return ty 4 │ fn this_has_wrong_return_type_in_impl(self) -> bool; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ signature of method in trait `Foo` · -12 │ fn this_has_wrong_return_type_in_impl(self) -> u8 { +14 │ fn this_has_wrong_return_type_in_impl(self) -> u8 { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ signature of method in `impl` block +error: method `this_has_no_self_in_impl` has a `self` declaration in the trait, but not in the `impl` + ┌─ compile_errors/trait_impl_mismatch.fe:5:8 + │ + 5 │ fn this_has_no_self_in_impl(self); + │ ^^^^^^^^^^^^^^^^^^^^^^^^ `self` declared on the `trait` + · +17 │ fn this_has_no_self_in_impl() {} + │ ^^^^^^^^^^^^^^^^^^^^^^^^ no `self` declared on the `impl` + +error: method `this_has_self_in_impl` has a `self` declaration in the impl, but not in the `trait` + ┌─ compile_errors/trait_impl_mismatch.fe:6:8 + │ + 6 │ fn this_has_self_in_impl(); + │ ^^^^^^^^^^^^^^^^^^^^^ no `self` declared on the `trait` + · +18 │ fn this_has_self_in_impl(self) {} + │ ^^^^^^^^^^^^^^^^^^^^^ `self` declared on the `impl` + error: method `this_does_not_exist_in_trait` is not a member of trait `Foo` - ┌─ compile_errors/trait_impl_mismatch.fe:15:5 + ┌─ compile_errors/trait_impl_mismatch.fe:19:5 │ -15 │ fn this_does_not_exist_in_trait() {} +19 │ fn this_does_not_exist_in_trait() {} │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not a member of trait `Foo` error: not all members of trait `Foo` implemented, missing: `this_misses_in_impl` diff --git a/crates/analyzer/tests/snapshots/errors__trait_not_in_scope2.snap b/crates/analyzer/tests/snapshots/errors__trait_not_in_scope2.snap new file mode 100644 index 0000000000..650741c5c8 --- /dev/null +++ b/crates/analyzer/tests/snapshots/errors__trait_not_in_scope2.snap @@ -0,0 +1,17 @@ +--- +source: crates/analyzer/tests/errors.rs +expression: error_string_ingot(&path) + +--- +error: Applicable items exist but are not in scope + ┌─ compile_errors/trait_not_in_scope2/src/foo.fe:11:6 + │ +11 │ fn do() { + │ ^^ candidate #1 is defined here on trait `DoThing` + · +16 │ fn do() { + │ ^^ candidate #2 is defined here on trait `DoOtherThing` + │ + = Hint: Bring one of these candidates in scope via `use module_name::trait_name` + + diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index 971bcc73cb..2fa7b3612e 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -38,6 +38,14 @@ pub struct Path { pub segments: Vec>, } +impl Path { + pub fn remove_last(&self) -> Path { + Path { + segments: self.segments[0..self.segments.len() - 1].to_vec(), + } + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] pub struct Use { pub tree: Node, diff --git a/crates/test-files/fixtures/compile_errors/ambiguous_traits3.fe b/crates/test-files/fixtures/compile_errors/ambiguous_traits3.fe new file mode 100644 index 0000000000..5ee7225959 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/ambiguous_traits3.fe @@ -0,0 +1,24 @@ +trait DoThing { + fn do(); +} + +trait DoOtherThing { + fn do(); +} + + +impl DoThing for u256 { + fn do() { + } +} + +impl DoOtherThing for u256 { + fn do() { + } +} + +contract Example { + pub fn run_test(self) { + u256::do() + } +} diff --git a/crates/test-files/fixtures/compile_errors/ambiguous_traits4.fe b/crates/test-files/fixtures/compile_errors/ambiguous_traits4.fe new file mode 100644 index 0000000000..f2adcbcf82 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/ambiguous_traits4.fe @@ -0,0 +1,20 @@ +trait DoThing { + fn do(); +} + +struct Bar { + pub fn do() { + + } +} + +impl DoThing for Bar { + fn do() { + } +} + +contract Example { + pub fn run_test(self) { + Bar::do() + } +} diff --git a/crates/test-files/fixtures/compile_errors/bad_ingot/src/biz/bad.fe b/crates/test-files/fixtures/compile_errors/bad_ingot/src/biz/bad.fe index 0eb4b24e59..f9563fce44 100644 --- a/crates/test-files/fixtures/compile_errors/bad_ingot/src/biz/bad.fe +++ b/crates/test-files/fixtures/compile_errors/bad_ingot/src/biz/bad.fe @@ -1,3 +1,3 @@ -struct Bur {} +pub struct Bur {} -struct Bud {} +pub struct Bud {} diff --git a/crates/test-files/fixtures/compile_errors/bad_visibility/src/foo.fe b/crates/test-files/fixtures/compile_errors/bad_visibility/src/foo.fe index ba7a7d21b3..7b7d85f843 100644 --- a/crates/test-files/fixtures/compile_errors/bad_visibility/src/foo.fe +++ b/crates/test-files/fixtures/compile_errors/bad_visibility/src/foo.fe @@ -12,4 +12,9 @@ fn my_func() {} contract MyContract { x: i32 +} + +enum MyEnum { + Some + Thing } \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/bad_visibility/src/main.fe b/crates/test-files/fixtures/compile_errors/bad_visibility/src/main.fe index 4ff77bf25e..690ca05862 100644 --- a/crates/test-files/fixtures/compile_errors/bad_visibility/src/main.fe +++ b/crates/test-files/fixtures/compile_errors/bad_visibility/src/main.fe @@ -21,6 +21,10 @@ contract Main { my_func() } + pub fn priv_enum() { + let e: MyEnum = MyEnum::Some + } + pub fn priv_contract(mut ctx: Context, addr: address) { let _: MyContract = MyContract(addr) MyContract.create(ctx, 1) diff --git a/crates/test-files/fixtures/compile_errors/call_trait_assoc_fn_on_invisible_type/src/foo.fe b/crates/test-files/fixtures/compile_errors/call_trait_assoc_fn_on_invisible_type/src/foo.fe new file mode 100644 index 0000000000..2917cc9698 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/call_trait_assoc_fn_on_invisible_type/src/foo.fe @@ -0,0 +1,12 @@ +pub trait DoThing { + fn do(); +} + +struct Bar {} + +impl DoThing for Bar { + fn do() { + } +} + + diff --git a/crates/test-files/fixtures/compile_errors/call_trait_assoc_fn_on_invisible_type/src/main.fe b/crates/test-files/fixtures/compile_errors/call_trait_assoc_fn_on_invisible_type/src/main.fe new file mode 100644 index 0000000000..eb63410f10 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/call_trait_assoc_fn_on_invisible_type/src/main.fe @@ -0,0 +1,7 @@ +use foo::DoThing + +contract Example { + pub fn run_test(self) { + foo::Bar::do() + } +} diff --git a/crates/test-files/fixtures/compile_errors/trait_fn_without_self.fe b/crates/test-files/fixtures/compile_errors/trait_fn_without_self.fe deleted file mode 100644 index 906b15483f..0000000000 --- a/crates/test-files/fixtures/compile_errors/trait_fn_without_self.fe +++ /dev/null @@ -1,3 +0,0 @@ -trait Foo { - fn this_has_no_self(); -} diff --git a/crates/test-files/fixtures/compile_errors/trait_impl_mismatch.fe b/crates/test-files/fixtures/compile_errors/trait_impl_mismatch.fe index 0344138e69..d65e97d092 100644 --- a/crates/test-files/fixtures/compile_errors/trait_impl_mismatch.fe +++ b/crates/test-files/fixtures/compile_errors/trait_impl_mismatch.fe @@ -2,6 +2,8 @@ trait Foo { fn this_misses_in_impl(self); fn this_has_wrong_args_in_impl(self, val: u8, val2: bool); fn this_has_wrong_return_type_in_impl(self) -> bool; + fn this_has_no_self_in_impl(self); + fn this_has_self_in_impl(); } struct Bar {} @@ -12,5 +14,7 @@ impl Foo for Bar { fn this_has_wrong_return_type_in_impl(self) -> u8 { return 0 } + fn this_has_no_self_in_impl() {} + fn this_has_self_in_impl(self) {} fn this_does_not_exist_in_trait() {} } \ No newline at end of file diff --git a/crates/test-files/fixtures/compile_errors/trait_not_in_scope/src copy/foo.fe b/crates/test-files/fixtures/compile_errors/trait_not_in_scope/src copy/foo.fe new file mode 100644 index 0000000000..f10a16b23c --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/trait_not_in_scope/src copy/foo.fe @@ -0,0 +1,9 @@ +trait DoThing { + fn do(self); +} + + +impl DoThing for u256 { + fn do(self) { + } +} diff --git a/crates/test-files/fixtures/compile_errors/trait_not_in_scope/src copy/main.fe b/crates/test-files/fixtures/compile_errors/trait_not_in_scope/src copy/main.fe new file mode 100644 index 0000000000..bc643eb322 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/trait_not_in_scope/src copy/main.fe @@ -0,0 +1,6 @@ + +contract Example { + pub fn run_test(self) { + 1.do() + } +} diff --git a/crates/test-files/fixtures/compile_errors/trait_not_in_scope2/src/foo.fe b/crates/test-files/fixtures/compile_errors/trait_not_in_scope2/src/foo.fe new file mode 100644 index 0000000000..a9f55946ed --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/trait_not_in_scope2/src/foo.fe @@ -0,0 +1,18 @@ +trait DoThing { + fn do(); +} + +trait DoOtherThing { + fn do(); +} + + +impl DoThing for u256 { + fn do() { + } +} + +impl DoOtherThing for u256 { + fn do() { + } +} diff --git a/crates/test-files/fixtures/compile_errors/trait_not_in_scope2/src/main.fe b/crates/test-files/fixtures/compile_errors/trait_not_in_scope2/src/main.fe new file mode 100644 index 0000000000..065d2dda34 --- /dev/null +++ b/crates/test-files/fixtures/compile_errors/trait_not_in_scope2/src/main.fe @@ -0,0 +1,6 @@ + +contract Example { + pub fn run_test(self) { + u256::do() + } +} diff --git a/crates/test-files/fixtures/features/trait_associated_functions.fe b/crates/test-files/fixtures/features/trait_associated_functions.fe new file mode 100644 index 0000000000..265c4211b8 --- /dev/null +++ b/crates/test-files/fixtures/features/trait_associated_functions.fe @@ -0,0 +1,16 @@ +trait Max { + fn max() -> u8; +} + +impl Max for u8 { + fn max() -> u8 { + return u8(255) + } +} + +contract Example { + + pub fn run_test(self) { + assert u8::max() == 255 + } +} diff --git a/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dang.fe b/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dang.fe index 9d3f08aae1..118bf499ff 100644 --- a/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dang.fe +++ b/crates/test-files/fixtures/ingots/basic_ingot/src/ding/dang.fe @@ -1 +1 @@ -type Dang = Array +pub type Dang = Array diff --git a/crates/test-files/fixtures/ingots/trait_no_ambiguity/src/foo.fe b/crates/test-files/fixtures/ingots/trait_no_ambiguity/src/foo.fe new file mode 100644 index 0000000000..81b93946a6 --- /dev/null +++ b/crates/test-files/fixtures/ingots/trait_no_ambiguity/src/foo.fe @@ -0,0 +1,5 @@ +pub struct MyS { + fn x() -> u256 { + return 10 + } +} \ No newline at end of file diff --git a/crates/test-files/fixtures/ingots/trait_no_ambiguity/src/main.fe b/crates/test-files/fixtures/ingots/trait_no_ambiguity/src/main.fe new file mode 100644 index 0000000000..fe7274ad2d --- /dev/null +++ b/crates/test-files/fixtures/ingots/trait_no_ambiguity/src/main.fe @@ -0,0 +1,17 @@ +use foo::MyS + +trait Trait { + fn x() -> u256; +} + +impl Trait for MyS { + fn x() -> u256 { + return 10 + } +} + +contract Foo { + pub fn main() -> u256 { + return MyS::x() + } +} diff --git a/crates/test-files/fixtures/ingots/visibility_ingot/src/foo.fe b/crates/test-files/fixtures/ingots/visibility_ingot/src/foo.fe index 9cc700fffd..720ba0899f 100644 --- a/crates/test-files/fixtures/ingots/visibility_ingot/src/foo.fe +++ b/crates/test-files/fixtures/ingots/visibility_ingot/src/foo.fe @@ -2,7 +2,7 @@ pub struct Bing { pub my_address: address } -fn get_42_backend() -> u256 { +pub fn get_42_backend() -> u256 { return 42 } diff --git a/crates/tests/src/features.rs b/crates/tests/src/features.rs index d8952aa8e8..31868d70d7 100644 --- a/crates/tests/src/features.rs +++ b/crates/tests/src/features.rs @@ -2229,6 +2229,7 @@ fn ctx_init_in_call() { #[rstest( fixture_file, case::simple_traits("simple_traits.fe"), + case::trait_associated_functions("trait_associated_functions.fe"), case::generic_functions("generic_functions.fe"), case::generic_functions_primitves("generic_functions_primitves.fe"), case::contract_pure_fns("contract_pure_fns.fe") diff --git a/crates/tests/src/ingots.rs b/crates/tests/src/ingots.rs index fe4d77edd1..db1141ac65 100644 --- a/crates/tests/src/ingots.rs +++ b/crates/tests/src/ingots.rs @@ -23,6 +23,13 @@ fn test_ingot_with_visibility() { }) } +#[test] +fn test_trait_no_ambiguity() { + with_executor(&|mut executor| { + let _harness = deploy_ingot(&mut executor, "trait_no_ambiguity", "Foo", &[]); + }) +} + #[test] fn test_ingot_pub_contract() { with_executor(&|mut executor| { diff --git a/crates/tests/src/snapshots/fe_compiler_tests__features__case_2_trait_associated_functions.snap b/crates/tests/src/snapshots/fe_compiler_tests__features__case_2_trait_associated_functions.snap new file mode 100644 index 0000000000..f49845796f --- /dev/null +++ b/crates/tests/src/snapshots/fe_compiler_tests__features__case_2_trait_associated_functions.snap @@ -0,0 +1,7 @@ +--- +source: crates/tests/src/features.rs +expression: "format!(\"{}\", harness.gas_reporter)" + +--- +run_test([]) used 32 gas + diff --git a/crates/tests/src/snapshots/fe_compiler_tests__features__case_3_generic_functions.snap b/crates/tests/src/snapshots/fe_compiler_tests__features__case_3_generic_functions.snap new file mode 100644 index 0000000000..f49845796f --- /dev/null +++ b/crates/tests/src/snapshots/fe_compiler_tests__features__case_3_generic_functions.snap @@ -0,0 +1,7 @@ +--- +source: crates/tests/src/features.rs +expression: "format!(\"{}\", harness.gas_reporter)" + +--- +run_test([]) used 32 gas + diff --git a/crates/tests/src/snapshots/fe_compiler_tests__features__case_4_generic_functions_primitves.snap b/crates/tests/src/snapshots/fe_compiler_tests__features__case_4_generic_functions_primitves.snap new file mode 100644 index 0000000000..f49845796f --- /dev/null +++ b/crates/tests/src/snapshots/fe_compiler_tests__features__case_4_generic_functions_primitves.snap @@ -0,0 +1,7 @@ +--- +source: crates/tests/src/features.rs +expression: "format!(\"{}\", harness.gas_reporter)" + +--- +run_test([]) used 32 gas + diff --git a/crates/tests/src/snapshots/fe_compiler_tests__features__case_5_contract_pure_fns.snap b/crates/tests/src/snapshots/fe_compiler_tests__features__case_5_contract_pure_fns.snap new file mode 100644 index 0000000000..09b509cccb --- /dev/null +++ b/crates/tests/src/snapshots/fe_compiler_tests__features__case_5_contract_pure_fns.snap @@ -0,0 +1,7 @@ +--- +source: crates/tests/src/features.rs +expression: "format!(\"{}\", harness.gas_reporter)" + +--- +run_test([]) used 35 gas + diff --git a/newsfragments/805.feature.md b/newsfragments/805.feature.md new file mode 100644 index 0000000000..211245324f --- /dev/null +++ b/newsfragments/805.feature.md @@ -0,0 +1,23 @@ +Trait associated functions + +This change allows trait functions that do not take a `self` parameter. +The following demonstrates a possible trait associated function and its usage: + +``` +trait Max { + fn max(self) -> u8; +} + +impl Max for u8 { + fn max() -> u8 { + return u8(255) + } +} + +contract Example { + + pub fn run_test(self) { + assert u8::max() == 255 + } +} +``` diff --git a/newsfragments/815.bugfix.md b/newsfragments/815.bugfix.md new file mode 100644 index 0000000000..2ae72dd6b8 --- /dev/null +++ b/newsfragments/815.bugfix.md @@ -0,0 +1,5 @@ +Disallow importing private type via `use` + +The following was previously allowed but will now error: + +`use foo::PrivateStruct`