diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 99f9c4ec8f7..f16ace4bad1 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -9,7 +9,7 @@ use crate::{ ast::{IdentOrQuotedType, ItemVisibility, UnresolvedType}, graph::CrateGraph, hir::def_collector::dc_crate::UnresolvedTrait, - hir_def::traits::ResolvedTraitBound, + hir_def::traits::{NamedType, ResolvedTraitBound}, node_interner::{GlobalValue, QuotedTypeId}, token::SecondaryAttributeKind, usage_tracker::UsageTracker, @@ -1392,22 +1392,37 @@ impl<'context> Elaborator<'context> { else { continue; }; + let trait_constraint_trait_name = trait_constraint_trait.name.to_string(); let trait_constraint_type = trait_constraint.typ.substitute(&bindings); let trait_bound = &trait_constraint.trait_bound; + let mut named_generics = trait_bound.trait_generics.named.clone(); + + // If the trait bound is over a trait that has associated types, the ones that + // aren't explicit will be in `named_generics` as implicitly added ones. + // If they are unbound, they won't be bound until monomorphization, in which cae + // the below trait implementation lookup will fail (an unbound named generic will + // never unify in this case). In this case we replace them with fresh type variables + // so they'll unify (the bindings aren't applied here so this is fine). + // If they are bound though, we won't replace them as we want to ensure the binding + // matches. + self.replace_implicitly_added_unbound_named_generics_with_fresh_type_variables( + &mut named_generics, + ); + if self .interner .try_lookup_trait_implementation( &trait_constraint_type, trait_bound.trait_id, &trait_bound.trait_generics.ordered, - &trait_bound.trait_generics.named, + &named_generics, ) .is_err() { let missing_trait = - format!("{}{}", trait_constraint_trait.name, trait_bound.trait_generics); + format!("{}{}", trait_constraint_trait_name, trait_bound.trait_generics); self.push_err(ResolverError::TraitNotImplemented { impl_trait: impl_trait.clone(), missing_trait, @@ -1419,6 +1434,22 @@ impl<'context> Elaborator<'context> { } } + fn replace_implicitly_added_unbound_named_generics_with_fresh_type_variables( + &mut self, + named_generics: &mut [NamedType], + ) { + for named_type in named_generics.iter_mut() { + match &named_type.typ { + Type::NamedGeneric(NamedGeneric { type_var, implicit: true, .. }) + if type_var.borrow().is_unbound() => + { + named_type.typ = self.interner.next_type_variable(); + } + _ => (), + }; + } + } + fn check_parent_traits_are_implemented(&mut self, trait_impl: &UnresolvedTraitImpl) { let Some(trait_id) = trait_impl.trait_id else { return; diff --git a/compiler/noirc_frontend/src/tests/traits.rs b/compiler/noirc_frontend/src/tests/traits.rs index 46b9d1bba79..11f1b03fd2f 100644 --- a/compiler/noirc_frontend/src/tests/traits.rs +++ b/compiler/noirc_frontend/src/tests/traits.rs @@ -2022,6 +2022,30 @@ fn associated_constant_mul_of_other_constants() { assert_no_errors!(src); } +#[named] +#[test] +fn trait_bound_with_associated_constant() { + let src = r#" + pub trait Other { + let N: u32; + } + + pub trait Trait + where + T: Other, + {} + + impl Other for Field { + let N: u32 = 1; + } + + impl Trait for i32 {} + + fn main() {} + "#; + assert_no_errors!(src); +} + #[named] #[test] fn trait_method_call_when_it_has_bounds_on_generic() { diff --git a/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/Nargo.toml b/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/Nargo.toml new file mode 100644 index 00000000000..4f86c0a1934 --- /dev/null +++ b/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/Nargo.toml @@ -0,0 +1,7 @@ + + [package] + name = "noirc_frontend_tests_traits_trait_bound_with_associated_constant" + type = "bin" + authors = [""] + + [dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/src/main.nr b/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/src/main.nr new file mode 100644 index 00000000000..4de7ff43329 --- /dev/null +++ b/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/src/main.nr @@ -0,0 +1,18 @@ + + pub trait Other { + let N: u32; + } + + pub trait Trait + where + T: Other, + {} + + impl Other for Field { + let N: u32 = 1; + } + + impl Trait for i32 {} + + fn main() {} + \ No newline at end of file diff --git a/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/src_hash.txt b/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/src_hash.txt new file mode 100644 index 00000000000..c6cb66299c4 --- /dev/null +++ b/test_programs/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/src_hash.txt @@ -0,0 +1 @@ +1955914972083164529 \ No newline at end of file diff --git a/tooling/nargo_cli/tests/snapshots/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/execute__tests__expanded.snap b/tooling/nargo_cli/tests/snapshots/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/execute__tests__expanded.snap new file mode 100644 index 00000000000..e23dc68552b --- /dev/null +++ b/tooling/nargo_cli/tests/snapshots/compile_success_no_bug/noirc_frontend_tests_traits_trait_bound_with_associated_constant/execute__tests__expanded.snap @@ -0,0 +1,20 @@ +--- +source: tooling/nargo_cli/tests/execute.rs +expression: expanded_code +--- +pub trait Other { + let N: u32; +} + +impl Other for Field { + let N: u32 = 1; +} + +pub trait Trait +where + T: Other, +{} + +impl Trait for i32 {} + +fn main() {}