Skip to content

Commit

Permalink
Implement 'as trait' syntax in expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
jfecher committed Feb 27, 2025
1 parent 79de085 commit 8e6b43f
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 14 deletions.
31 changes: 18 additions & 13 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use crate::{
DefinitionId, DefinitionKind, ExprId, FuncId, InternedStatementKind, StmtId, TraitMethodId,
},
token::{FmtStrFragment, Tokens},
DataType, Kind, QuotedType, Shared, Type, TypeBindings,
DataType, Kind, QuotedType, Shared, Type,
};

use super::{Elaborator, LambdaContext, UnsafeBlockStatus};
Expand Down Expand Up @@ -1329,14 +1329,25 @@ impl Elaborator<'_> {
},
};

let Some(constraint) = self.resolve_trait_constraint(&constraint) else {
// TODO: Error
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 { todo!() };
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 };
Expand All @@ -1349,17 +1360,11 @@ impl Elaborator<'_> {
impl_kind: ImplKind::TraitMethod(trait_method),
};

// TODO: AsTraitPath doesn't support generics on the impl item
let typ = self.interner.id_type_substitute_trait_as_type(definition_id);
let (typ, bindings) = self.instantiate(typ, TypeBindings::default(), None, 0, location);

let id = self.interner.push_expr(HirExpression::Ident(ident, None));
let id = self.interner.push_expr(HirExpression::Ident(ident.clone(), None));
self.interner.push_expr_location(id, location);
self.interner.push_expr_type(id, typ.clone());

self.interner.store_instantiation_bindings(id, bindings);

self.push_trait_constraint(constraint, id, true);
let typ = self.type_check_variable(ident, id, None);
self.interner.push_expr_type(id, typ.clone());
(id, typ)
}
}
16 changes: 15 additions & 1 deletion compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,11 +734,25 @@ impl<'context> Elaborator<'context> {

pub(crate) fn push_err(&mut self, error: impl Into<CompilationError>) {
let error: CompilationError = error.into();
if matches!(
&error,
CompilationError::TypeError(TypeCheckError::GenericCountMismatch { .. })
) {
panic!("oops!");
}
self.errors.push(error);
}

pub(crate) fn push_errors(&mut self, errors: impl IntoIterator<Item = CompilationError>) {
self.errors.extend(errors);
self.errors.extend(errors.into_iter().map(|e| {
if matches!(
&e,
CompilationError::TypeError(TypeCheckError::GenericCountMismatch { .. })
) {
panic!("oops!");
}
e
}));
}

fn run_lint(&mut self, lint: impl Fn(&Elaborator) -> Option<CompilationError>) {
Expand Down
10 changes: 10 additions & 0 deletions compiler/noirc_frontend/src/hir/resolution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,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 @@ -263,6 +265,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 @@ -824,6 +827,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,
)
},
}
}
}
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);
}

0 comments on commit 8e6b43f

Please sign in to comment.