diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 23164c57080..08b89a99fb0 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -5,12 +5,12 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::{ - ArrayLiteral, BinaryOpKind, BlockExpression, CallExpression, CastExpression, + ArrayLiteral, AsTraitPath, BinaryOpKind, BlockExpression, CallExpression, CastExpression, ConstrainExpression, ConstrainKind, ConstructorExpression, Expression, ExpressionKind, Ident, IfExpression, IndexExpression, InfixExpression, ItemVisibility, Lambda, Literal, MatchExpression, MemberAccessExpression, MethodCallExpression, Path, PathSegment, - PrefixExpression, StatementKind, UnaryOp, UnresolvedTypeData, UnresolvedTypeExpression, - UnsafeExpression, + PrefixExpression, StatementKind, TraitBound, UnaryOp, UnresolvedTraitConstraint, + UnresolvedTypeData, UnresolvedTypeExpression, UnsafeExpression, }, hir::{ comptime::{self, InterpreterError}, @@ -25,7 +25,7 @@ use crate::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, HirConstrainExpression, HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirLiteral, - HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, + HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, ImplKind, TraitMethod, }, stmt::{HirLetStatement, HirPattern, HirStatement}, traits::{ResolvedTraitBound, TraitConstraint}, @@ -94,11 +94,8 @@ impl Elaborator<'_> { self.push_err(ResolverError::UnquoteUsedOutsideQuote { location: expr.location }); (HirExpression::Error, Type::Error) } - ExpressionKind::AsTraitPath(_) => { - self.push_err(ResolverError::AsTraitPathNotYetImplemented { - location: expr.location, - }); - (HirExpression::Error, Type::Error) + ExpressionKind::AsTraitPath(path) => { + return self.elaborate_as_trait_path(path); } ExpressionKind::TypePath(path) => return self.elaborate_type_path(path), }; @@ -1319,4 +1316,55 @@ impl Elaborator<'_> { let (expr_id, typ) = self.inline_comptime_value(result, location); Some((self.interner.expression(&expr_id), typ)) } + + fn elaborate_as_trait_path(&mut self, path: AsTraitPath) -> (ExprId, Type) { + let location = path.typ.location.merge(path.trait_path.location); + + let constraint = UnresolvedTraitConstraint { + typ: path.typ, + trait_bound: TraitBound { + trait_path: path.trait_path, + trait_id: None, + trait_generics: path.trait_generics, + }, + }; + + let typ = self.resolve_type(constraint.typ.clone()); + let Some(trait_bound) = self.resolve_trait_bound(&constraint.trait_bound) else { + // resolve_trait_bound only returns None if it has already issued an error, so don't + // issue another here. + let error = self.interner.push_expr_full(HirExpression::Error, location, Type::Error); + return (error, Type::Error); + }; + + let constraint = TraitConstraint { typ, trait_bound }; + + let the_trait = self.interner.get_trait(constraint.trait_bound.trait_id); + let Some(method) = the_trait.find_method(&path.impl_item.0.contents) else { + let trait_name = the_trait.name.to_string(); + let method_name = path.impl_item.to_string(); + let location = path.impl_item.location(); + self.push_err(ResolverError::NoSuchMethodInTrait { trait_name, method_name, location }); + let error = self.interner.push_expr_full(HirExpression::Error, location, Type::Error); + return (error, Type::Error); + }; + + let trait_method = + TraitMethod { method_id: method, constraint: constraint.clone(), assumed: true }; + + let definition_id = self.interner.trait_method_id(trait_method.method_id); + + let ident = HirIdent { + location: path.impl_item.location(), + id: definition_id, + impl_kind: ImplKind::TraitMethod(trait_method), + }; + + let id = self.interner.push_expr(HirExpression::Ident(ident.clone(), None)); + self.interner.push_expr_location(id, location); + + let typ = self.type_check_variable(ident, id, None); + self.interner.push_expr_type(id, typ.clone()); + (id, typ) + } } diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index 9847ced3b41..ae7cac86998 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -132,8 +132,6 @@ pub enum ResolverError { ArrayLengthInterpreter { error: InterpreterError }, #[error("The unquote operator '$' can only be used within a quote expression")] UnquoteUsedOutsideQuote { location: Location }, - #[error("\"as trait path\" not yet implemented")] - AsTraitPathNotYetImplemented { location: Location }, #[error("Invalid syntax in macro call")] InvalidSyntaxInMacroCall { location: Location }, #[error("Macros must be comptime functions")] @@ -194,6 +192,8 @@ pub enum ResolverError { TypeUnsupportedInMatch { typ: Type, location: Location }, #[error("Expected a struct, enum, or literal value in pattern, but found a {item}")] UnexpectedItemInPattern { location: Location, item: &'static str }, + #[error("Trait `{trait_name}` doesn't have a method named `{method_name}`")] + NoSuchMethodInTrait { trait_name: String, method_name: String, location: Location }, } impl ResolverError { @@ -247,7 +247,6 @@ impl ResolverError { | ResolverError::SelfReferentialType { location } | ResolverError::NumericGenericUsedForType { location, .. } | ResolverError::UnquoteUsedOutsideQuote { location } - | ResolverError::AsTraitPathNotYetImplemented { location } | ResolverError::InvalidSyntaxInMacroCall { location } | ResolverError::MacroIsNotComptime { location } | ResolverError::NonFunctionInAnnotation { location } @@ -263,6 +262,7 @@ impl ResolverError { | ResolverError::NonIntegerGlobalUsedInPattern { location, .. } | ResolverError::TypeUnsupportedInMatch { location, .. } | ResolverError::UnexpectedItemInPattern { location, .. } + | ResolverError::NoSuchMethodInTrait { location, .. } | ResolverError::VariableAlreadyDefinedInPattern { new_location: location, .. } => { *location } @@ -660,13 +660,6 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *location, ) }, - ResolverError::AsTraitPathNotYetImplemented { location } => { - Diagnostic::simple_error( - "\"as trait path\" not yet implemented".into(), - "".into(), - *location, - ) - }, ResolverError::InvalidSyntaxInMacroCall { location } => { Diagnostic::simple_error( "Invalid syntax in macro call".into(), @@ -824,6 +817,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *location, ) }, + ResolverError::NoSuchMethodInTrait { trait_name, method_name, location } => { + Diagnostic::simple_error( + format!("Trait `{trait_name}` has no method named `{method_name}`"), + String::new(), + *location, + ) + }, } } } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 250d3f752ac..1fb70ceeee2 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -725,6 +725,15 @@ impl NodeInterner { ExprId(self.nodes.insert(Node::Expression(expr))) } + /// Intern an expression with everything needed for it (location & Type) + /// instead of requiring they be pushed later. + pub fn push_expr_full(&mut self, expr: HirExpression, location: Location, typ: Type) -> ExprId { + let id = self.push_expr(expr); + self.push_expr_location(id, location); + self.push_expr_type(id, typ); + id + } + /// Stores the span for an interned expression. pub fn push_expr_location(&mut self, expr_id: ExprId, location: Location) { self.id_to_location.insert(expr_id.into(), location); diff --git a/compiler/noirc_frontend/src/tests/traits.rs b/compiler/noirc_frontend/src/tests/traits.rs index e5b19e695d4..e9dc6010fd1 100644 --- a/compiler/noirc_frontend/src/tests/traits.rs +++ b/compiler/noirc_frontend/src/tests/traits.rs @@ -1389,3 +1389,36 @@ fn calls_trait_method_using_struct_name_when_multiple_impls_exist_and_errors_tur assert_eq!(errors.len(), 1); assert!(matches!(errors[0], CompilationError::TypeError(TypeCheckError::TypeMismatch { .. }))); } + +#[test] +fn as_trait_path_in_expression() { + let src = r#" + fn main() { + cursed::(); + } + + fn cursed() + where T: Foo + Foo2 + { + ::bar(1); + ::bar(()); + + // Use each function with different generic arguments + ::bar(()); + } + + trait Foo { fn bar(x: U); } + trait Foo2 { fn bar(x: U); } + + pub struct S {} + + impl Foo for S { + fn bar(_x: Z) {} + } + + impl Foo2 for S { + fn bar(_x: Z) {} + } + "#; + assert_no_errors(src); +} diff --git a/docs/docs/noir/concepts/traits.md b/docs/docs/noir/concepts/traits.md index 17cc04a9751..af5b396bfb8 100644 --- a/docs/docs/noir/concepts/traits.md +++ b/docs/docs/noir/concepts/traits.md @@ -153,6 +153,37 @@ fn main() { } ``` +## As Trait Syntax + +Rarely to call a method it may not be sufficient to use the general method call syntax of `obj.method(args)`. +One case where this may happen is if there are two traits in scope which both define a method with the same name. +For example: + +```rust +trait Foo { fn bar(); } +trait Foo2 { fn bar(); } + +fn example() + where T: Foo + Foo2 +{ + // How to call Foo::bar and Foo2::bar? +} +``` + +In the above example we have both `Foo` and `Foo2` which define a `bar` method. The normal way to resolve +this would be to use the static method syntax `Foo::bar(object)` but there is no object in this case and +`Self` does not appear in the type signature of `bar` at all so we would not know which impl to choose. +For these situations there is the "as trait" syntax: `::method(object, args...)` + +```rust +fn example() + where T: Foo + Foo2 +{ + ::bar(); + ::bar(); +} +``` + ## Generic Implementations You can add generics to a trait implementation by adding the generic list after the `impl` keyword: