From 742b8d0daad0ea53bce352e217da2932155c23bf Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 23 Dec 2024 16:13:46 -0300 Subject: [PATCH 1/8] feat!: type-check trait default methods --- compiler/noirc_frontend/src/ast/expression.rs | 8 +- compiler/noirc_frontend/src/elaborator/mod.rs | 6 + .../noirc_frontend/src/elaborator/patterns.rs | 2 +- .../noirc_frontend/src/elaborator/traits.rs | 37 +++--- .../noirc_frontend/src/elaborator/types.rs | 29 ++++- .../src/hir/def_collector/dc_mod.rs | 4 +- compiler/noirc_frontend/src/hir_def/expr.rs | 9 +- .../noirc_frontend/src/hir_def/function.rs | 4 +- compiler/noirc_frontend/src/node_interner.rs | 20 --- compiler/noirc_frontend/src/tests.rs | 3 +- compiler/noirc_frontend/src/tests/traits.rs | 67 +++++++++- .../trait_function_calls/src/main.nr | 117 ++++++++++++++---- 12 files changed, 233 insertions(+), 73 deletions(-) diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index ae622f46686..9d521545e7a 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -821,8 +821,8 @@ impl FunctionDefinition { is_unconstrained: bool, generics: &UnresolvedGenerics, parameters: &[(Ident, UnresolvedType)], - body: &BlockExpression, - where_clause: &[UnresolvedTraitConstraint], + body: BlockExpression, + where_clause: Vec, return_type: &FunctionReturnType, ) -> FunctionDefinition { let p = parameters @@ -843,9 +843,9 @@ impl FunctionDefinition { visibility: ItemVisibility::Private, generics: generics.clone(), parameters: p, - body: body.clone(), + body, span: name.span(), - where_clause: where_clause.to_vec(), + where_clause, return_type: return_type.clone(), return_visibility: Visibility::Private, } diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 593ea6b20e8..182db0c1164 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -328,6 +328,12 @@ impl<'context> Elaborator<'context> { self.elaborate_functions(functions); } + for (trait_id, unresolved_trait) in items.traits { + self.current_trait = Some(trait_id); + self.elaborate_functions(unresolved_trait.fns_with_default_impl); + } + self.current_trait = None; + for impls in items.impls.into_values() { self.elaborate_impls(impls); } diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 133473219f6..242f5f0b496 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -856,7 +856,7 @@ impl<'context> Elaborator<'context> { let impl_kind = match method { HirMethodReference::FuncId(_) => ImplKind::NotATraitMethod, - HirMethodReference::TraitMethodId(method_id, generics) => { + HirMethodReference::TraitMethodId(method_id, generics, _) => { let mut constraint = self.interner.get_trait(method_id.trait_id).as_constraint(span); constraint.trait_bound.trait_generics = generics; diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index e1be45927ca..f08c13834b2 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -28,6 +28,11 @@ impl<'context> Elaborator<'context> { self.recover_generics(|this| { this.current_trait = Some(*trait_id); + let the_trait = this.interner.get_trait(*trait_id); + let self_typevar = the_trait.self_type_typevar.clone(); + let self_type = Type::TypeVariable(self_typevar.clone()); + this.self_type = Some(self_type.clone()); + let resolved_generics = this.interner.get_trait(*trait_id).generics.clone(); this.add_existing_generics( &unresolved_trait.trait_def.generics, @@ -48,12 +53,15 @@ impl<'context> Elaborator<'context> { .add_trait_dependency(DependencyId::Trait(bound.trait_id), *trait_id); } + this.interner.update_trait(*trait_id, |trait_def| { + trait_def.set_trait_bounds(resolved_trait_bounds); + trait_def.set_where_clause(where_clause); + }); + let methods = this.resolve_trait_methods(*trait_id, unresolved_trait); this.interner.update_trait(*trait_id, |trait_def| { trait_def.set_methods(methods); - trait_def.set_trait_bounds(resolved_trait_bounds); - trait_def.set_where_clause(where_clause); }); }); @@ -94,7 +102,7 @@ impl<'context> Elaborator<'context> { parameters, return_type, where_clause, - body: _, + body, is_unconstrained, visibility: _, is_comptime: _, @@ -103,7 +111,6 @@ impl<'context> Elaborator<'context> { self.recover_generics(|this| { let the_trait = this.interner.get_trait(trait_id); let self_typevar = the_trait.self_type_typevar.clone(); - let self_type = Type::TypeVariable(self_typevar.clone()); let name_span = the_trait.name.span(); this.add_existing_generic( @@ -115,9 +122,12 @@ impl<'context> Elaborator<'context> { span: name_span, }, ); - this.self_type = Some(self_type.clone()); let func_id = unresolved_trait.method_ids[&name.0.contents]; + let mut where_clause = where_clause.to_vec(); + + // Attach any trait constraints on the trait to the function + where_clause.extend(unresolved_trait.trait_def.where_clause.clone()); this.resolve_trait_function( trait_id, @@ -127,6 +137,7 @@ impl<'context> Elaborator<'context> { parameters, return_type, where_clause, + body, func_id, ); @@ -188,12 +199,14 @@ impl<'context> Elaborator<'context> { generics: &UnresolvedGenerics, parameters: &[(Ident, UnresolvedType)], return_type: &FunctionReturnType, - where_clause: &[UnresolvedTraitConstraint], + where_clause: Vec, + body: &Option, func_id: FuncId, ) { - let old_generic_count = self.generics.len(); - - self.scopes.start_function(); + let body = match body { + Some(body) => body.clone(), + None => BlockExpression { statements: Vec::new() }, + }; let kind = FunctionKind::Normal; let mut def = FunctionDefinition::normal( @@ -201,7 +214,7 @@ impl<'context> Elaborator<'context> { is_unconstrained, generics, parameters, - &BlockExpression { statements: Vec::new() }, + body, where_clause, return_type, ); @@ -210,10 +223,6 @@ impl<'context> Elaborator<'context> { let mut function = NoirFunction { kind, def }; self.define_function_meta(&mut function, func_id, Some(trait_id)); - self.elaborate_function(func_id); - let _ = self.scopes.end_function(); - // Don't check the scope tree for unused variables, they can't be used in a declaration anyway. - self.generics.truncate(old_generic_count); } } diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 550ee41fbd4..c3375fc35ad 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -566,12 +566,17 @@ impl<'context> Elaborator<'context> { } // this resolves Self::some_static_method, inside an impl block (where we don't have a concrete self_type) + // or inside a trait default method. // // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` fn resolve_trait_static_method_by_self(&mut self, path: &Path) -> Option { - let trait_impl = self.current_trait_impl?; - let trait_id = self.interner.try_get_trait_implementation(trait_impl)?.borrow().trait_id; + let trait_id = if let Some(current_trait) = self.current_trait { + current_trait + } else { + let trait_impl = self.current_trait_impl?; + self.interner.try_get_trait_implementation(trait_impl)?.borrow().trait_id + }; if path.kind == PathKind::Plain && path.segments.len() == 2 { let name = &path.segments[0].ident.0.contents; @@ -1395,6 +1400,25 @@ impl<'context> Elaborator<'context> { }; let func_meta = self.interner.function_meta(&func_id); + // If inside a trait method, check if it's a method on `self` + if let Some(trait_id) = func_meta.trait_id { + if Some(object_type) == self.self_type.as_ref() { + let the_trait = self.interner.get_trait(trait_id); + let constraint = the_trait.as_constraint(the_trait.name.span()); + if let Some(HirMethodReference::TraitMethodId(method_id, generics, _)) = self + .lookup_method_in_trait( + the_trait, + method_name, + &constraint.trait_bound, + the_trait.id, + ) + { + // If it is, it's an assumed trait + return Some(HirMethodReference::TraitMethodId(method_id, generics, true)); + } + } + } + for constraint in &func_meta.trait_constraints { if *object_type == constraint.typ { if let Some(the_trait) = @@ -1432,6 +1456,7 @@ impl<'context> Elaborator<'context> { return Some(HirMethodReference::TraitMethodId( trait_method, trait_bound.trait_generics.clone(), + false, )); } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index e7953aab5a4..df1200d6255 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -518,8 +518,8 @@ impl<'a> ModCollector<'a> { *is_unconstrained, generics, parameters, - body, - where_clause, + body.clone(), + where_clause.clone(), return_type, )); unresolved_functions.push_fn( diff --git a/compiler/noirc_frontend/src/hir_def/expr.rs b/compiler/noirc_frontend/src/hir_def/expr.rs index e243fc88cff..9b3bf4962bb 100644 --- a/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/compiler/noirc_frontend/src/hir_def/expr.rs @@ -209,14 +209,14 @@ pub enum HirMethodReference { /// Or a method can come from a Trait impl block, in which case /// the actual function called will depend on the instantiated type, /// which can be only known during monomorphization. - TraitMethodId(TraitMethodId, TraitGenerics), + TraitMethodId(TraitMethodId, TraitGenerics, bool /* assumed */), } impl HirMethodReference { pub fn func_id(&self, interner: &NodeInterner) -> Option { match self { HirMethodReference::FuncId(func_id) => Some(*func_id), - HirMethodReference::TraitMethodId(method_id, _) => { + HirMethodReference::TraitMethodId(method_id, _, _) => { let id = interner.trait_method_id(*method_id); match &interner.try_definition(id)?.kind { DefinitionKind::Function(func_id) => Some(*func_id), @@ -246,7 +246,7 @@ impl HirMethodCallExpression { HirMethodReference::FuncId(func_id) => { (interner.function_definition_id(func_id), ImplKind::NotATraitMethod) } - HirMethodReference::TraitMethodId(method_id, trait_generics) => { + HirMethodReference::TraitMethodId(method_id, trait_generics, assumed) => { let id = interner.trait_method_id(method_id); let constraint = TraitConstraint { typ: object_type, @@ -256,7 +256,8 @@ impl HirMethodCallExpression { span: location.span, }, }; - (id, ImplKind::TraitMethod(TraitMethod { method_id, constraint, assumed: false })) + + (id, ImplKind::TraitMethod(TraitMethod { method_id, constraint, assumed })) } }; let func_var = HirIdent { location, id, impl_kind }; diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index db6c3507b15..f6ee003b179 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -175,12 +175,12 @@ pub enum FunctionBody { impl FuncMeta { /// A stub function does not have a body. This includes Builtin, LowLevel, - /// and Oracle functions in addition to method declarations within a trait. + /// and Oracle functions. /// /// We don't check the return type of these functions since it will always have /// an empty body, and we don't check for unused parameters. pub fn is_stub(&self) -> bool { - self.kind.can_ignore_return_type() || self.trait_id.is_some() + self.kind.can_ignore_return_type() } pub fn function_signature(&self) -> FunctionSignature { diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 1df2cd76721..3ed183df49c 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use std::fmt; use std::hash::Hash; use std::marker::Copy; -use std::ops::Deref; use fm::FileId; use iter_extended::vecmap; @@ -1478,25 +1477,6 @@ impl NodeInterner { Ok(impl_kind) } - /// Given a `ObjectType: TraitId` pair, find all implementations without taking constraints into account or - /// applying any type bindings. Useful to look for a specific trait in a type that is used in a macro. - pub fn lookup_all_trait_implementations( - &self, - object_type: &Type, - trait_id: TraitId, - ) -> Vec<&TraitImplKind> { - let trait_impl = self.trait_implementation_map.get(&trait_id); - - let trait_impl = trait_impl.map(|trait_impl| { - let impls = trait_impl.iter().filter_map(|(typ, impl_kind)| match &typ { - Type::Forall(_, typ) => (typ.deref() == object_type).then_some(impl_kind), - _ => None, - }); - impls.collect() - }); - trait_impl.unwrap_or_default() - } - /// Similar to `lookup_trait_implementation` but does not apply any type bindings on success. /// On error returns either: /// - 1+ failing trait constraints, including the original. diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 3d908d1aa0c..589b3128eb0 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -2942,7 +2942,7 @@ fn uses_self_type_inside_trait() { fn uses_self_type_in_trait_where_clause() { let src = r#" pub trait Trait { - fn trait_func() -> bool; + fn trait_func(self) -> bool; } pub trait Foo where Self: Trait { @@ -2963,6 +2963,7 @@ fn uses_self_type_in_trait_where_clause() { "#; let errors = get_program_errors(src); + dbg!(&errors); assert_eq!(errors.len(), 2); let CompilationError::ResolverError(ResolverError::TraitNotImplemented { .. }) = &errors[0].0 diff --git a/compiler/noirc_frontend/src/tests/traits.rs b/compiler/noirc_frontend/src/tests/traits.rs index 811a32bab86..b641f726e47 100644 --- a/compiler/noirc_frontend/src/tests/traits.rs +++ b/compiler/noirc_frontend/src/tests/traits.rs @@ -592,7 +592,7 @@ fn trait_bounds_which_are_dependent_on_generic_types_are_resolved_correctly() { // Regression test for https://github.com/noir-lang/noir/issues/6420 let src = r#" trait Foo { - fn foo() -> Field; + fn foo(self) -> Field; } trait Bar: Foo { @@ -613,7 +613,8 @@ fn trait_bounds_which_are_dependent_on_generic_types_are_resolved_correctly() { where T: MarkerTrait, { - fn foo() -> Field { + fn foo(self) -> Field { + let _ = self; 42 } } @@ -652,3 +653,65 @@ fn does_not_crash_on_as_trait_path_with_empty_path() { ); assert!(!errors.is_empty()); } + +#[test] +fn type_checks_trait_default_method_and_errors() { + let src = r#" + pub trait Foo { + fn foo(self) -> i32 { + let _ = self; + true + } + } + + fn main() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::TypeMismatchWithSource { + expected, + actual, + .. + }) = &errors[0].0 + else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; + + assert_eq!(expected.to_string(), "i32"); + assert_eq!(actual.to_string(), "bool"); +} + +#[test] +fn type_checks_trait_default_method_and_does_not_error() { + let src = r#" + pub trait Foo { + fn foo(self) -> i32 { + let _ = self; + 1 + } + } + + fn main() {} + "#; + assert_no_errors(src); +} + +#[test] +fn type_checks_trait_default_method_and_does_not_error_using_self() { + let src = r#" + pub trait Foo { + fn foo(self) -> i32 { + self.bar() + } + + fn bar(self) -> i32 { + let _ = self; + 1 + } + } + + fn main() {} + "#; + assert_no_errors(src); +} diff --git a/test_programs/compile_success_empty/trait_function_calls/src/main.nr b/test_programs/compile_success_empty/trait_function_calls/src/main.nr index f9a338bfa47..6c35ee97222 100644 --- a/test_programs/compile_success_empty/trait_function_calls/src/main.nr +++ b/test_programs/compile_success_empty/trait_function_calls/src/main.nr @@ -23,25 +23,31 @@ // 1a) trait default method -> trait default method trait Trait1a { fn trait_method1(self) -> Field { - self.trait_method2() * 7892 - self.vl + self.trait_method2() * 7892 - self.vl() } fn trait_method2(self) -> Field { let _ = self; 43278 } + fn vl(self) -> Field; } struct Struct1a { vl: Field, } -impl Trait1a for Struct1a {} +impl Trait1a for Struct1a { + fn vl(self) -> Field { + self.vl + } +} // 1b) trait default method -> trait overriden method trait Trait1b { fn trait_method1(self) -> Field { - self.trait_method2() * 2832 - self.vl + self.trait_method2() * 2832 - self.vl() } fn trait_method2(self) -> Field { 9323 } + fn vl(self) -> Field; } struct Struct1b { vl: Field, @@ -51,13 +57,17 @@ impl Trait1b for Struct1b { let _ = self; 2394 } + fn vl(self) -> Field { + self.vl + } } // 1c) trait default method -> trait overriden (no default) method trait Trait1c { fn trait_method1(self) -> Field { - self.trait_method2() * 7635 - self.vl + self.trait_method2() * 7635 - self.vl() } fn trait_method2(self) -> Field; + fn vl(self) -> Field; } struct Struct1c { vl: Field, @@ -67,16 +77,20 @@ impl Trait1c for Struct1c { let _ = self; 5485 } + fn vl(self) -> Field { + self.vl + } } // 1d) trait overriden method -> trait default method trait Trait1d { fn trait_method1(self) -> Field { - self.trait_method2() * 2825 - self.vl + self.trait_method2() * 2825 - self.vl() } fn trait_method2(self) -> Field { let _ = self; 29341 } + fn vl(self) -> Field; } struct Struct1d { vl: Field, @@ -85,15 +99,19 @@ impl Trait1d for Struct1d { fn trait_method1(self) -> Field { self.trait_method2() * 9342 - self.vl } + fn vl(self) -> Field { + self.vl + } } // 1e) trait overriden method -> trait overriden method trait Trait1e { fn trait_method1(self) -> Field { - self.trait_method2() * 85465 - self.vl + self.trait_method2() * 85465 - self.vl() } fn trait_method2(self) -> Field { 2381 } + fn vl(self) -> Field; } struct Struct1e { vl: Field, @@ -106,13 +124,17 @@ impl Trait1e for Struct1e { let _ = self; 58945 } + fn vl(self) -> Field { + self.vl + } } // 1f) trait overriden method -> trait overriden (no default) method trait Trait1f { fn trait_method1(self) -> Field { - self.trait_method2() * 43257 - self.vl + self.trait_method2() * 43257 - self.vl() } fn trait_method2(self) -> Field; + fn vl(self) -> Field; } struct Struct1f { vl: Field, @@ -125,6 +147,9 @@ impl Trait1f for Struct1f { let _ = self; 5748 } + fn vl(self) -> Field { + self.vl + } } // 1g) trait overriden (no default) method -> trait default method trait Trait1g { @@ -182,24 +207,30 @@ impl Trait1i for Struct1i { // 2a) trait default method -> trait default function trait Trait2a { fn trait_method1(self) -> Field { - Self::trait_function2() * 2385 - self.vl + Self::trait_function2() * 2385 - self.vl() } fn trait_function2() -> Field { 7843 } + fn vl(self) -> Field; } struct Struct2a { vl: Field, } -impl Trait2a for Struct2a {} +impl Trait2a for Struct2a { + fn vl(self) -> Field { + self.vl + } +} // 2b) trait default method -> trait overriden function trait Trait2b { fn trait_method1(self) -> Field { - Self::trait_function2() * 6583 - self.vl + Self::trait_function2() * 6583 - self.vl() } fn trait_function2() -> Field { 3752 } + fn vl(self) -> Field; } struct Struct2b { vl: Field, @@ -208,13 +239,17 @@ impl Trait2b for Struct2b { fn trait_function2() -> Field { 8477 } + fn vl(self) -> Field { + self.vl + } } // 2c) trait default method -> trait overriden (no default) function trait Trait2c { fn trait_method1(self) -> Field { - Self::trait_function2() * 2831 - self.vl + Self::trait_function2() * 2831 - self.vl() } fn trait_function2() -> Field; + fn vl(self) -> Field; } struct Struct2c { vl: Field, @@ -223,15 +258,19 @@ impl Trait2c for Struct2c { fn trait_function2() -> Field { 8342 } + fn vl(self) -> Field { + self.vl + } } // 2d) trait overriden method -> trait default function trait Trait2d { fn trait_method1(self) -> Field { - Self::trait_function2() * 924 - self.vl + Self::trait_function2() * 924 - self.vl() } fn trait_function2() -> Field { 384 } + fn vl(self) -> Field; } struct Struct2d { vl: Field, @@ -240,15 +279,19 @@ impl Trait2d for Struct2d { fn trait_method1(self) -> Field { Self::trait_function2() * 3984 - self.vl } + fn vl(self) -> Field { + self.vl + } } // 2e) trait overriden method -> trait overriden function trait Trait2e { fn trait_method1(self) -> Field { - Self::trait_function2() * 3642 - self.vl + Self::trait_function2() * 3642 - self.vl() } fn trait_function2() -> Field { 97342 } + fn vl(self) -> Field; } struct Struct2e { vl: Field, @@ -260,13 +303,17 @@ impl Trait2e for Struct2e { fn trait_function2() -> Field { 39400 } + fn vl(self) -> Field { + self.vl + } } // 2f) trait overriden method -> trait overriden (no default) function trait Trait2f { fn trait_method1(self) -> Field { - Self::trait_function2() * 2783 - self.vl + Self::trait_function2() * 2783 - self.vl() } fn trait_function2() -> Field; + fn vl(self) -> Field; } struct Struct2f { vl: Field, @@ -278,6 +325,9 @@ impl Trait2f for Struct2f { fn trait_function2() -> Field { 72311 } + fn vl(self) -> Field { + self.vl + } } // 2g) trait overriden (no default) method -> trait default function trait Trait2g { @@ -332,25 +382,31 @@ impl Trait2i for Struct2i { // 3a) trait default function -> trait default method trait Trait3a { fn trait_function1(a: Field, b: Self) -> Field { - b.trait_method2() * 8344 - b.vl + a + b.trait_method2() * 8344 - b.vl() + a } fn trait_method2(self) -> Field { let _ = self; 19212 } + fn vl(self) -> Field; } struct Struct3a { vl: Field, } -impl Trait3a for Struct3a {} +impl Trait3a for Struct3a { + fn vl(self) -> Field { + self.vl + } +} // 3b) trait default function -> trait overriden method trait Trait3b { fn trait_function1(a: Field, b: Self) -> Field { - b.trait_method2() * 9233 - b.vl + a + b.trait_method2() * 9233 - b.vl() + a } fn trait_method2(self) -> Field { 9111 } + fn vl(self) -> Field; } struct Struct3b { vl: Field, @@ -360,13 +416,17 @@ impl Trait3b for Struct3b { let _ = self; 2392 } + fn vl(self) -> Field { + self.vl + } } // 3c) trait default function -> trait overriden (no default) method trait Trait3c { fn trait_function1(a: Field, b: Self) -> Field { - b.trait_method2() * 2822 - b.vl + a + b.trait_method2() * 2822 - b.vl() + a } fn trait_method2(self) -> Field; + fn vl(self) -> Field; } struct Struct3c { vl: Field, @@ -376,16 +436,20 @@ impl Trait3c for Struct3c { let _ = self; 7743 } + fn vl(self) -> Field { + self.vl + } } // 3d) trait overriden function -> trait default method trait Trait3d { fn trait_function1(a: Field, b: Self) -> Field { - b.trait_method2() * 291 - b.vl + a + b.trait_method2() * 291 - b.vl() + a } fn trait_method2(self) -> Field { let _ = self; 3328 } + fn vl(self) -> Field; } struct Struct3d { vl: Field, @@ -394,15 +458,19 @@ impl Trait3d for Struct3d { fn trait_function1(a: Field, b: Self) -> Field { b.trait_method2() * 4933 - b.vl + a } + fn vl(self) -> Field { + self.vl + } } // 3e) trait overriden function -> trait overriden method trait Trait3e { fn trait_function1(a: Field, b: Self) -> Field { - b.trait_method2() * 71231 - b.vl + a + b.trait_method2() * 71231 - b.vl() + a } fn trait_method2(self) -> Field { 373 } + fn vl(self) -> Field; } struct Struct3e { vl: Field, @@ -415,13 +483,17 @@ impl Trait3e for Struct3e { let _ = self; 80002 } + fn vl(self) -> Field { + self.vl + } } // 3f) trait overriden function -> trait overriden (no default) method trait Trait3f { fn trait_function1(a: Field, b: Self) -> Field { - b.trait_method2() * 28223 - b.vl + a + b.trait_method2() * 28223 - b.vl() + a } fn trait_method2(self) -> Field; + fn vl(self) -> Field; } struct Struct3f { vl: Field, @@ -434,6 +506,9 @@ impl Trait3f for Struct3f { let _ = self; 63532 } + fn vl(self) -> Field { + self.vl + } } // 3g) trait overriden (no default) function -> trait default method trait Trait3g { From 35578ef5ca541f0d995744bab35de1d238deae09 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 23 Dec 2024 16:16:30 -0300 Subject: [PATCH 2/8] Remove some dbg left-overs --- compiler/noirc_frontend/src/parser/parser/module.rs | 2 -- compiler/noirc_frontend/src/tests.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/compiler/noirc_frontend/src/parser/parser/module.rs b/compiler/noirc_frontend/src/parser/parser/module.rs index 263338863c0..da733168099 100644 --- a/compiler/noirc_frontend/src/parser/parser/module.rs +++ b/compiler/noirc_frontend/src/parser/parser/module.rs @@ -74,7 +74,6 @@ mod tests { fn parse_submodule() { let src = "mod foo { mod bar; }"; let (module, errors) = parse_program(src); - dbg!(&errors); expect_no_errors(&errors); assert_eq!(module.items.len(), 1); let item = &module.items[0]; @@ -90,7 +89,6 @@ mod tests { fn parse_contract() { let src = "contract foo {}"; let (module, errors) = parse_program(src); - dbg!(&errors); expect_no_errors(&errors); assert_eq!(module.items.len(), 1); let item = &module.items[0]; diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 589b3128eb0..88f35cb27a9 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -2963,7 +2963,6 @@ fn uses_self_type_in_trait_where_clause() { "#; let errors = get_program_errors(src); - dbg!(&errors); assert_eq!(errors.len(), 2); let CompilationError::ResolverError(ResolverError::TraitNotImplemented { .. }) = &errors[0].0 From 1695b35a76b7d5375464d60972ba21e4131c76d4 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 23 Dec 2024 17:11:19 -0300 Subject: [PATCH 3/8] Elaborate trait functions without a body --- compiler/noirc_frontend/src/ast/function.rs | 9 +++++++-- compiler/noirc_frontend/src/elaborator/mod.rs | 7 ++++--- .../noirc_frontend/src/elaborator/traits.rs | 20 +++++++++++++++++-- .../src/hir/def_collector/dc_mod.rs | 4 +++- .../src/monomorphization/mod.rs | 2 +- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/compiler/noirc_frontend/src/ast/function.rs b/compiler/noirc_frontend/src/ast/function.rs index 99ae78c93ea..8957564e0d6 100644 --- a/compiler/noirc_frontend/src/ast/function.rs +++ b/compiler/noirc_frontend/src/ast/function.rs @@ -19,22 +19,27 @@ pub struct NoirFunction { pub def: FunctionDefinition, } -/// Currently, we support three types of functions: +/// Currently, we support four types of functions: /// - Normal functions /// - LowLevel/Foreign which link to an OPCODE in ACIR /// - BuiltIn which are provided by the runtime +/// - TraitFunctionWithoutBody for which we don't type-check their body #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FunctionKind { LowLevel, Builtin, Normal, Oracle, + TraitFunctionWithoutBody, } impl FunctionKind { pub fn can_ignore_return_type(self) -> bool { match self { - FunctionKind::LowLevel | FunctionKind::Builtin | FunctionKind::Oracle => true, + FunctionKind::LowLevel + | FunctionKind::Builtin + | FunctionKind::Oracle + | FunctionKind::TraitFunctionWithoutBody => true, FunctionKind::Normal => false, } } diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 182db0c1164..07ce9539ce5 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -456,9 +456,10 @@ impl<'context> Elaborator<'context> { self.add_trait_constraints_to_scope(&func_meta); let (hir_func, body_type) = match kind { - FunctionKind::Builtin | FunctionKind::LowLevel | FunctionKind::Oracle => { - (HirFunction::empty(), Type::Error) - } + FunctionKind::Builtin + | FunctionKind::LowLevel + | FunctionKind::Oracle + | FunctionKind::TraitFunctionWithoutBody => (HirFunction::empty(), Type::Error), FunctionKind::Normal => { let (block, body_type) = self.elaborate_block(body); let expr_id = self.intern_expr(block, body_span); diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index f08c13834b2..a2e683e7d18 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -203,12 +203,18 @@ impl<'context> Elaborator<'context> { body: &Option, func_id: FuncId, ) { + let old_generic_count = self.generics.len(); + + self.scopes.start_function(); + + let has_body = body.is_some(); + let body = match body { Some(body) => body.clone(), None => BlockExpression { statements: Vec::new() }, }; - - let kind = FunctionKind::Normal; + let kind = + if has_body { FunctionKind::Normal } else { FunctionKind::TraitFunctionWithoutBody }; let mut def = FunctionDefinition::normal( name, is_unconstrained, @@ -223,6 +229,16 @@ impl<'context> Elaborator<'context> { let mut function = NoirFunction { kind, def }; self.define_function_meta(&mut function, func_id, Some(trait_id)); + + // Here we elaborate functions without a body, mainly to check the arguments and return types. + // Later on we'll elaborate functions with a body by fully type-checking them. + if !has_body { + self.elaborate_function(func_id); + } + + let _ = self.scopes.end_function(); + // Don't check the scope tree for unused variables, they can't be used in a declaration anyway. + self.generics.truncate(old_generic_count); } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index df1200d6255..9391b9d6122 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -482,7 +482,9 @@ impl<'a> ModCollector<'a> { is_comptime, } => { let func_id = context.def_interner.push_empty_fn(); - method_ids.insert(name.to_string(), func_id); + if !method_ids.contains_key(&name.0.contents) { + method_ids.insert(name.to_string(), func_id); + } let location = Location::new(name.span(), self.file_id); let modifiers = FunctionModifiers { diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 8c07d71de21..99dbfd94d1f 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -235,7 +235,7 @@ impl<'interner> Monomorphizer<'interner> { ); Definition::Builtin(opcode.to_string()) } - FunctionKind::Normal => { + FunctionKind::Normal | FunctionKind::TraitFunctionWithoutBody => { let id = self.queue_function(id, expr_id, typ, turbofish_generics, trait_method); Definition::Function(id) From f6e2d0fc25001e642da286a1a828766aa78a2065 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 23 Dec 2024 17:11:26 -0300 Subject: [PATCH 4/8] Remove warnings in test program --- .../compile_success_empty/trait_function_calls/src/main.nr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test_programs/compile_success_empty/trait_function_calls/src/main.nr b/test_programs/compile_success_empty/trait_function_calls/src/main.nr index 6c35ee97222..bdc4a3d8c90 100644 --- a/test_programs/compile_success_empty/trait_function_calls/src/main.nr +++ b/test_programs/compile_success_empty/trait_function_calls/src/main.nr @@ -45,6 +45,7 @@ trait Trait1b { self.trait_method2() * 2832 - self.vl() } fn trait_method2(self) -> Field { + let _ = self; 9323 } fn vl(self) -> Field; @@ -109,6 +110,7 @@ trait Trait1e { self.trait_method2() * 85465 - self.vl() } fn trait_method2(self) -> Field { + let _ = self; 2381 } fn vl(self) -> Field; @@ -171,6 +173,7 @@ impl Trait1g for Struct1g { trait Trait1h { fn trait_method1(self) -> Field; fn trait_method2(self) -> Field { + let _ = self; 7823 } } @@ -404,6 +407,7 @@ trait Trait3b { b.trait_method2() * 9233 - b.vl() + a } fn trait_method2(self) -> Field { + let _ = self; 9111 } fn vl(self) -> Field; @@ -468,6 +472,7 @@ trait Trait3e { b.trait_method2() * 71231 - b.vl() + a } fn trait_method2(self) -> Field { + let _ = self; 373 } fn vl(self) -> Field; @@ -530,6 +535,7 @@ impl Trait3g for Struct3g { trait Trait3h { fn trait_function1(a: Field, b: Self) -> Field; fn trait_method2(self) -> Field { + let _ = self; 293 } } From eb304cd9274270b6cbb0d7d72a982c3a452960d8 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 23 Dec 2024 17:14:04 -0300 Subject: [PATCH 5/8] Put back comment --- compiler/noirc_frontend/src/hir_def/function.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index f6ee003b179..aa04738733f 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -175,7 +175,8 @@ pub enum FunctionBody { impl FuncMeta { /// A stub function does not have a body. This includes Builtin, LowLevel, - /// and Oracle functions. + /// and Oracle functions in addition to method declarations within a trait + /// without a body. /// /// We don't check the return type of these functions since it will always have /// an empty body, and we don't check for unused parameters. From 0d52a7277ba3b0faad28a3b64b2e73e650cc0618 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 2 Jan 2025 10:44:05 -0300 Subject: [PATCH 6/8] Add trait assumptions for `self` inside trait definition --- compiler/noirc_frontend/src/elaborator/mod.rs | 19 +++++++++++++++++++ .../trait_default_method_uses_op/Nargo.toml | 7 +++++++ .../trait_default_method_uses_op/src/main.nr | 8 ++++++++ 3 files changed, 34 insertions(+) create mode 100644 test_programs/compile_success_empty/trait_default_method_uses_op/Nargo.toml create mode 100644 test_programs/compile_success_empty/trait_default_method_uses_op/src/main.nr diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 07ce9539ce5..dcc55ddb809 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -485,6 +485,11 @@ impl<'context> Elaborator<'context> { .remove_assumed_trait_implementations_for_trait(constraint.trait_bound.trait_id); } + // Also remove the assumed trait implementation for `self` if this is a trait definition + if let Some(trait_id) = self.current_trait { + self.interner.remove_assumed_trait_implementations_for_trait(trait_id); + } + let func_scope_tree = self.scopes.end_function(); // The arguments to low-level and oracle functions are always unused so we do not produce warnings for them. @@ -1004,6 +1009,20 @@ impl<'context> Elaborator<'context> { constraint.trait_bound.trait_id, ); } + + // Also assume `self` implements the current trait if we are inside a trait definition + if let Some(trait_id) = self.current_trait { + let the_trait = self.interner.get_trait(trait_id); + let constraint = the_trait.as_constraint(the_trait.name.span()); + let self_type = + self.self_type.clone().expect("Expected a self type if there's a current trait"); + self.add_trait_bound_to_scope( + func_meta, + &self_type, + &constraint.trait_bound, + constraint.trait_bound.trait_id, + ); + } } fn add_trait_bound_to_scope( diff --git a/test_programs/compile_success_empty/trait_default_method_uses_op/Nargo.toml b/test_programs/compile_success_empty/trait_default_method_uses_op/Nargo.toml new file mode 100644 index 00000000000..d8b882c1696 --- /dev/null +++ b/test_programs/compile_success_empty/trait_default_method_uses_op/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "trait_default_method_uses_op" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/trait_default_method_uses_op/src/main.nr b/test_programs/compile_success_empty/trait_default_method_uses_op/src/main.nr new file mode 100644 index 00000000000..6aa93de5fdf --- /dev/null +++ b/test_programs/compile_success_empty/trait_default_method_uses_op/src/main.nr @@ -0,0 +1,8 @@ +pub trait Foo: Eq { + fn foo(self) -> bool + { + self == self + } +} + +fn main() {} From 2325cdc443f7efcd8576e3beacce1720c101392d Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 6 Jan 2025 14:11:15 -0300 Subject: [PATCH 7/8] nargo fmt --- .../trait_default_method_uses_op/src/main.nr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test_programs/compile_success_empty/trait_default_method_uses_op/src/main.nr b/test_programs/compile_success_empty/trait_default_method_uses_op/src/main.nr index 6aa93de5fdf..415f36fe207 100644 --- a/test_programs/compile_success_empty/trait_default_method_uses_op/src/main.nr +++ b/test_programs/compile_success_empty/trait_default_method_uses_op/src/main.nr @@ -1,6 +1,5 @@ pub trait Foo: Eq { - fn foo(self) -> bool - { + fn foo(self) -> bool { self == self } } From 6d30e06623800975820224f1bffe0d774ede78f6 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 7 Jan 2025 07:40:14 -0300 Subject: [PATCH 8/8] Extract `remove_trait_constraints_from_scope` --- compiler/noirc_frontend/src/elaborator/mod.rs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 43b498b8a44..9f9fdbc3fde 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -483,16 +483,7 @@ impl<'context> Elaborator<'context> { // when multiple impls are available. Instead we default first to choose the Field or u64 impl. self.check_and_pop_function_context(); - // Now remove all the `where` clause constraints we added - for constraint in &func_meta.trait_constraints { - self.interner - .remove_assumed_trait_implementations_for_trait(constraint.trait_bound.trait_id); - } - - // Also remove the assumed trait implementation for `self` if this is a trait definition - if let Some(trait_id) = self.current_trait { - self.interner.remove_assumed_trait_implementations_for_trait(trait_id); - } + self.remove_trait_constraints_from_scope(&func_meta); let func_scope_tree = self.scopes.end_function(); @@ -1029,6 +1020,18 @@ impl<'context> Elaborator<'context> { } } + fn remove_trait_constraints_from_scope(&mut self, func_meta: &FuncMeta) { + for constraint in &func_meta.trait_constraints { + self.interner + .remove_assumed_trait_implementations_for_trait(constraint.trait_bound.trait_id); + } + + // Also remove the assumed trait implementation for `self` if this is a trait definition + if let Some(trait_id) = self.current_trait { + self.interner.remove_assumed_trait_implementations_for_trait(trait_id); + } + } + fn add_trait_bound_to_scope( &mut self, func_meta: &FuncMeta,