Skip to content
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
66 changes: 57 additions & 9 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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},
Expand Down Expand Up @@ -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),
};
Expand Down Expand Up @@ -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)
}
}
20 changes: 10 additions & 10 deletions compiler/noirc_frontend/src/hir/resolution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 }
Expand All @@ -263,6 +262,7 @@ impl ResolverError {
| ResolverError::NonIntegerGlobalUsedInPattern { location, .. }
| ResolverError::TypeUnsupportedInMatch { location, .. }
| ResolverError::UnexpectedItemInPattern { location, .. }
| ResolverError::NoSuchMethodInTrait { location, .. }
| ResolverError::VariableAlreadyDefinedInPattern { new_location: location, .. } => {
*location
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
)
},
}
}
}
9 changes: 9 additions & 0 deletions compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,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 219 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 224 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 @@ -697,7 +697,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 700 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 Down Expand Up @@ -725,6 +725,15 @@
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);
Expand Down Expand Up @@ -2171,11 +2180,11 @@
&mut self,
typ: UnresolvedTypeData,
) -> InternedUnresolvedTypeData {
InternedUnresolvedTypeData(self.interned_unresolved_type_datas.insert(typ))

Check warning on line 2183 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 2187 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
33 changes: 33 additions & 0 deletions compiler/noirc_frontend/src/tests/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<S>();
}

fn cursed<T>()
where T: Foo + Foo2
{
<T as Foo>::bar(1);
<T as Foo2>::bar(());

// Use each function with different generic arguments
<T as Foo>::bar(());
}

trait Foo { fn bar<U>(x: U); }
trait Foo2 { fn bar<U>(x: U); }

pub struct S {}

impl Foo for S {
fn bar<Z>(_x: Z) {}
}

impl Foo2 for S {
fn bar<Z>(_x: Z) {}
}
"#;
assert_no_errors(src);
}
31 changes: 31 additions & 0 deletions docs/docs/noir/concepts/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>()
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: `<Type as Trait>::method(object, args...)`

```rust
fn example<T>()
where T: Foo + Foo2
{
<T as Foo>::bar();
<T as Foo2>::bar();
}
```

## Generic Implementations

You can add generics to a trait implementation by adding the generic list after the `impl` keyword:
Expand Down
Loading