diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index 8e78ca5ec54..932c30edac1 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -1,7 +1,8 @@ +use acvm::FieldElement; use noirc_errors::Span; use crate::{ - ParsedModule, QuotedType, + BinaryTypeOperator, ParsedModule, QuotedType, ast::{ ArrayLiteral, AsTraitPath, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainExpression, ConstructorExpression, Expression, ExpressionKind, @@ -416,7 +417,9 @@ pub trait Visitor { true } - fn visit_expression_type(&mut self, _: &UnresolvedTypeExpression, _: Span) {} + fn visit_expression_type(&mut self, _: &UnresolvedTypeExpression, _: Span) -> bool { + true + } fn visit_format_string_type( &mut self, @@ -427,7 +430,9 @@ pub trait Visitor { true } - fn visit_string_type(&mut self, _: &UnresolvedTypeExpression, _: Span) {} + fn visit_string_type(&mut self, _: &UnresolvedTypeExpression, _: Span) -> bool { + true + } fn visit_unspecified_type(&mut self, _: Span) {} @@ -465,6 +470,30 @@ pub trait Visitor { true } + fn visit_unresolved_type_expression(&mut self, _: &UnresolvedTypeExpression) -> bool { + true + } + + fn visit_variable_type_expression(&mut self, _: &Path) -> bool { + true + } + + fn visit_constant_type_expression(&mut self, _value: FieldElement, _span: Span) {} + + fn visit_binary_type_expression( + &mut self, + _lhs: &UnresolvedTypeExpression, + _op: BinaryTypeOperator, + _rhs: &UnresolvedTypeExpression, + _span: Span, + ) -> bool { + true + } + + fn visit_as_trait_path_type_expression(&mut self, _as_trait_path: &AsTraitPath) -> bool { + true + } + fn visit_pattern(&mut self, _: &Pattern) -> bool { true } @@ -1357,6 +1386,7 @@ impl UnresolvedType { unresolved_type, self.location.span, ) { + unresolved_type_expression.accept(visitor); unresolved_type.accept(visitor); } } @@ -1405,21 +1435,28 @@ impl UnresolvedType { } } UnresolvedTypeData::Expression(expr) => { - visitor.visit_expression_type(expr, self.location.span); + if visitor.visit_expression_type(expr, self.location.span) { + expr.accept(visitor); + } } UnresolvedTypeData::FormatString(expr, typ) => { if visitor.visit_format_string_type(expr, typ, self.location.span) { + expr.accept(visitor); typ.accept(visitor); } } - UnresolvedTypeData::String(expr) => visitor.visit_string_type(expr, self.location.span), + UnresolvedTypeData::String(expr) => { + if visitor.visit_string_type(expr, self.location.span) { + expr.accept(visitor); + } + } UnresolvedTypeData::Unspecified => visitor.visit_unspecified_type(self.location.span), UnresolvedTypeData::Quoted(typ) => visitor.visit_quoted_type(typ, self.location.span), UnresolvedTypeData::FieldElement => { visitor.visit_field_element_type(self.location.span); } - UnresolvedTypeData::Integer(signedness, size) => { - visitor.visit_integer_type(*signedness, *size, self.location.span); + UnresolvedTypeData::Integer(signdness, size) => { + visitor.visit_integer_type(*signdness, *size, self.location.span); } UnresolvedTypeData::Bool => visitor.visit_bool_type(self.location.span), UnresolvedTypeData::Unit => visitor.visit_unit_type(self.location.span), @@ -1498,6 +1535,38 @@ impl UnresolvedTraitConstraint { } } +impl UnresolvedTypeExpression { + pub fn accept(&self, visitor: &mut impl Visitor) { + if visitor.visit_unresolved_type_expression(self) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + match self { + UnresolvedTypeExpression::Variable(path) => { + if visitor.visit_variable_type_expression(path) { + path.accept(visitor); + } + } + UnresolvedTypeExpression::Constant(field_element, location) => { + visitor.visit_constant_type_expression(*field_element, location.span); + } + UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, location) => { + if visitor.visit_binary_type_expression(lhs, *op, rhs, location.span) { + lhs.accept(visitor); + rhs.accept(visitor); + } + } + UnresolvedTypeExpression::AsTraitPath(as_trait_path) => { + if visitor.visit_as_trait_path_type_expression(as_trait_path) { + as_trait_path.accept(self.span(), visitor); + } + } + } + } +} + impl Pattern { pub fn accept(&self, visitor: &mut impl Visitor) { if visitor.visit_pattern(self) { diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 6cd21bb9c14..3e84695a2d7 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -17,7 +17,7 @@ use crate::{ ast::{ BlockExpression, FunctionKind, GenericTypeArgs, Ident, NoirFunction, NoirStruct, Param, Path, Pattern, TraitBound, UnresolvedGeneric, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedTypeData, UnsupportedNumericGenericType, + UnresolvedTraitConstraint, UnresolvedTypeData, UnsupportedNumericGenericType, Visitor, }, graph::CrateId, hir::{ @@ -64,6 +64,7 @@ mod traits; pub mod types; mod unquote; +use im::HashSet; use iter_extended::vecmap; use noirc_errors::{Located, Location}; pub(crate) use options::ElaboratorOptions; @@ -368,8 +369,8 @@ impl<'context> Elaborator<'context> { // re-collect the methods within into their proper module. This cannot be // done during def collection since we need to be able to resolve the type of // the impl since that determines the module we should collect into. - for ((_self_type, module), impls) in &mut items.impls { - self.collect_impls(*module, impls); + for ((self_type, module), impls) in &mut items.impls { + self.collect_impls(*module, impls, self_type); } // Bind trait impls to their trait. Collect trait functions, that have a @@ -1477,10 +1478,13 @@ impl<'context> Elaborator<'context> { &mut self, module: LocalModuleId, impls: &mut [(UnresolvedGenerics, Location, UnresolvedFunctions)], + self_type: &UnresolvedType, ) { self.local_module = module; for (generics, location, unresolved) in impls { + self.check_generics_appear_in_type(generics, self_type); + let old_generic_count = self.generics.len(); self.add_generics(generics); self.declare_methods_on_struct(None, unresolved, *location); @@ -2062,6 +2066,7 @@ impl<'context> Elaborator<'context> { for (generics, _, function_set) in function_sets { self.add_generics(generics); let self_type = self.resolve_type(self_type.clone()); + function_set.self_type = Some(self_type.clone()); self.self_type = Some(self_type); self.define_function_metas_for_functions(function_set); @@ -2201,6 +2206,46 @@ impl<'context> Elaborator<'context> { }) } + /// Check that all the generics show up in `self_type` (if they don't, we produce an error) + fn check_generics_appear_in_type( + &mut self, + generics: &[UnresolvedGeneric], + self_type: &UnresolvedType, + ) { + if generics.is_empty() { + return; + } + + // Turn each generic into an Ident + let mut idents = HashSet::new(); + for generic in generics { + match generic { + UnresolvedGeneric::Variable(ident) => { + idents.insert(ident.clone()); + } + UnresolvedGeneric::Numeric { ident, typ: _ } => { + idents.insert(ident.clone()); + } + UnresolvedGeneric::Resolved(quoted_type_id, span) => { + if let Type::NamedGeneric(_type_variable, name) = + self.interner.get_quoted_type(*quoted_type_id).follow_bindings() + { + idents.insert(Ident::new(name.to_string(), *span)); + } + } + } + } + + // Remove the ones that show up in `self_type` + let mut visitor = RemoveGenericsAppearingInTypeVisitor { idents: &mut idents }; + self_type.accept(&mut visitor); + + // The ones that remain are not mentioned in the impl: it's an error. + for ident in idents { + self.push_err(ResolverError::UnconstrainedTypeParameter { ident }); + } + } + /// Register a use of the given unstable feature. Errors if the feature has not /// been explicitly enabled in this package. pub fn use_unstable_feature(&mut self, feature: UnstableFeature, location: Location) { @@ -2219,3 +2264,15 @@ impl<'context> Elaborator<'context> { (errored, ret) } } + +struct RemoveGenericsAppearingInTypeVisitor<'a> { + idents: &'a mut HashSet, +} + +impl Visitor for RemoveGenericsAppearingInTypeVisitor<'_> { + fn visit_path(&mut self, path: &Path) { + if let Some(ident) = path.as_ident() { + self.idents.remove(ident); + } + } +} diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index d1e16447b6c..f58b82e19a0 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -196,6 +196,10 @@ pub enum ResolverError { "Indexing an array or slice with a type other than `u32` is deprecated and will soon be an error" )] NonU32Index { location: Location }, + #[error( + "The type parameter `{ident}` is not constrained by the impl trait, self type, or predicates" + )] + UnconstrainedTypeParameter { ident: Ident }, } impl ResolverError { @@ -270,7 +274,8 @@ impl ResolverError { | ResolverError::LowLevelFunctionOutsideOfStdlib { ident } | ResolverError::OracleMarkedAsConstrained { ident } | ResolverError::NoPredicatesAttributeOnUnconstrained { ident } - | ResolverError::FoldAttributeOnUnconstrained { ident } => ident.location(), + | ResolverError::FoldAttributeOnUnconstrained { ident } + | ResolverError::UnconstrainedTypeParameter { ident } => ident.location(), ResolverError::ArrayLengthInterpreter { error } => error.location(), ResolverError::PathResolutionError(path_resolution_error) => { path_resolution_error.location() @@ -811,6 +816,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *location, ) }, + ResolverError::UnconstrainedTypeParameter { ident} => { + Diagnostic::simple_error( + format!("The type parameter `{ident}` is not constrained by the impl trait, self type, or predicates"), + format!("Hint: remove the `{ident}` type parameter"), + ident.location(), + ) + } } } } diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 9477d974fd4..99e4e1ded99 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -4148,7 +4148,6 @@ fn int_min_global() { let _x = MIN; } "#; - assert_no_errors!(src); } @@ -4540,3 +4539,37 @@ fn cannot_determine_type_of_generic_argument_in_enum_constructor() { let features = vec![UnstableFeature::Enums]; check_monomorphization_error_using_features!(src, &features); } + +#[named] +#[test] +fn unconstrained_type_parameter_in_impl() { + let src = r#" + pub struct Foo {} + + impl Foo {} + ^ The type parameter `U` is not constrained by the impl trait, self type, or predicates + ~ Hint: remove the `U` type parameter + + fn main() { + let _ = Foo {}; + } + "#; + check_errors!(src); +} + +#[named] +#[test] +fn unconstrained_numeric_generic_in_impl() { + let src = r#" + pub struct Foo {} + + impl Foo {} + ^ The type parameter `N` is not constrained by the impl trait, self type, or predicates + ~ Hint: remove the `N` type parameter + + fn main() { + let _ = Foo {}; + } + "#; + check_errors!(src); +} diff --git a/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/Nargo.toml b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/Nargo.toml new file mode 100644 index 00000000000..1b9acb2e8fc --- /dev/null +++ b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/Nargo.toml @@ -0,0 +1,7 @@ + + [package] + name = "noirc_frontend_tests_unconstrained_numeric_generic_in_impl" + type = "bin" + authors = [""] + + [dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/src/main.nr b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/src/main.nr new file mode 100644 index 00000000000..529aa05f6ba --- /dev/null +++ b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/src/main.nr @@ -0,0 +1,9 @@ + + pub struct Foo {} + + impl Foo {} + + fn main() { + let _ = Foo {}; + } + \ No newline at end of file diff --git a/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/src_hash.txt b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/src_hash.txt new file mode 100644 index 00000000000..cefc6a0159f --- /dev/null +++ b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/src_hash.txt @@ -0,0 +1 @@ +10648545254261603633 \ No newline at end of file diff --git a/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/stderr.txt b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/stderr.txt new file mode 100644 index 00000000000..c56d7e91b87 --- /dev/null +++ b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_numeric_generic_in_impl/stderr.txt @@ -0,0 +1,8 @@ +error: The type parameter `N` is not constrained by the impl trait, self type, or predicates + ┌─ src/main.nr:4:18 + │ +4 │ impl Foo {} + │ - Hint: remove the `N` type parameter + │ + +Aborting due to 1 previous error diff --git a/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/Nargo.toml b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/Nargo.toml new file mode 100644 index 00000000000..e41a788b5b9 --- /dev/null +++ b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/Nargo.toml @@ -0,0 +1,7 @@ + + [package] + name = "noirc_frontend_tests_unconstrained_type_parameter_in_impl" + type = "bin" + authors = [""] + + [dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/src/main.nr b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/src/main.nr new file mode 100644 index 00000000000..9c53a79ac1c --- /dev/null +++ b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/src/main.nr @@ -0,0 +1,9 @@ + + pub struct Foo {} + + impl Foo {} + + fn main() { + let _ = Foo {}; + } + \ No newline at end of file diff --git a/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/src_hash.txt b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/src_hash.txt new file mode 100644 index 00000000000..7058e6db6f3 --- /dev/null +++ b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/src_hash.txt @@ -0,0 +1 @@ +5158513121244495309 \ No newline at end of file diff --git a/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/stderr.txt b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/stderr.txt new file mode 100644 index 00000000000..75072956871 --- /dev/null +++ b/test_programs/compile_failure/noirc_frontend_tests_unconstrained_type_parameter_in_impl/stderr.txt @@ -0,0 +1,8 @@ +error: The type parameter `U` is not constrained by the impl trait, self type, or predicates + ┌─ src/main.nr:4:17 + │ +4 │ impl Foo {} + │ - Hint: remove the `U` type parameter + │ + +Aborting due to 1 previous error