From efef6b4c9f2ff4bce7e83bed004eb05332ac349f Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 31 Jul 2024 12:37:37 -0300 Subject: [PATCH] fix: error on unbound generics in structs (#5619) # Description ## Problem Resolves #5436 ## Summary The monomorphizer checks that no types exist that aren't unbound and can't have a default type. This is also the case for structs, but this check is done on field types after they are paired with generics. For example: ```rust struct Foo { x: T } fn main() { let _ = Foo { x: 1 }; } ``` Here we pair `T` with the generic type of 1, then it's defaulted to `Field`, no error. However, it can happen that a generic parameter isn't mentioned at all in a struct field. For example: ```rust struct Foo { } fn main() { let _ = Foo {}; } ``` Here `T` isn't paired with anything, so it's left unbound. However, because we only check that the resulting field types aren't unbound, no error happens. The solution in this PR is to check that generic parameters aren't unbound. This required a method like `convert_type`, called `check_type`, that only checks for this errors. The reason is that I wanted to avoid converting types twice. But let me know if that would be fine (or if there's another solution for this). ## Additional Context In Rust if a generic parameter isn't used in a field, an error about that is made. However, no error happens if the generic parameter is a `const` (similar to a `let N: u32` parameter in Noir) so here I chose to error un unbound generics, not check if a generic is unused. ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: jfecher --- .../src/monomorphization/mod.rs | 115 ++++++++++++++++-- .../Nargo.toml | 7 ++ .../src/main.nr | 6 + .../Nargo.toml | 7 ++ .../src/main.nr | 12 ++ 5 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 test_programs/compile_failure/type_annotation_needed_on_struct_constructor/Nargo.toml create mode 100644 test_programs/compile_failure/type_annotation_needed_on_struct_constructor/src/main.nr create mode 100644 test_programs/compile_failure/type_annotation_needed_on_struct_new/Nargo.toml create mode 100644 test_programs/compile_failure/type_annotation_needed_on_struct_new/src/main.nr diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index c63a6961da5..e6506a5fde6 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -383,8 +383,8 @@ impl<'interner> Monomorphizer<'interner> { self.parameter(field, &typ, new_params)?; } } - HirPattern::Struct(_, fields, _) => { - let struct_field_types = unwrap_struct_type(typ); + HirPattern::Struct(_, fields, location) => { + let struct_field_types = unwrap_struct_type(typ, *location)?; assert_eq!(struct_field_types.len(), fields.len()); let mut fields = @@ -663,8 +663,10 @@ impl<'interner> Monomorphizer<'interner> { constructor: HirConstructorExpression, id: node_interner::ExprId, ) -> Result { + let location = self.interner.expr_location(&id); + let typ = self.interner.id_type(id); - let field_types = unwrap_struct_type(&typ); + let field_types = unwrap_struct_type(&typ, location)?; let field_type_map = btree_map(&field_types, |x| x.clone()); @@ -740,8 +742,8 @@ impl<'interner> Monomorphizer<'interner> { let fields = unwrap_tuple_type(typ); self.unpack_tuple_pattern(value, patterns.into_iter().zip(fields)) } - HirPattern::Struct(_, patterns, _) => { - let fields = unwrap_struct_type(typ); + HirPattern::Struct(_, patterns, location) => { + let fields = unwrap_struct_type(typ, location)?; assert_eq!(patterns.len(), fields.len()); let mut patterns = @@ -975,12 +977,24 @@ impl<'interner> Monomorphizer<'interner> { } HirType::Struct(def, args) => { + // Not all generic arguments may be used in a struct's fields so we have to check + // the arguments as well as the fields in case any need to be defaulted or are unbound. + for arg in args { + Self::check_type(arg, location)?; + } + let fields = def.borrow().get_fields(args); let fields = try_vecmap(fields, |(_, field)| Self::convert_type(&field, location))?; ast::Type::Tuple(fields) } HirType::Alias(def, args) => { + // Similar to the struct case above: generics of an alias might not end up being + // used in the type that is aliased. + for arg in args { + Self::check_type(arg, location)?; + } + Self::convert_type(&def.borrow().get_type(args), location)? } @@ -1019,6 +1033,83 @@ impl<'interner> Monomorphizer<'interner> { }) } + // Similar to `convert_type` but returns an error if any type variable can't be defaulted. + fn check_type(typ: &HirType, location: Location) -> Result<(), MonomorphizationError> { + match typ { + HirType::FieldElement + | HirType::Integer(..) + | HirType::Bool + | HirType::String(..) + | HirType::Unit + | HirType::TraitAsType(..) + | HirType::Forall(_, _) + | HirType::Constant(_) + | HirType::Error + | HirType::Quoted(_) => Ok(()), + HirType::FmtString(_size, fields) => Self::check_type(fields.as_ref(), location), + HirType::Array(_length, element) => Self::check_type(element.as_ref(), location), + HirType::Slice(element) => Self::check_type(element.as_ref(), location), + HirType::NamedGeneric(binding, _, _) => { + if let TypeBinding::Bound(binding) = &*binding.borrow() { + return Self::check_type(binding, location); + } + + Ok(()) + } + + HirType::TypeVariable(binding, kind) => { + if let TypeBinding::Bound(binding) = &*binding.borrow() { + return Self::check_type(binding, location); + } + + // Default any remaining unbound type variables. + // This should only happen if the variable in question is unused + // and within a larger generic type. + let default = match kind.default_type() { + Some(typ) => typ, + None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }), + }; + + Self::check_type(&default, location) + } + + HirType::Struct(_def, args) => { + for arg in args { + Self::check_type(arg, location)?; + } + + Ok(()) + } + + HirType::Alias(_def, args) => { + for arg in args { + Self::check_type(arg, location)?; + } + + Ok(()) + } + + HirType::Tuple(fields) => { + for field in fields { + Self::check_type(field, location)?; + } + + Ok(()) + } + + HirType::Function(args, ret, env) => { + for arg in args { + Self::check_type(arg, location)?; + } + + Self::check_type(ret, location)?; + Self::check_type(env, location) + } + + HirType::MutableReference(element) => Self::check_type(element, location), + } + } + fn is_function_closure(&self, t: ast::Type) -> bool { if self.is_function_closure_type(&t) { true @@ -1753,9 +1844,19 @@ fn unwrap_tuple_type(typ: &HirType) -> Vec { } } -fn unwrap_struct_type(typ: &HirType) -> Vec<(String, HirType)> { +fn unwrap_struct_type( + typ: &HirType, + location: Location, +) -> Result, MonomorphizationError> { match typ.follow_bindings() { - HirType::Struct(def, args) => def.borrow().get_fields(&args), + HirType::Struct(def, args) => { + // Some of args might not be mentioned in fields, so we need to check that they aren't unbound. + for arg in &args { + Monomorphizer::check_type(arg, location)?; + } + + Ok(def.borrow().get_fields(&args)) + } other => unreachable!("unwrap_struct_type: expected struct, found {:?}", other), } } diff --git a/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/Nargo.toml b/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/Nargo.toml new file mode 100644 index 00000000000..ac7933fa250 --- /dev/null +++ b/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "type_annotation_needed_on_struct_constructor" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/src/main.nr b/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/src/main.nr new file mode 100644 index 00000000000..5207210dfbf --- /dev/null +++ b/test_programs/compile_failure/type_annotation_needed_on_struct_constructor/src/main.nr @@ -0,0 +1,6 @@ +struct Foo { +} + +fn main() { + let foo = Foo {}; +} diff --git a/test_programs/compile_failure/type_annotation_needed_on_struct_new/Nargo.toml b/test_programs/compile_failure/type_annotation_needed_on_struct_new/Nargo.toml new file mode 100644 index 00000000000..cb53d2924f4 --- /dev/null +++ b/test_programs/compile_failure/type_annotation_needed_on_struct_new/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "type_annotation_needed_on_struct_new" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/type_annotation_needed_on_struct_new/src/main.nr b/test_programs/compile_failure/type_annotation_needed_on_struct_new/src/main.nr new file mode 100644 index 00000000000..f740dfa6d37 --- /dev/null +++ b/test_programs/compile_failure/type_annotation_needed_on_struct_new/src/main.nr @@ -0,0 +1,12 @@ +struct Foo { +} + +impl Foo { + fn new() -> Foo { + Foo {} + } +} + +fn main() { + let foo = Foo::new(); +}