diff --git a/aztec_macros/src/transforms/storage.rs b/aztec_macros/src/transforms/storage.rs index 872e7c37801..a2cc522b7a2 100644 --- a/aztec_macros/src/transforms/storage.rs +++ b/aztec_macros/src/transforms/storage.rs @@ -91,7 +91,7 @@ pub fn inject_context_in_storage(module: &mut SortedModule) -> Result<(), AztecM r#struct.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(storage)")) }) .unwrap(); - storage_struct.generics.push(ident("Context")); + storage_struct.generics.push(ident("Context").into()); storage_struct .fields .iter_mut() @@ -243,7 +243,7 @@ pub fn generate_storage_implementation( span: Some(Span::default()), }, type_span: Span::default(), - generics: vec![generic_context_ident], + generics: vec![generic_context_ident.into()], methods: vec![(init, Span::default())], diff --git a/compiler/noirc_driver/tests/stdlib_warnings.rs b/compiler/noirc_driver/tests/stdlib_warnings.rs index 327c8daad06..84f46793caf 100644 --- a/compiler/noirc_driver/tests/stdlib_warnings.rs +++ b/compiler/noirc_driver/tests/stdlib_warnings.rs @@ -3,6 +3,7 @@ use std::path::Path; use noirc_driver::{file_manager_with_stdlib, prepare_crate, ErrorsAndWarnings}; use noirc_frontend::hir::{def_map::parse_file, Context}; +#[ignore = "Temporarily ignoring the test until the stdlib is updated to use explicit numeric generics"] #[test] fn stdlib_does_not_produce_constant_warnings() -> Result<(), ErrorsAndWarnings> { // We use a minimal source file so that if stdlib produces warnings then we can expect these warnings to _always_ diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 2657869a9d7..b2fb6013613 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -5,9 +5,11 @@ use crate::ast::{ Ident, ItemVisibility, Path, Pattern, Recoverable, Statement, StatementKind, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, Visibility, }; +use crate::hir::def_collector::errors::DefCollectorErrorKind; use crate::macros_api::StructId; use crate::node_interner::ExprId; use crate::token::{Attributes, Token, Tokens}; +use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; @@ -46,7 +48,72 @@ pub enum ExpressionKind { /// A Vec of unresolved names for type variables. /// For `fn foo(...)` this corresponds to vec!["A", "B"]. -pub type UnresolvedGenerics = Vec; +pub type UnresolvedGenerics = Vec; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum UnresolvedGeneric { + Variable(Ident), + Numeric { ident: Ident, typ: UnresolvedType }, +} + +impl UnresolvedGeneric { + pub fn span(&self) -> Span { + match self { + UnresolvedGeneric::Variable(ident) => ident.0.span(), + UnresolvedGeneric::Numeric { ident, typ } => { + ident.0.span().merge(typ.span.unwrap_or_default()) + } + } + } + + pub fn kind(&self) -> Result { + match self { + UnresolvedGeneric::Variable(_) => Ok(Kind::Normal), + UnresolvedGeneric::Numeric { typ, .. } => { + let typ = self.resolve_numeric_kind_type(typ)?; + Ok(Kind::Numeric(Box::new(typ))) + } + } + } + + fn resolve_numeric_kind_type( + &self, + typ: &UnresolvedType, + ) -> Result { + use crate::ast::UnresolvedTypeData::{FieldElement, Integer}; + + match typ.typ { + FieldElement => Ok(Type::FieldElement), + Integer(sign, bits) => Ok(Type::Integer(sign, bits)), + // Only fields and integers are supported for numeric kinds + _ => Err(DefCollectorErrorKind::UnsupportedNumericGenericType { + ident: self.ident().clone(), + typ: typ.typ.clone(), + }), + } + } + + pub(crate) fn ident(&self) -> &Ident { + match self { + UnresolvedGeneric::Variable(ident) | UnresolvedGeneric::Numeric { ident, .. } => ident, + } + } +} + +impl Display for UnresolvedGeneric { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnresolvedGeneric::Variable(ident) => write!(f, "{ident}"), + UnresolvedGeneric::Numeric { ident, typ } => write!(f, "let {ident}: {typ}"), + } + } +} + +impl From for UnresolvedGeneric { + fn from(value: Ident) -> Self { + UnresolvedGeneric::Variable(value) + } +} impl ExpressionKind { pub fn into_path(self) -> Option { diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index a1ae349b537..ddd675cef30 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -146,7 +146,7 @@ pub struct UnaryRhsMethodCall { } /// The precursor to TypeExpression, this is the type that the parser allows -/// to be used in the length position of an array type. Only constants, variables, +/// to be used in the length position of an array type. Only constant integers, variables, /// and numeric binary operators are allowed here. #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum UnresolvedTypeExpression { @@ -259,6 +259,10 @@ impl UnresolvedType { pub fn unspecified() -> UnresolvedType { UnresolvedType { typ: UnresolvedTypeData::Unspecified, span: None } } + + pub(crate) fn is_type_expression(&self) -> bool { + matches!(&self.typ, UnresolvedTypeData::Expression(_)) + } } impl UnresolvedTypeData { diff --git a/compiler/noirc_frontend/src/ast/structure.rs b/compiler/noirc_frontend/src/ast/structure.rs index bda6b8c0b11..bb2d89841b9 100644 --- a/compiler/noirc_frontend/src/ast/structure.rs +++ b/compiler/noirc_frontend/src/ast/structure.rs @@ -20,7 +20,7 @@ impl NoirStruct { pub fn new( name: Ident, attributes: Vec, - generics: Vec, + generics: UnresolvedGenerics, fields: Vec<(Ident, UnresolvedType)>, span: Span, ) -> NoirStruct { diff --git a/compiler/noirc_frontend/src/ast/traits.rs b/compiler/noirc_frontend/src/ast/traits.rs index 9d7345d0270..b1b14e3f657 100644 --- a/compiler/noirc_frontend/src/ast/traits.rs +++ b/compiler/noirc_frontend/src/ast/traits.rs @@ -14,7 +14,7 @@ use crate::node_interner::TraitId; #[derive(Clone, Debug)] pub struct NoirTrait { pub name: Ident, - pub generics: Vec, + pub generics: UnresolvedGenerics, pub where_clause: Vec, pub span: Span, pub items: Vec, @@ -26,7 +26,7 @@ pub struct NoirTrait { pub enum TraitItem { Function { name: Ident, - generics: Vec, + generics: UnresolvedGenerics, parameters: Vec<(Ident, UnresolvedType)>, return_type: FunctionReturnType, where_clause: Vec, diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 9d864a0de91..02d4225875b 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -29,7 +29,7 @@ use crate::{ }, node_interner::{DefinitionKind, ExprId, FuncId}, token::Tokens, - QuotedType, Shared, StructType, Type, + Kind, QuotedType, Shared, StructType, Type, }; use super::Elaborator; @@ -52,7 +52,20 @@ impl<'context> Elaborator<'context> { ExpressionKind::If(if_) => self.elaborate_if(*if_), ExpressionKind::Variable(variable, generics) => { let generics = generics.map(|option_inner| { - option_inner.into_iter().map(|generic| self.resolve_type(generic)).collect() + option_inner + .into_iter() + .map(|generic| { + // All type expressions should resolve to a `Type::Constant` + if generic.is_type_expression() { + self.resolve_type_inner( + generic, + &Kind::Numeric(Box::new(Type::default_int_type())), + ) + } else { + self.resolve_type(generic) + } + }) + .collect() }); return self.elaborate_variable(variable, generics); } diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index dc99ceae2f0..7b3cc59e341 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -22,6 +22,7 @@ use crate::{ expr::HirIdent, function::{FunctionBody, Parameters}, traits::TraitConstraint, + types::{Generics, Kind, ResolvedGeneric}, }, macros_api::{ BlockExpression, Ident, NodeInterner, NoirFunction, NoirStruct, Pattern, @@ -33,7 +34,7 @@ use crate::{ Shared, Type, TypeVariable, }; use crate::{ - ast::{TraitBound, UnresolvedGenerics}, + ast::{TraitBound, UnresolvedGeneric, UnresolvedGenerics}, graph::CrateId, hir::{ def_collector::{dc_crate::CollectedItems, errors::DefCollectorErrorKind}, @@ -44,7 +45,6 @@ use crate::{ hir_def::function::{FuncMeta, HirFunction}, macros_api::{Param, Path, UnresolvedType, UnresolvedTypeData}, node_interner::TraitImplId, - Generics, }; use crate::{ hir::{ @@ -105,7 +105,7 @@ pub struct Elaborator<'context> { /// unique type variables if we're resolving a struct. Empty otherwise. /// This is a Vec rather than a map to preserve the order a functions generics /// were declared in. - generics: Vec<(Rc, TypeVariable, Span)>, + generics: Vec, /// When resolving lambda expressions, we need to keep track of the variables /// that are captured. We do this in order to create the hidden environment @@ -239,12 +239,13 @@ impl<'context> Elaborator<'context> { self.define_type_alias(alias_id, alias); } - self.define_function_metas(&mut items.functions, &mut items.impls, &mut items.trait_impls); - self.collect_traits(items.traits); - // Must resolve structs before we resolve globals. self.collect_struct_definitions(items.types); + self.define_function_metas(&mut items.functions, &mut items.impls, &mut items.trait_impls); + + self.collect_traits(items.traits); + // Before we resolve any function symbols we must go through our impls and // 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 @@ -335,14 +336,26 @@ impl<'context> Elaborator<'context> { self.in_unconstrained_fn = true; } + // Introduce all numeric generics into scope + for generic in &func_meta.all_generics { + if let Kind::Numeric(typ) = &generic.kind { + let definition = DefinitionKind::GenericType(generic.type_var.clone()); + let ident = Ident::new(generic.name.to_string(), generic.span); + let hir_ident = + self.add_variable_decl_inner(ident, false, false, false, definition); + self.interner.push_definition_type(hir_ident.id, *typ.clone()); + } + } + // The DefinitionIds for each parameter were already created in define_function_meta // so we need to reintroduce the same IDs into scope here. for parameter in &func_meta.parameter_idents { let name = self.interner.definition_name(parameter.id).to_owned(); - self.add_existing_variable_to_scope(name, parameter.clone()); + self.add_existing_variable_to_scope(name, parameter.clone(), true); } self.generics = func_meta.all_generics.clone(); + self.declare_numeric_generics(&func_meta.parameters, func_meta.return_type()); self.add_trait_constraints_to_scope(&func_meta); @@ -439,7 +452,7 @@ impl<'context> Elaborator<'context> { generics.push(new_generic.clone()); let name = format!("impl {trait_path}"); - let generic_type = Type::NamedGeneric(new_generic, Rc::new(name)); + let generic_type = Type::NamedGeneric(new_generic, Rc::new(name), Kind::Normal); let trait_bound = TraitBound { trait_path, trait_id: None, trait_generics }; if let Some(new_constraint) = self.resolve_trait_bound(&trait_bound, generic_type.clone()) { @@ -456,25 +469,57 @@ impl<'context> Elaborator<'context> { // Map the generic to a fresh type variable let id = self.interner.next_type_variable_id(); let typevar = TypeVariable::unbound(id); - let span = generic.0.span(); + let ident = generic.ident(); + let span = ident.0.span(); + + // Resolve the generic's kind + let kind = self.resolve_generic_kind(generic); // Check for name collisions of this generic - let name = Rc::new(generic.0.contents.clone()); + let name = Rc::new(ident.0.contents.clone()); + + let resolved_generic = + ResolvedGeneric { name: name.clone(), type_var: typevar.clone(), kind, span }; - if let Some((_, _, first_span)) = self.find_generic(&name) { + if let Some(generic) = self.find_generic(&name) { self.push_err(ResolverError::DuplicateDefinition { - name: generic.0.contents.clone(), - first_span: *first_span, + name: ident.0.contents.clone(), + first_span: generic.span, second_span: span, }); } else { - self.generics.push((name, typevar.clone(), span)); + self.generics.push(resolved_generic.clone()); } - typevar + resolved_generic }) } + /// Return the kind of an unresolved generic. + /// If a numeric generic has been specified, resolve the annotated type to make + /// sure only primitive numeric types are being used. + pub(super) fn resolve_generic_kind(&mut self, generic: &UnresolvedGeneric) -> Kind { + if let UnresolvedGeneric::Numeric { ident, typ } = generic { + let typ = typ.clone(); + let typ = if typ.is_type_expression() { + self.resolve_type_inner(typ, &Kind::Numeric(Box::new(Type::default_int_type()))) + } else { + self.resolve_type(typ.clone()) + }; + if !matches!(typ, Type::FieldElement | Type::Integer(_, _)) { + let unsupported_typ_err = + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { + ident: ident.clone(), + typ: typ.clone(), + }); + self.errors.push((unsupported_typ_err, self.file)); + } + Kind::Numeric(Box::new(typ)) + } else { + Kind::Normal + } + } + fn push_err(&mut self, error: impl Into) { self.errors.push((error.into(), self.file)); } @@ -523,12 +568,20 @@ impl<'context> Elaborator<'context> { } fn resolve_trait_bound(&mut self, bound: &TraitBound, typ: Type) -> Option { - let trait_generics = vecmap(&bound.trait_generics, |typ| self.resolve_type(typ.clone())); + let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?; + + let resolved_generics = &the_trait.generics.clone(); + assert_eq!(resolved_generics.len(), bound.trait_generics.len()); + let generics_with_types = resolved_generics.iter().zip(&bound.trait_generics); + let trait_generics = vecmap(generics_with_types, |(generic, typ)| { + self.resolve_type_inner(typ.clone(), &generic.kind) + }); - let span = bound.trait_path.span(); let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?; let trait_id = the_trait.id; + let span = bound.trait_path.span(); + let expected_generics = the_trait.generics.len(); let actual_generics = trait_generics.len(); @@ -596,7 +649,7 @@ impl<'context> Elaborator<'context> { let mut trait_constraints = self.resolve_trait_constraints(&func.def.where_clause); - let mut generics = vecmap(&self.generics, |(_, typevar, _)| typevar.clone()); + let mut generics = vecmap(&self.generics, |generic| generic.type_var.clone()); let mut parameters = Vec::new(); let mut parameter_types = Vec::new(); let mut parameter_idents = Vec::new(); @@ -612,7 +665,7 @@ impl<'context> Elaborator<'context> { UnresolvedTypeData::TraitAsType(path, args) => { self.desugar_impl_trait_arg(path, args, &mut generics, &mut trait_constraints) } - _ => self.resolve_type_inner(typ), + _ => self.resolve_type_inner(typ, &Kind::Normal), }; self.check_if_type_is_valid_for_program_input( @@ -621,6 +674,7 @@ impl<'context> Elaborator<'context> { has_inline_attribute, type_span, ); + let pattern = self.elaborate_pattern_and_store_ids( pattern, typ.clone(), @@ -645,8 +699,8 @@ impl<'context> Elaborator<'context> { let direct_generics = func.def.generics.iter(); let direct_generics = direct_generics - .filter_map(|generic| self.find_generic(&generic.0.contents)) - .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) + .filter_map(|generic| self.find_generic(&generic.ident().0.contents)) + .map(|ResolvedGeneric { name, type_var, .. }| (name.clone(), type_var.clone())) .collect(); let statements = std::mem::take(&mut func.def.body.statements); @@ -710,6 +764,7 @@ impl<'context> Elaborator<'context> { } } + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove implicit numeric generics fn declare_numeric_generics(&mut self, params: &Parameters, return_type: &Type) { if self.generics.is_empty() { return; @@ -722,12 +777,32 @@ impl<'context> Elaborator<'context> { // We can fail to find the generic in self.generics if it is an implicit one created // by the compiler. This can happen when, e.g. eliding array lengths using the slice // syntax [T]. - if let Some((name, _, span)) = - self.generics.iter().find(|(name, _, _)| name.as_ref() == &name_to_find) + if let Some(ResolvedGeneric { name, span, kind, .. }) = + self.generics.iter_mut().find(|generic| generic.name.as_ref() == &name_to_find) { + let scope = self.scopes.get_mut_scope(); + let value = scope.find(&name_to_find); + if value.is_some() { + // With the addition of explicit numeric generics we do not want to introduce numeric generics in this manner + // However, this is going to be a big breaking change so for now we simply issue a warning while users have time + // to transition to the new syntax + // e.g. this code would break with a duplicate definition error: + // ``` + // fn foo(arr: [Field; N]) { } + // ``` + continue; + } + *kind = Kind::Numeric(Box::new(Type::default_int_type())); let ident = Ident::new(name.to_string(), *span); let definition = DefinitionKind::GenericType(type_variable); - self.add_variable_decl_inner(ident, false, false, false, definition); + self.add_variable_decl_inner(ident.clone(), false, false, false, definition); + + self.errors.push(( + CompilationError::ResolverError(ResolverError::UseExplicitNumericGeneric { + ident, + }), + self.file, + )); } } } @@ -753,7 +828,7 @@ impl<'context> Elaborator<'context> { } } - fn elaborate_impls(&mut self, impls: Vec<(Vec, Span, UnresolvedFunctions)>) { + fn elaborate_impls(&mut self, impls: Vec<(UnresolvedGenerics, Span, UnresolvedFunctions)>) { for (_, _, functions) in impls { self.file = functions.file_id; self.recover_generics(|this| this.elaborate_functions(functions)); @@ -783,7 +858,7 @@ impl<'context> Elaborator<'context> { fn collect_impls( &mut self, module: LocalModuleId, - impls: &mut [(Vec, Span, UnresolvedFunctions)], + impls: &mut [(UnresolvedGenerics, Span, UnresolvedFunctions)], ) { self.local_module = module; @@ -800,7 +875,6 @@ impl<'context> Elaborator<'context> { self.local_module = trait_impl.module_id; self.file = trait_impl.file_id; self.current_trait_impl = trait_impl.impl_id; - trait_impl.trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); let self_type = trait_impl.methods.self_type.clone(); let self_type = @@ -844,7 +918,7 @@ impl<'context> Elaborator<'context> { methods, }); - let generics = vecmap(&self.generics, |(_, type_variable, _)| type_variable.clone()); + let generics = vecmap(&self.generics, |generic| generic.type_var.clone()); if let Err((prev_span, prev_file)) = self.interner.add_trait_implementation( self_type.clone(), @@ -868,6 +942,7 @@ impl<'context> Elaborator<'context> { } self.generics.clear(); + self.current_trait_impl = None; self.self_type = None; } @@ -1100,11 +1175,32 @@ impl<'context> Elaborator<'context> { let attributes = std::mem::take(&mut typ.struct_def.attributes); let span = typ.struct_def.span; - let (generics, fields) = self.resolve_struct_fields(typ.struct_def, type_id); + let fields = self.resolve_struct_fields(typ.struct_def, type_id); self.interner.update_struct(type_id, |struct_def| { struct_def.set_fields(fields); - struct_def.generics = generics; + + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this with implicit numeric generics + // This is only necessary for resolving named types when implicit numeric generics are used. + let mut found_names = Vec::new(); + struct_def.find_numeric_generics_in_fields(&mut found_names); + for generic in struct_def.generics.iter_mut() { + for found_generic in found_names.iter() { + if found_generic == generic.name.as_str() { + if matches!(generic.kind, Kind::Normal) { + let ident = Ident::new(generic.name.to_string(), generic.span); + self.errors.push(( + CompilationError::ResolverError( + ResolverError::UseExplicitNumericGeneric { ident }, + ), + self.file, + )); + generic.kind = Kind::Numeric(Box::new(Type::default_int_type())); + } + break; + } + } + } }); self.run_comptime_attributes_on_struct(attributes, type_id, span); @@ -1167,17 +1263,20 @@ impl<'context> Elaborator<'context> { &mut self, unresolved: NoirStruct, struct_id: StructId, - ) -> (Generics, Vec<(Ident, Type)>) { + ) -> Vec<(Ident, Type)> { self.recover_generics(|this| { - let generics = this.add_generics(&unresolved.generics); - this.current_item = Some(DependencyId::Struct(struct_id)); this.resolving_ids.insert(struct_id); + + let struct_def = this.interner.get_struct(struct_id); + this.add_existing_generics(&unresolved.generics, &struct_def.borrow().generics); + let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, this.resolve_type(typ))); + this.resolving_ids.remove(&struct_id); - (generics, fields) + fields }) } @@ -1273,12 +1372,29 @@ impl<'context> Elaborator<'context> { self.file = trait_impl.file_id; self.local_module = trait_impl.module_id; + trait_impl.trait_id = self.resolve_trait_by_path(trait_impl.trait_path.clone()); let unresolved_type = &trait_impl.object_type; + self.add_generics(&trait_impl.generics); trait_impl.resolved_generics = self.generics.clone(); - let trait_generics = - vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())); + // Fetch trait constraints here + let trait_generics = if let Some(trait_id) = trait_impl.trait_id { + let trait_def = self.interner.get_trait(trait_id); + let resolved_generics = trait_def.generics.clone(); + assert_eq!(resolved_generics.len(), trait_impl.trait_generics.len()); + trait_impl + .trait_generics + .iter() + .enumerate() + .map(|(i, generic)| { + self.resolve_type_inner(generic.clone(), &resolved_generics[i].kind) + }) + .collect() + } else { + // We still resolve as to continue type checking + vecmap(&trait_impl.trait_generics, |generic| self.resolve_type(generic.clone())) + }; trait_impl.resolved_trait_generics = trait_generics; diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 4d07009e064..4f04f5c523c 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -301,9 +301,14 @@ impl<'context> Elaborator<'context> { ident } - pub fn add_existing_variable_to_scope(&mut self, name: String, ident: HirIdent) { + pub fn add_existing_variable_to_scope( + &mut self, + name: String, + ident: HirIdent, + warn_if_unused: bool, + ) { let second_span = ident.location.span; - let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; + let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused }; let old_value = self.scopes.get_mut_scope().add_key_value(name.clone(), resolver_meta); @@ -389,6 +394,7 @@ impl<'context> Elaborator<'context> { ) -> (ExprId, Type) { let span = variable.span; let expr = self.resolve_variable(variable); + let id = self.interner.push_expr(HirExpression::Ident(expr.clone(), generics.clone())); self.interner.push_expr_location(id, span, self.file); let typ = self.type_check_variable(expr, id, generics); @@ -464,8 +470,8 @@ impl<'context> Elaborator<'context> { for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { // Avoid binding t = t - if !arg.occurs(param.id()) { - bindings.insert(param.id(), (param.clone(), arg.clone())); + if !arg.occurs(param.type_var.id()) { + bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); } } diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 3e04dbc784a..77ac8e476f8 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -1,10 +1,12 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, rc::Rc}; use iter_extended::vecmap; use noirc_errors::Location; use crate::{ - ast::{FunctionKind, TraitItem, UnresolvedGenerics, UnresolvedTraitConstraint}, + ast::{ + FunctionKind, TraitItem, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, + }, hir::def_collector::dc_crate::UnresolvedTrait, hir_def::traits::{TraitConstant, TraitFunction, TraitType}, macros_api::{ @@ -13,7 +15,7 @@ use crate::{ }, node_interner::{FuncId, TraitId}, token::Attributes, - Type, TypeVariableKind, + Kind, ResolvedGeneric, Type, TypeVariableKind, }; use super::Elaborator; @@ -22,7 +24,11 @@ impl<'context> Elaborator<'context> { pub fn collect_traits(&mut self, traits: BTreeMap) { for (trait_id, unresolved_trait) in traits { self.recover_generics(|this| { - this.add_generics(&unresolved_trait.trait_def.generics); + let resolved_generics = this.interner.get_trait(trait_id).generics.clone(); + this.add_existing_generics( + &unresolved_trait.trait_def.generics, + &resolved_generics, + ); // Resolve order // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) @@ -34,7 +40,6 @@ impl<'context> Elaborator<'context> { this.interner.update_trait(trait_id, |trait_def| { trait_def.set_methods(methods); - trait_def.generics = vecmap(&this.generics, |(_, generic, _)| generic.clone()); }); }); @@ -87,10 +92,20 @@ impl<'context> Elaborator<'context> { Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); let name_span = the_trait.name.span(); - this.add_existing_generic("Self", name_span, self_typevar); + this.add_existing_generic( + &UnresolvedGeneric::Variable(Ident::from("Self")), + name_span, + &ResolvedGeneric { + name: Rc::new("Self".to_owned()), + type_var: self_typevar, + span: name_span, + kind: Kind::Normal, + }, + ); this.self_type = Some(self_type.clone()); let func_id = unresolved_trait.method_ids[&name.0.contents]; + this.resolve_trait_function( name, generics, @@ -105,7 +120,7 @@ impl<'context> Elaborator<'context> { let arguments = vecmap(&func_meta.parameters.0, |(_, typ, _)| typ.clone()); let return_type = func_meta.return_type().clone(); - let generics = vecmap(&this.generics, |(_, type_var, _)| type_var.clone()); + let generics = vecmap(&this.generics, |generic| generic.type_var.clone()); let default_impl_list: Vec<_> = unresolved_trait .fns_with_default_impl @@ -147,6 +162,7 @@ impl<'context> Elaborator<'context> { func_id: FuncId, ) { let old_generic_count = self.generics.len(); + self.scopes.start_function(); let kind = FunctionKind::Normal; diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index fcb7ac94c26..06f7b0f9b6c 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -5,9 +5,13 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use crate::{ - ast::{BinaryOpKind, IntegerBitSize, UnresolvedGenerics, UnresolvedTypeExpression}, + ast::{ + BinaryOpKind, IntegerBitSize, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTypeExpression, + }, hir::{ comptime::{Interpreter, Value}, + def_collector::dc_crate::CompilationError, def_map::ModuleDefId, resolution::{ errors::ResolverError, @@ -28,48 +32,75 @@ use crate::{ UnaryOp, UnresolvedType, UnresolvedTypeData, }, node_interner::{DefinitionKind, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId}, - Generics, Type, TypeBinding, TypeVariable, TypeVariableKind, + Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeVariable, TypeVariableKind, }; use super::{lints, Elaborator}; impl<'context> Elaborator<'context> { - /// Translates an UnresolvedType to a Type + /// Translates an UnresolvedType to a Type with a `TypeKind::Normal` pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { let span = typ.span; - let resolved_type = self.resolve_type_inner(typ); + let resolved_type = self.resolve_type_inner(typ, &Kind::Normal); if resolved_type.is_nested_slice() { - self.push_err(ResolverError::NestedSlices { span: span.unwrap() }); + self.push_err(ResolverError::NestedSlices { + span: span.expect("Type should have span"), + }); } resolved_type } /// Translates an UnresolvedType into a Type and appends any /// freshly created TypeVariables created to new_variables. - pub fn resolve_type_inner(&mut self, typ: UnresolvedType) -> Type { + pub fn resolve_type_inner(&mut self, typ: UnresolvedType, kind: &Kind) -> Type { use crate::ast::UnresolvedTypeData::*; + let span = typ.span; + let resolved_type = match typ.typ { FieldElement => Type::FieldElement, Array(size, elem) => { - let elem = Box::new(self.resolve_type_inner(*elem)); - let size = self.convert_expression_type(size); + let elem = Box::new(self.resolve_type_inner(*elem, kind)); + let mut size = self.convert_expression_type(size); + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this once we only have explicit numeric generics + if let Type::NamedGeneric(type_var, name, _) = size { + size = Type::NamedGeneric( + type_var, + name, + Kind::Numeric(Box::new(Type::default_int_type())), + ); + } Type::Array(Box::new(size), elem) } Slice(elem) => { - let elem = Box::new(self.resolve_type_inner(*elem)); + let elem = Box::new(self.resolve_type_inner(*elem, kind)); Type::Slice(elem) } Expression(expr) => self.convert_expression_type(expr), Integer(sign, bits) => Type::Integer(sign, bits), Bool => Type::Bool, String(size) => { - let resolved_size = self.convert_expression_type(size); + let mut resolved_size = self.convert_expression_type(size); + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this once we only have explicit numeric generics + if let Type::NamedGeneric(type_var, name, _) = resolved_size { + resolved_size = Type::NamedGeneric( + type_var, + name, + Kind::Numeric(Box::new(Type::default_int_type())), + ); + } Type::String(Box::new(resolved_size)) } FormatString(size, fields) => { - let resolved_size = self.convert_expression_type(size); - let fields = self.resolve_type_inner(*fields); + let mut resolved_size = self.convert_expression_type(size); + if let Type::NamedGeneric(type_var, name, _) = resolved_size { + resolved_size = Type::NamedGeneric( + type_var, + name, + Kind::Numeric(Box::new(Type::default_int_type())), + ); + } + let fields = self.resolve_type_inner(*fields, kind); Type::FmtString(Box::new(resolved_size), Box::new(fields)) } Quoted(quoted) => Type::Quoted(quoted), @@ -79,10 +110,12 @@ impl<'context> Elaborator<'context> { Named(path, args, _) => self.resolve_named_type(path, args), TraitAsType(path, args) => self.resolve_trait_as_type(path, args), - Tuple(fields) => Type::Tuple(vecmap(fields, |field| self.resolve_type_inner(field))), + Tuple(fields) => { + Type::Tuple(vecmap(fields, |field| self.resolve_type_inner(field, kind))) + } Function(args, ret, env) => { - let args = vecmap(args, |arg| self.resolve_type_inner(arg)); - let ret = Box::new(self.resolve_type_inner(*ret)); + let args = vecmap(args, |arg| self.resolve_type_inner(arg, kind)); + let ret = Box::new(self.resolve_type_inner(*ret, kind)); // expect() here is valid, because the only places we don't have a span are omitted types // e.g. a function without return type implicitly has a spanless UnresolvedType::Unit return type @@ -90,10 +123,10 @@ impl<'context> Elaborator<'context> { let env_span = env.span.expect("Unexpected missing span for closure environment type"); - let env = Box::new(self.resolve_type_inner(*env)); + let env = Box::new(self.resolve_type_inner(*env, kind)); match *env { - Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _) => { + Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { Type::Function(args, ret, env) } _ => { @@ -106,9 +139,9 @@ impl<'context> Elaborator<'context> { } } MutableReference(element) => { - Type::MutableReference(Box::new(self.resolve_type_inner(*element))) + Type::MutableReference(Box::new(self.resolve_type_inner(*element, kind))) } - Parenthesized(typ) => self.resolve_type_inner(*typ), + Parenthesized(typ) => self.resolve_type_inner(*typ, kind), }; if let Type::Struct(_, _) = resolved_type { @@ -120,11 +153,37 @@ impl<'context> Elaborator<'context> { ); } } + + // Check that any types with a type kind match the expected type kind supplied to this function + // TODO(https://github.com/noir-lang/noir/issues/5156): make this named generic check more general with `*resolved_kind != kind` + // as implicit numeric generics still existing makes this check more challenging to enforce + // An example of a more general check that we should switch to: + // if resolved_type.kind() != kind.clone() { + // let expected_typ_err = CompilationError::TypeError(TypeCheckError::TypeKindMismatch { + // expected_kind: kind.to_string(), + // expr_kind: resolved_type.kind().to_string(), + // expr_span: span.expect("Type should have span"), + // }); + // self.errors.push((expected_typ_err, self.file)); + // return Type::Error; + // } + if let Type::NamedGeneric(_, name, resolved_kind) = &resolved_type { + if matches!(resolved_kind, Kind::Numeric { .. }) && matches!(kind, Kind::Normal) { + let expected_typ_err = + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { + name: name.to_string(), + span: span.expect("Type should have span"), + }); + self.errors.push((expected_typ_err, self.file)); + return Type::Error; + } + } + resolved_type } - pub fn find_generic(&self, target_name: &str) -> Option<&(Rc, TypeVariable, Span)> { - self.generics.iter().find(|(name, _, _)| name.as_ref() == target_name) + pub fn find_generic(&self, target_name: &str) -> Option<&ResolvedGeneric> { + self.generics.iter().find(|generic| generic.name.as_ref() == target_name) } fn resolve_named_type(&mut self, path: Path, args: Vec) -> Type { @@ -152,7 +211,6 @@ impl<'context> Elaborator<'context> { } let span = path.span(); - let mut args = vecmap(args, |arg| self.resolve_type_inner(arg)); if let Some(type_alias) = self.lookup_type_alias(path.clone()) { let type_alias = type_alias.borrow(); @@ -160,6 +218,10 @@ impl<'context> Elaborator<'context> { let type_alias_string = type_alias.to_string(); let id = type_alias.id; + let mut args = vecmap(type_alias.generics.iter().zip(args), |(generic, arg)| { + self.resolve_type_inner(arg, &generic.kind) + }); + self.verify_generics_count(expected_generic_count, &mut args, span, || { type_alias_string }); @@ -192,6 +254,7 @@ impl<'context> Elaborator<'context> { } let expected_generic_count = struct_type.borrow().generics.len(); + if !self.in_contract && self .interner @@ -203,6 +266,12 @@ impl<'context> Elaborator<'context> { span: struct_type.borrow().name.span(), }); } + + let mut args = + vecmap(struct_type.borrow().generics.iter().zip(args), |(generic, arg)| { + self.resolve_type_inner(arg, &generic.kind) + }); + self.verify_generics_count(expected_generic_count, &mut args, span, || { struct_type.borrow().to_string() }); @@ -219,10 +288,19 @@ impl<'context> Elaborator<'context> { } fn resolve_trait_as_type(&mut self, path: Path, args: Vec) -> Type { - let args = vecmap(args, |arg| self.resolve_type_inner(arg)); - - if let Some(t) = self.lookup_trait_or_error(path) { - Type::TraitAsType(t.id, Rc::new(t.name.to_string()), args) + // Fetch information needed from the trait as the closure for resolving all the `args` + // requires exclusive access to `self` + let trait_as_type_info = self + .lookup_trait_or_error(path) + .map(|t| (t.id, Rc::new(t.name.to_string()), t.generics.clone())); + + if let Some((id, name, resolved_generics)) = trait_as_type_info { + assert_eq!(resolved_generics.len(), args.len()); + let generics_with_types = resolved_generics.iter().zip(args); + let args = vecmap(generics_with_types, |(generic, typ)| { + self.resolve_type_inner(typ, &generic.kind) + }); + Type::TraitAsType(id, Rc::new(name.to_string()), args) } else { Type::Error } @@ -251,8 +329,9 @@ impl<'context> Elaborator<'context> { pub fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { if path.segments.len() == 1 { let name = &path.last_segment().0.contents; - if let Some((name, var, _)) = self.find_generic(name) { - return Some(Type::NamedGeneric(var.clone(), name.clone())); + if let Some(generic) = self.find_generic(name) { + let generic = generic.clone(); + return Some(Type::NamedGeneric(generic.type_var, generic.name, generic.kind)); } } @@ -318,9 +397,12 @@ impl<'context> Elaborator<'context> { let constraint = TraitConstraint { typ: self.self_type.clone()?, - trait_generics: Type::from_generics(&the_trait.generics), + trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { + generic.type_var.clone() + })), trait_id, }; + return Some((method, constraint, false)); } } @@ -349,7 +431,9 @@ impl<'context> Elaborator<'context> { the_trait.self_type_typevar.clone(), TypeVariableKind::Normal, ), - trait_generics: Type::from_generics(&the_trait.generics), + trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { + generic.type_var.clone() + })), trait_id, }; return Some((method, constraint, false)); @@ -371,7 +455,7 @@ impl<'context> Elaborator<'context> { } for constraint in self.trait_bounds.clone() { - if let Type::NamedGeneric(_, name) = &constraint.typ { + if let Type::NamedGeneric(_, name, _) = &constraint.typ { // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` if path.segments[0].0.contents != name.as_str() { continue; @@ -1077,7 +1161,7 @@ impl<'context> Elaborator<'context> { }); None } - Type::NamedGeneric(_, _) => { + Type::NamedGeneric(_, _, _) => { let func_meta = self.interner.function_meta( &self.current_function.expect("unexpected method outside a function"), ); @@ -1353,26 +1437,34 @@ impl<'context> Elaborator<'context> { } } - pub fn add_existing_generics(&mut self, names: &UnresolvedGenerics, generics: &Generics) { - assert_eq!(names.len(), generics.len()); + pub fn add_existing_generics( + &mut self, + unresolved_generics: &UnresolvedGenerics, + generics: &Generics, + ) { + assert_eq!(unresolved_generics.len(), generics.len()); - for (name, typevar) in names.iter().zip(generics) { - self.add_existing_generic(&name.0.contents, name.0.span(), typevar.clone()); + for (unresolved_generic, generic) in unresolved_generics.iter().zip(generics) { + self.add_existing_generic(unresolved_generic, unresolved_generic.span(), generic); } } - pub fn add_existing_generic(&mut self, name: &str, span: Span, typevar: TypeVariable) { - // Check for name collisions of this generic - let rc_name = Rc::new(name.to_owned()); + pub fn add_existing_generic( + &mut self, + unresolved_generic: &UnresolvedGeneric, + span: Span, + resolved_generic: &ResolvedGeneric, + ) { + let name = &unresolved_generic.ident().0.contents; - if let Some((_, _, first_span)) = self.find_generic(&rc_name) { + if let Some(generic) = self.find_generic(name) { self.push_err(ResolverError::DuplicateDefinition { - name: name.to_owned(), - first_span: *first_span, + name: name.clone(), + first_span: generic.span, second_span: span, }); } else { - self.generics.push((rc_name, typevar, span)); + self.generics.push(resolved_generic.clone()); } } @@ -1397,7 +1489,7 @@ impl<'context> Elaborator<'context> { | Type::Error | Type::TypeVariable(_, _) | Type::Constant(_) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Quoted(_) | Type::Forall(_, _) => (), @@ -1408,7 +1500,7 @@ impl<'context> Elaborator<'context> { } Type::Array(length, element_type) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(element_type, found); @@ -1433,7 +1525,7 @@ impl<'context> Elaborator<'context> { Type::Struct(struct_type, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name) = generic { + if let Type::NamedGeneric(type_variable, name, _) = generic { if struct_type.borrow().generic_is_numeric(i) { found.insert(name.to_string(), type_variable.clone()); } @@ -1444,7 +1536,7 @@ impl<'context> Elaborator<'context> { } Type::Alias(alias, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name) = generic { + if let Type::NamedGeneric(type_variable, name, _) = generic { if alias.borrow().generic_is_numeric(i) { found.insert(name.to_string(), type_variable.clone()); } @@ -1455,12 +1547,12 @@ impl<'context> Elaborator<'context> { } Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), Type::String(length) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } } Type::FmtString(length, fields) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(fields, found); diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index cccc9c6d545..48f05f287d8 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -86,7 +86,7 @@ fn type_def_as_type( if i != 0 { tokens.push(SpannedToken::new(Token::Comma, span)); } - tokens.push(make_token(generic.borrow().to_string())); + tokens.push(make_token(generic.type_var.borrow().to_string())); } Ok(Value::Code(Rc::new(Tokens(tokens)))) @@ -112,7 +112,7 @@ fn type_def_generics( .generics .iter() .map(|generic| { - let name = SpannedToken::new(Token::Str(generic.borrow().to_string()), span); + let name = SpannedToken::new(Token::Str(generic.type_var.borrow().to_string()), span); Value::Code(Rc::new(Tokens(vec![name]))) }) .collect(); diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 216ed5fc545..9afff6a6055 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -5,7 +5,7 @@ use crate::graph::CrateId; use crate::hir::comptime::{Interpreter, InterpreterError}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; use crate::hir::resolution::errors::ResolverError; -use crate::{Type, TypeVariable}; +use crate::{ResolvedGeneric, Type}; use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; use crate::hir::resolution::{ @@ -33,7 +33,6 @@ use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic, Span}; use std::collections::{BTreeMap, HashMap}; -use std::rc::Rc; use std::vec; #[derive(Default)] @@ -125,7 +124,7 @@ pub struct UnresolvedTraitImpl { pub trait_id: Option, pub impl_id: Option, pub resolved_object_type: Option, - pub resolved_generics: Vec<(Rc, TypeVariable, Span)>, + pub resolved_generics: Vec, // The resolved generic on the trait itself. E.g. it is the `` in // `impl Foo for Bar { ... }` @@ -379,6 +378,7 @@ impl DefCollector { def_collector.items.traits, crate_id, )); + // Must resolve structs before we resolve globals. resolved_module.errors.extend(resolve_structs( context, 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 e7bbe5951d8..3d8a861b009 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -309,11 +309,21 @@ impl<'a> ModCollector<'a> { struct_def: struct_definition, }; + let resolved_generics = context.resolve_generics( + &unresolved.struct_def.generics, + &mut definition_errors, + self.file_id, + ); + // Create the corresponding module for the struct namespace let id = match self.push_child_module(&name, self.file_id, false, false) { - Ok(local_id) => { - context.def_interner.new_struct(&unresolved, krate, local_id, self.file_id) - } + Ok(local_id) => context.def_interner.new_struct( + &unresolved, + resolved_generics, + krate, + local_id, + self.file_id, + ), Err(error) => { definition_errors.push((error.into(), self.file_id)); continue; @@ -357,7 +367,14 @@ impl<'a> ModCollector<'a> { type_alias_def: type_alias, }; - let type_alias_id = context.def_interner.push_type_alias(&unresolved); + let resolved_generics = context.resolve_generics( + &unresolved.type_alias_def.generics, + &mut errors, + self.file_id, + ); + + let type_alias_id = + context.def_interner.push_type_alias(&unresolved, resolved_generics); // Add the type alias to scope so its path can be looked up later let result = self.def_collector.def_map.modules[self.module_id.0] @@ -517,6 +534,9 @@ impl<'a> ModCollector<'a> { } } + let resolved_generics = + context.resolve_generics(&trait_definition.generics, &mut errors, self.file_id); + // And store the TraitId -> TraitType mapping somewhere it is reachable let unresolved = UnresolvedTrait { file_id: self.file_id, @@ -526,7 +546,8 @@ impl<'a> ModCollector<'a> { method_ids, fns_with_default_impl: unresolved_functions, }; - context.def_interner.push_empty_trait(trait_id, &unresolved); + context.def_interner.push_empty_trait(trait_id, &unresolved, resolved_generics); + self.def_collector.items.traits.insert(trait_id, unresolved); } errors diff --git a/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/compiler/noirc_frontend/src/hir/def_collector/errors.rs index edeb463e10d..af2264c3843 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -1,4 +1,4 @@ -use crate::ast::{Ident, Path}; +use crate::ast::{Ident, Path, UnresolvedTypeData}; use crate::hir::resolution::import::PathResolutionError; use noirc_errors::CustomDiagnostic as Diagnostic; @@ -66,6 +66,8 @@ pub enum DefCollectorErrorKind { TraitImplOrphaned { span: Span }, #[error("macro error : {0:?}")] MacroError(MacroError), + #[error("The only supported types of numeric generics are integers, fields, and booleans")] + UnsupportedNumericGenericType { ident: Ident, typ: UnresolvedTypeData }, } /// An error struct that macro processors can return. @@ -228,6 +230,15 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { DefCollectorErrorKind::MacroError(macro_error) => { Diagnostic::simple_error(macro_error.primary_message.clone(), macro_error.secondary_message.clone().unwrap_or_default(), macro_error.span.unwrap_or_default()) }, + DefCollectorErrorKind::UnsupportedNumericGenericType { ident, typ } => { + let name = &ident.0.contents; + + Diagnostic::simple_error( + format!("{name} has a type of {typ}. The only supported types of numeric generics are integers and fields"), + "Unsupported numeric generic type".to_string(), + ident.0.span(), + ) + } } } } diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index 55dc22d6c5d..71fdc6b30d2 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -5,17 +5,21 @@ pub mod resolution; pub mod scope; pub mod type_check; +use crate::ast::UnresolvedGenerics; use crate::debug::DebugInstrumenter; use crate::graph::{CrateGraph, CrateId}; use crate::hir_def::function::FuncMeta; use crate::node_interner::{FuncId, NodeInterner, StructId}; use crate::parser::ParserError; -use crate::ParsedModule; +use crate::{Generics, Kind, ParsedModule, ResolvedGeneric, Type, TypeVariable}; +use def_collector::dc_crate::CompilationError; use def_map::{Contract, CrateDefMap}; -use fm::FileManager; +use fm::{FileId, FileManager}; +use iter_extended::vecmap; use noirc_errors::Location; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; +use std::rc::Rc; use self::def_map::TestFunction; @@ -80,7 +84,7 @@ impl Context<'_, '_> { } } - pub fn parsed_file_results(&self, file_id: fm::FileId) -> (ParsedModule, Vec) { + pub fn parsed_file_results(&self, file_id: FileId) -> (ParsedModule, Vec) { self.parsed_files.get(&file_id).expect("noir file wasn't parsed").clone() } @@ -256,4 +260,34 @@ impl Context<'_, '_> { pub fn module(&self, module_id: def_map::ModuleId) -> &def_map::ModuleData { module_id.module(&self.def_maps) } + + /// Generics need to be resolved before elaboration to distinguish + /// between normal and numeric generics. + /// This method is expected to be used during definition collection. + /// Each result is returned in a list rather than returned as a single result as to allow + /// definition collection to provide an error for each ill-formed numeric generic. + pub(crate) fn resolve_generics( + &mut self, + generics: &UnresolvedGenerics, + errors: &mut Vec<(CompilationError, FileId)>, + file_id: FileId, + ) -> Generics { + vecmap(generics, |generic| { + // Map the generic to a fresh type variable + let id = self.def_interner.next_type_variable_id(); + let type_var = TypeVariable::unbound(id); + let ident = generic.ident(); + let span = ident.0.span(); + + // Check for name collisions of this generic + let name = Rc::new(ident.0.contents.clone()); + + let kind = generic.kind().unwrap_or_else(|err| { + errors.push((err.into(), file_id)); + Kind::Numeric(Box::new(Type::Error)) + }); + + ResolvedGeneric { name, type_var, kind, span } + }) + } } diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index dad0149ec73..bf6de746791 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -98,6 +98,12 @@ pub enum ResolverError { NoPredicatesAttributeOnUnconstrained { ident: Ident }, #[error("#[fold] attribute is only allowed on constrained functions")] FoldAttributeOnUnconstrained { ident: Ident }, + #[error("The only supported types of numeric generics are integers, fields, and booleans")] + UnsupportedNumericGenericType { ident: Ident, typ: Type }, + #[error("Numeric generics should be explicit")] + UseExplicitNumericGeneric { ident: Ident }, + #[error("expected type, found numeric generic parameter")] + NumericGenericUsedForType { name: String, span: Span }, #[error("Invalid array length construction")] ArrayLengthInterpreter { error: InterpreterError }, #[error("The unquote operator '$' can only be used within a quote expression")] @@ -400,6 +406,31 @@ impl<'a> From<&'a ResolverError> for Diagnostic { diag.add_note("The `#[fold]` attribute specifies whether a constrained function should be treated as a separate circuit rather than inlined into the program entry point".to_owned()); diag } + ResolverError::UnsupportedNumericGenericType { ident , typ } => { + let name = &ident.0.contents; + + Diagnostic::simple_error( + format!("{name} has a type of {typ}. The only supported types of numeric generics are integers, fields, and booleans."), + "Unsupported numeric generic type".to_string(), + ident.0.span(), + ) + } + ResolverError::UseExplicitNumericGeneric { ident } => { + let name = &ident.0.contents; + + Diagnostic::simple_warning( + String::from("Noir now supports explicit numeric generics. Support for implicit numeric generics will be removed in the following release."), + format!("Numeric generic `{name}` should now be specified with `let {name}: `"), + ident.0.span(), + ) + } + ResolverError::NumericGenericUsedForType { name, span } => { + Diagnostic::simple_error( + format!("expected type, found numeric generic parameter {name}"), + String::from("not a type"), + *span, + ) + } ResolverError::ArrayLengthInterpreter { error } => Diagnostic::from(error), ResolverError::UnquoteUsedOutsideQuote { span } => { Diagnostic::simple_error( diff --git a/compiler/noirc_frontend/src/hir/resolution/functions.rs b/compiler/noirc_frontend/src/hir/resolution/functions.rs index e63de9b9173..fe46796ed24 100644 --- a/compiler/noirc_frontend/src/hir/resolution/functions.rs +++ b/compiler/noirc_frontend/src/hir/resolution/functions.rs @@ -1,8 +1,7 @@ -use std::{collections::BTreeMap, rc::Rc}; +use std::collections::BTreeMap; use fm::FileId; use iter_extended::vecmap; -use noirc_errors::Span; use crate::{ graph::CrateId, @@ -11,10 +10,10 @@ use crate::{ def_map::{CrateDefMap, ModuleId}, }, node_interner::{FuncId, NodeInterner, TraitImplId}, - Type, TypeVariable, + ResolvedGeneric, Type, }; -use super::{path_resolver::StandardPathResolver, resolver::Resolver}; +use super::{path_resolver::StandardPathResolver, Resolver}; #[allow(clippy::too_many_arguments)] pub(crate) fn resolve_function_set( @@ -24,7 +23,7 @@ pub(crate) fn resolve_function_set( mut unresolved_functions: UnresolvedFunctions, self_type: Option, trait_impl_id: Option, - impl_generics: Vec<(Rc, TypeVariable, Span)>, + impl_generics: Vec, errors: &mut Vec<(CompilationError, FileId)>, ) -> Vec<(FileId, FuncId)> { let file_id = unresolved_functions.file_id; diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 5706e62e193..ebb3f7290ea 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -32,8 +32,9 @@ use crate::ast::{ ArrayLiteral, BinaryOpKind, BlockExpression, Expression, ExpressionKind, ForRange, FunctionDefinition, FunctionKind, FunctionReturnType, Ident, ItemVisibility, LValue, LetStatement, Literal, NoirFunction, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, - Statement, StatementKind, TraitBound, UnaryOp, UnresolvedGenerics, UnresolvedTraitConstraint, - UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, Visibility, ERROR_IDENT, + Statement, StatementKind, TraitBound, UnaryOp, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, + Visibility, ERROR_IDENT, }; use crate::graph::CrateId; use crate::hir::def_map::{ModuleDefId, TryFromModuleDefId, MAIN_FUNCTION}; @@ -47,7 +48,10 @@ use crate::node_interner::{ DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, NodeInterner, StmtId, StructId, TraitId, TraitImplId, TraitMethodId, TypeAliasId, }; -use crate::{Generics, Shared, StructType, Type, TypeAlias, TypeVariable, TypeVariableKind}; +use crate::{ + GenericTypeVars, Generics, Kind, ResolvedGeneric, Shared, StructType, Type, TypeAlias, + TypeVariable, TypeVariableKind, +}; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span, Spanned}; @@ -131,7 +135,7 @@ pub struct Resolver<'a> { /// unique type variables if we're resolving a struct. Empty otherwise. /// This is a Vec rather than a map to preserve the order a functions generics /// were declared in. - generics: Vec<(Rc, TypeVariable, Span)>, + generics: Vec, /// When resolving lambda expressions, we need to keep track of the variables /// that are captured. We do this in order to create the hidden environment @@ -223,7 +227,8 @@ impl<'a> Resolver<'a> { let mut new_generic_ident: Ident = format!("T{}_impl_{}", func_id, path.as_string()).into(); let mut new_generic_path = Path::from_ident(new_generic_ident.clone()); - while impl_trait_generics.contains(&new_generic_ident) + let new_generic = UnresolvedGeneric::from(new_generic_ident.clone()); + while impl_trait_generics.contains(&new_generic) || self.lookup_generic_or_global_type(&new_generic_path).is_some() { new_generic_ident = @@ -231,7 +236,7 @@ impl<'a> Resolver<'a> { new_generic_path = Path::from_ident(new_generic_ident.clone()); counter += 1; } - impl_trait_generics.insert(new_generic_ident.clone()); + impl_trait_generics.insert(UnresolvedGeneric::from(new_generic_ident.clone())); let is_synthesized = true; let new_generic_type_data = @@ -249,7 +254,7 @@ impl<'a> Resolver<'a> { }; parameter.typ.typ = new_generic_type_data; - func.def.generics.push(new_generic_ident); + func.def.generics.push(new_generic_ident.into()); func.def.where_clause.push(new_trait_constraint); } } @@ -591,7 +596,7 @@ impl<'a> Resolver<'a> { let env = Box::new(self.resolve_type_inner(*env)); match *env { - Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _) => { + Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { Type::Function(args, ret, env) } _ => { @@ -621,8 +626,8 @@ impl<'a> Resolver<'a> { resolved_type } - fn find_generic(&self, target_name: &str) -> Option<&(Rc, TypeVariable, Span)> { - self.generics.iter().find(|(name, _, _)| name.as_ref() == target_name) + fn find_generic(&self, target_name: &str) -> Option<&ResolvedGeneric> { + self.generics.iter().find(|generic| generic.name.as_ref() == target_name) } fn resolve_named_type(&mut self, path: Path, args: Vec) -> Type { @@ -747,9 +752,15 @@ impl<'a> Resolver<'a> { fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { if path.segments.len() == 1 { let name = &path.last_segment().0.contents; - if let Some((name, var, _)) = self.find_generic(name) { - return Some(Type::NamedGeneric(var.clone(), name.clone())); - } + if let Some(generic) = self.find_generic(name) { + // We always insert a `TypeKind::Normal` as we do not support explicit numeric generics + // in the resolver + return Some(Type::NamedGeneric( + generic.type_var.clone(), + generic.name.clone(), + Kind::Normal, + )); + }; } // If we cannot find a local generic of the same name, try to look up a global @@ -848,14 +859,14 @@ impl<'a> Resolver<'a> { /// Return the current generics. /// Needed to keep referring to the same type variables across many /// methods in a single impl. - pub fn get_generics(&self) -> &[(Rc, TypeVariable, Span)] { + pub fn get_generics(&self) -> &[ResolvedGeneric] { &self.generics } /// Set the current generics that are in scope. /// Unlike add_generics, this function will not create any new type variables, /// opting to reuse the existing ones it is directly given. - pub fn set_generics(&mut self, generics: Vec<(Rc, TypeVariable, Span)>) { + pub fn set_generics(&mut self, generics: Vec) { self.generics = generics; } @@ -875,48 +886,79 @@ impl<'a> Resolver<'a> { // Map the generic to a fresh type variable let id = self.interner.next_type_variable_id(); let typevar = TypeVariable::unbound(id); - let span = generic.0.span(); + let ident = generic.ident(); + let span = ident.0.span(); // Check for name collisions of this generic - let name = Rc::new(generic.0.contents.clone()); + let name = Rc::new(ident.0.contents.clone()); - if let Some((_, _, first_span)) = self.find_generic(&name) { + let resolved_generic = ResolvedGeneric { + name: name.clone(), + type_var: typevar, + // We only support numeric generics in the elaborator + kind: Kind::Normal, + span, + }; + if let Some(generic) = self.find_generic(&name) { self.errors.push(ResolverError::DuplicateDefinition { - name: generic.0.contents.clone(), - first_span: *first_span, + name: ident.0.contents.clone(), + first_span: generic.span, second_span: span, }); } else { - self.generics.push((name, typevar.clone(), span)); + self.generics.push(resolved_generic.clone()); } - typevar + resolved_generic }) } /// Add the given existing generics to scope. /// This is useful for adding the same generics to many items. E.g. apply impl generics /// to each function in the impl or trait generics to each item in the trait. - pub fn add_existing_generics(&mut self, names: &UnresolvedGenerics, generics: &Generics) { - assert_eq!(names.len(), generics.len()); + pub fn add_existing_generics( + &mut self, + unresolved_generics: &UnresolvedGenerics, + generics: &GenericTypeVars, + ) { + assert_eq!(unresolved_generics.len(), generics.len()); - for (name, typevar) in names.iter().zip(generics) { - self.add_existing_generic(&name.0.contents, name.0.span(), typevar.clone()); + for (unresolved_generic, typevar) in unresolved_generics.iter().zip(generics) { + self.add_existing_generic( + unresolved_generic, + unresolved_generic.span(), + typevar.clone(), + ); } } - pub fn add_existing_generic(&mut self, name: &str, span: Span, typevar: TypeVariable) { + pub fn add_existing_generic( + &mut self, + unresolved_generic: &UnresolvedGeneric, + span: Span, + typevar: TypeVariable, + ) { + let name = &unresolved_generic.ident().0.contents; + // Check for name collisions of this generic - let rc_name = Rc::new(name.to_owned()); + let rc_name = Rc::new(name.clone()); - if let Some((_, _, first_span)) = self.find_generic(&rc_name) { + if let Some(generic) = self.find_generic(&rc_name) { self.errors.push(ResolverError::DuplicateDefinition { - name: name.to_owned(), - first_span: *first_span, + name: name.clone(), + first_span: generic.span, second_span: span, }); } else { - self.generics.push((rc_name, typevar, span)); + let resolved_generic = ResolvedGeneric { + name: rc_name, + type_var: typevar.clone(), + kind: unresolved_generic + .kind() + .expect("ICE: Deprecated code should only support normal kinds"), + span, + }; + self.generics.push(resolved_generic); } } @@ -992,7 +1034,7 @@ impl<'a> Resolver<'a> { // indicate we should code generate in the same way. Thus, we unify the attributes into one flag here. let has_inline_attribute = has_no_predicates_attribute || should_fold; - let generics = vecmap(&self.generics, |(_, typevar, _)| typevar.clone()); + let generics = vecmap(&self.generics, |generic| generic.type_var.clone()); let mut parameters = vec![]; let mut parameter_types = vec![]; @@ -1053,8 +1095,8 @@ impl<'a> Resolver<'a> { let direct_generics = func.def.generics.iter(); let direct_generics = direct_generics - .filter_map(|generic| self.find_generic(&generic.0.contents)) - .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) + .filter_map(|generic| self.find_generic(&generic.ident().0.contents)) + .map(|ResolvedGeneric { name, type_var, .. }| (name.clone(), type_var.clone())) .collect(); FuncMeta { @@ -1107,6 +1149,7 @@ impl<'a> Resolver<'a> { !func.def.is_unconstrained } + // TODO(https://github.com/noir-lang/noir/issues/5156): Remove this method in favor of explicit numeric generics fn declare_numeric_generics(&mut self, params: &[Type], return_type: &Type) { if self.generics.is_empty() { return; @@ -1119,12 +1162,12 @@ impl<'a> Resolver<'a> { // We can fail to find the generic in self.generics if it is an implicit one created // by the compiler. This can happen when, e.g. eliding array lengths using the slice // syntax [T]. - if let Some((name, _, span)) = - self.generics.iter().find(|(name, _, _)| name.as_ref() == &name_to_find) + if let Some(ResolvedGeneric { name, span, .. }) = + self.generics.iter().find(|generic| generic.name.as_ref() == &name_to_find) { let ident = Ident::new(name.to_string(), *span); let definition = DefinitionKind::GenericType(type_variable); - self.add_variable_decl_inner(ident, false, false, false, definition); + self.add_variable_decl_inner(ident.clone(), false, false, false, definition); } } } @@ -1150,7 +1193,7 @@ impl<'a> Resolver<'a> { | Type::Error | Type::TypeVariable(_, _) | Type::Constant(_) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Quoted(_) | Type::Forall(_, _) => (), @@ -1161,7 +1204,7 @@ impl<'a> Resolver<'a> { } Type::Array(length, element_type) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(element_type, found); @@ -1186,7 +1229,7 @@ impl<'a> Resolver<'a> { Type::Struct(struct_type, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name) = generic { + if let Type::NamedGeneric(type_variable, name, _) = generic { if struct_type.borrow().generic_is_numeric(i) { found.insert(name.to_string(), type_variable.clone()); } @@ -1197,7 +1240,7 @@ impl<'a> Resolver<'a> { } Type::Alias(alias, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name) = generic { + if let Type::NamedGeneric(type_variable, name, _) = generic { if alias.borrow().generic_is_numeric(i) { found.insert(name.to_string(), type_variable.clone()); } @@ -1208,12 +1251,12 @@ impl<'a> Resolver<'a> { } Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), Type::String(length) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } } Type::FmtString(length, fields) => { - if let Type::NamedGeneric(type_variable, name) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(fields, found); @@ -1874,7 +1917,9 @@ impl<'a> Resolver<'a> { let constraint = TraitConstraint { typ: self.self_type.clone()?, - trait_generics: Type::from_generics(&the_trait.generics), + trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { + generic.type_var.clone() + })), trait_id, }; return Some((method, constraint, false)); @@ -1902,7 +1947,9 @@ impl<'a> Resolver<'a> { the_trait.self_type_typevar.clone(), TypeVariableKind::Normal, ), - trait_generics: Type::from_generics(&the_trait.generics), + trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { + generic.type_var.clone() + })), trait_id, }; return Some((method, constraint, false)); diff --git a/compiler/noirc_frontend/src/hir/resolution/traits.rs b/compiler/noirc_frontend/src/hir/resolution/traits.rs index 4c360731711..a6b732439b0 100644 --- a/compiler/noirc_frontend/src/hir/resolution/traits.rs +++ b/compiler/noirc_frontend/src/hir/resolution/traits.rs @@ -4,7 +4,7 @@ use fm::FileId; use iter_extended::vecmap; use noirc_errors::Location; -use crate::ast::{ItemVisibility, Path, TraitItem}; +use crate::ast::{Ident, ItemVisibility, Path, TraitItem, UnresolvedGeneric}; use crate::{ graph::CrateId, hir::{ @@ -17,7 +17,7 @@ use crate::{ }, hir_def::traits::{TraitConstant, TraitFunction, TraitImpl, TraitType}, node_interner::{FuncId, NodeInterner, TraitId}, - Generics, Shared, Type, TypeVariable, TypeVariableKind, + GenericTypeVars, Shared, Type, TypeVariableKind, }; use super::{ @@ -36,14 +36,18 @@ pub(crate) fn resolve_traits( crate_id: CrateId, ) -> Vec<(CompilationError, FileId)> { for (trait_id, unresolved_trait) in &traits { - context.def_interner.push_empty_trait(*trait_id, unresolved_trait); + context.def_interner.push_empty_trait(*trait_id, unresolved_trait, vec![]); } let mut all_errors = Vec::new(); for (trait_id, unresolved_trait) in traits { - let generics = vecmap(&unresolved_trait.trait_def.generics, |_| { - TypeVariable::unbound(context.def_interner.next_type_variable_id()) - }); + let file_id = context.def_maps[&crate_id].file_id(unresolved_trait.module_id); + let generics = context.resolve_generics( + &unresolved_trait.trait_def.generics, + &mut all_errors, + file_id, + ); + let generic_type_vars = generics.iter().map(|generic| generic.type_var.clone()).collect(); // Resolve order // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) @@ -51,8 +55,13 @@ pub(crate) fn resolve_traits( // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) let _ = resolve_trait_constants(context, crate_id, &unresolved_trait); // 3. Trait Methods - let (methods, errors) = - resolve_trait_methods(context, trait_id, crate_id, &unresolved_trait, &generics); + let (methods, errors) = resolve_trait_methods( + context, + trait_id, + crate_id, + &unresolved_trait, + &generic_type_vars, + ); all_errors.extend(errors); @@ -93,7 +102,7 @@ fn resolve_trait_methods( trait_id: TraitId, crate_id: CrateId, unresolved_trait: &UnresolvedTrait, - trait_generics: &Generics, + trait_generics: &GenericTypeVars, ) -> (Vec, Vec<(CompilationError, FileId)>) { let interner = &mut context.def_interner; let def_maps = &mut context.def_maps; @@ -126,7 +135,11 @@ fn resolve_trait_methods( resolver.add_generics(generics); resolver.add_existing_generics(&unresolved_trait.trait_def.generics, trait_generics); - resolver.add_existing_generic("Self", name_span, self_typevar); + resolver.add_existing_generic( + &UnresolvedGeneric::Variable(Ident::from("Self")), + name_span, + self_typevar, + ); resolver.set_self_type(Some(self_type.clone())); let func_id = unresolved_trait.method_ids[&name.0.contents]; @@ -143,7 +156,7 @@ fn resolve_trait_methods( let arguments = vecmap(parameters, |param| resolver.resolve_type(param.1.clone())); let return_type = resolver.resolve_type(return_type.get_type().into_owned()); - let generics = vecmap(resolver.get_generics(), |(_, type_var, _)| type_var.clone()); + let generics = vecmap(resolver.get_generics(), |generic| generic.type_var.clone()); let default_impl_list: Vec<_> = unresolved_trait .fns_with_default_impl @@ -464,7 +477,7 @@ pub(crate) fn resolve_trait_impls( methods: vecmap(&impl_methods, |(_, func_id)| *func_id), }); - let impl_generics = vecmap(impl_generics, |(_, type_variable, _)| type_variable); + let impl_generics = vecmap(impl_generics, |generic| generic.type_var); if let Err((prev_span, prev_file)) = interner.add_trait_implementation( self_type.clone(), diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index d9d021aee3f..f18e8a9e843 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -40,6 +40,8 @@ pub enum TypeCheckError { TypeMismatch { expected_typ: String, expr_typ: String, expr_span: Span }, #[error("Expected type {expected} is not the same as {actual}")] TypeMismatchWithSource { expected: Type, actual: Type, span: Span, source: Source }, + #[error("Expected type {expected_kind:?} is not the same as {expr_kind:?}")] + TypeKindMismatch { expected_kind: String, expr_kind: String, expr_span: Span }, #[error("Expected {expected:?} found {found:?}")] ArityMisMatch { expected: usize, found: usize, span: Span }, #[error("Return type in a function cannot be public")] @@ -178,6 +180,13 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { *expr_span, ) } + TypeCheckError::TypeKindMismatch { expected_kind, expr_kind, expr_span } => { + Diagnostic::simple_error( + format!("Expected kind {expected_kind}, found kind {expr_kind}"), + String::new(), + *expr_span, + ) + } TypeCheckError::TraitMethodParameterTypeMismatch { method_name, expected_typ, actual_typ, parameter_index, parameter_span } => { Diagnostic::simple_error( format!("Parameter #{parameter_index} of method `{method_name}` must be of type {expected_typ}, not {actual_typ}"), diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index 4ded04ec2a4..77861a6d8f8 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -404,8 +404,8 @@ impl<'interner> TypeChecker<'interner> { for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { // Avoid binding t = t - if !arg.occurs(param.id()) { - bindings.insert(param.id(), (param.clone(), arg.clone())); + if !arg.occurs(param.type_var.id()) { + bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); } } @@ -1025,7 +1025,7 @@ impl<'interner> TypeChecker<'interner> { }); None } - Type::NamedGeneric(_, _) => { + Type::NamedGeneric(_, _, _) => { let func_meta = self.interner.function_meta( &self.current_function.expect("unexpected method outside a function"), ); diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 98e1cd9c72a..b2d15911147 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -22,7 +22,7 @@ use crate::{ traits::TraitConstraint, }, node_interner::{ExprId, FuncId, GlobalId, NodeInterner}, - Type, TypeBindings, + Kind, Type, TypeBindings, }; pub use self::errors::Source; @@ -263,7 +263,7 @@ pub(crate) fn check_trait_impl_method_matches_declaration( // Substitute each generic on the trait with the corresponding generic on the impl for (generic, arg) in trait_info.generics.iter().zip(&impl_.trait_generics) { - bindings.insert(generic.id(), (generic.clone(), arg.clone())); + bindings.insert(generic.type_var.id(), (generic.type_var.clone(), arg.clone())); } // If this is None, the trait does not have the corresponding function. @@ -284,7 +284,7 @@ pub(crate) fn check_trait_impl_method_matches_declaration( for ((_, trait_fn_generic), (name, impl_fn_generic)) in trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) { - let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone()); + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), Kind::Normal); bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); } diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index 53eabe21081..d11d3132212 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -9,7 +9,7 @@ use super::traits::TraitConstraint; use crate::ast::{FunctionKind, FunctionReturnType, Visibility}; use crate::macros_api::BlockExpression; use crate::node_interner::{ExprId, NodeInterner, TraitImplId}; -use crate::{Type, TypeVariable}; +use crate::{ResolvedGeneric, Type, TypeVariable}; /// A Hir function is a block expression /// with a list of statements @@ -118,7 +118,7 @@ pub struct FuncMeta { /// from outer scopes, such as those introduced by an impl. /// This is stored when the FuncMeta is first created to later be used to set the current /// generics when the function's body is later resolved. - pub all_generics: Vec<(Rc, TypeVariable, Span)>, + pub all_generics: Vec, pub location: Location, diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 86d1fafd502..8733f4b2de1 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -82,7 +82,7 @@ pub enum Type { /// NamedGenerics are the 'T' or 'U' in a user-defined generic function /// like `fn foo(...) {}`. Unlike TypeVariables, they cannot be bound over. - NamedGeneric(TypeVariable, Rc), + NamedGeneric(TypeVariable, Rc, Kind), /// A functions with arguments, a return type and environment. /// the environment should be `Unit` by default, @@ -98,7 +98,7 @@ pub enum Type { /// but it makes handling them both easier. The TypeVariableId should /// never be bound over during type checking, but during monomorphization it /// will be and thus needs the full TypeVariable link. - Forall(Generics, Box), + Forall(GenericTypeVars, Box), /// A type-level integer. Included to let an Array's size type variable /// bind to an integer without special checks to bind it to a non-type. @@ -142,7 +142,7 @@ impl Type { | Type::Unit | Type::TypeVariable(_, _) | Type::TraitAsType(..) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Function(_, _, _) | Type::MutableReference(_) | Type::Forall(_, _) @@ -187,6 +187,27 @@ impl Type { } } +/// A Kind is the type of a Type. These are used since only certain kinds of types are allowed in +/// certain positions. +/// +/// For example, the type of a struct field or a function parameter is expected to be +/// a type of kind * (represented here as `Normal`). Types used in positions where a number +/// is expected (such as in an array length position) are expected to be of kind `Kind::Numeric`. +#[derive(PartialEq, Eq, Clone, Hash, Debug)] +pub enum Kind { + Normal, + Numeric(Box), +} + +impl std::fmt::Display for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Kind::Normal => write!(f, "normal"), + Kind::Numeric(typ) => write!(f, "numeric {}", typ), + } + } +} + #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] pub enum QuotedType { Expr, @@ -222,7 +243,22 @@ pub struct StructType { } /// Corresponds to generic lists such as `` in the source program. -pub type Generics = Vec; +/// Used mainly for resolved types which no longer need information such +/// as names or kinds. +pub type GenericTypeVars = Vec; + +/// Corresponds to generic lists such as `` with additional +/// information gathered during name resolution that is necessary +/// correctly resolving types. +pub type Generics = Vec; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedGeneric { + pub name: Rc, + pub type_var: TypeVariable, + pub kind: Kind, + pub span: Span, +} impl std::hash::Hash for StructType { fn hash(&self, state: &mut H) { @@ -271,7 +307,7 @@ impl StructType { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.id(), (old.clone(), new.clone()))) + .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) .collect(); (typ.substitute(&substitutions), i) @@ -287,7 +323,7 @@ impl StructType { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.id(), (old.clone(), new.clone()))) + .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) .collect(); vecmap(&self.fields, |(name, typ)| { @@ -310,11 +346,19 @@ impl StructType { self.fields.iter().map(|(name, _)| name.clone()).collect() } + /// Search the fields of a struct for any types with a `TypeKind::Numeric` + pub fn find_numeric_generics_in_fields(&self, found_names: &mut Vec) { + for (_, field) in self.fields.iter() { + field.find_numeric_type_vars(found_names); + } + } + /// True if the given index is the same index as a generic type of this struct /// which is expected to be a numeric generic. /// This is needed because we infer type kinds in Noir and don't have extensive kind checking. + /// TODO(https://github.com/noir-lang/noir/issues/5156): This is outdated and we should remove this implicit searching for numeric generics pub fn generic_is_numeric(&self, index_of_generic: usize) -> bool { - let target_id = self.generics[index_of_generic].0; + let target_id = self.generics[index_of_generic].type_var.id(); self.fields.iter().any(|(_, field)| field.contains_numeric_typevar(target_id)) } @@ -383,7 +427,7 @@ impl TypeAlias { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.id(), (old.clone(), new.clone()))) + .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) .collect(); self.typ.substitute(&substitutions) @@ -393,7 +437,7 @@ impl TypeAlias { /// which is expected to be a numeric generic. /// This is needed because we infer type kinds in Noir and don't have extensive kind checking. pub fn generic_is_numeric(&self, index_of_generic: usize) -> bool { - let target_id = self.generics[index_of_generic].0; + let target_id = self.generics[index_of_generic].type_var.id(); self.typ.contains_numeric_typevar(target_id) } } @@ -641,7 +685,7 @@ impl Type { fn contains_numeric_typevar(&self, target_id: TypeVariableId) -> bool { // True if the given type is a NamedGeneric with the target_id let named_generic_id_matches_target = |typ: &Type| { - if let Type::NamedGeneric(type_variable, _) = typ { + if let Type::NamedGeneric(type_variable, _, _) = typ { match &*type_variable.borrow() { TypeBinding::Bound(_) => { unreachable!("Named generics should not be bound until monomorphization") @@ -661,7 +705,7 @@ impl Type { | Type::Error | Type::TypeVariable(_, _) | Type::Constant(_) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Forall(_, _) | Type::Quoted(_) => false, @@ -705,6 +749,85 @@ impl Type { } } + /// TODO(https://github.com/noir-lang/noir/issues/5156): Remove with explicit numeric generics + pub fn find_numeric_type_vars(&self, found_names: &mut Vec) { + // Return whether the named generic has a TypeKind::Numeric and save its name + let named_generic_is_numeric = |typ: &Type, found_names: &mut Vec| { + if let Type::NamedGeneric(_, name, Kind::Numeric { .. }) = typ { + found_names.push(name.to_string()); + true + } else { + false + } + }; + + match self { + Type::FieldElement + | Type::Integer(_, _) + | Type::Bool + | Type::Unit + | Type::Error + | Type::Constant(_) + | Type::Forall(_, _) + | Type::Quoted(_) => {} + + Type::TypeVariable(type_var, _) => { + if let TypeBinding::Bound(typ) = &*type_var.borrow() { + named_generic_is_numeric(typ, found_names); + } + } + + Type::NamedGeneric(_, _, _) => { + named_generic_is_numeric(self, found_names); + } + + Type::TraitAsType(_, _, args) => { + for arg in args.iter() { + arg.find_numeric_type_vars(found_names); + } + } + Type::Array(length, elem) => { + elem.find_numeric_type_vars(found_names); + named_generic_is_numeric(length, found_names); + } + Type::Slice(elem) => elem.find_numeric_type_vars(found_names), + Type::Tuple(fields) => { + for field in fields.iter() { + field.find_numeric_type_vars(found_names); + } + } + Type::Function(parameters, return_type, env) => { + for parameter in parameters.iter() { + parameter.find_numeric_type_vars(found_names); + } + return_type.find_numeric_type_vars(found_names); + env.find_numeric_type_vars(found_names); + } + Type::Struct(_, generics) => { + for generic in generics.iter() { + if !named_generic_is_numeric(generic, found_names) { + generic.find_numeric_type_vars(found_names); + } + } + } + Type::Alias(_, generics) => { + for generic in generics.iter() { + if !named_generic_is_numeric(generic, found_names) { + generic.find_numeric_type_vars(found_names); + } + } + } + Type::MutableReference(element) => element.find_numeric_type_vars(found_names), + Type::String(length) => { + named_generic_is_numeric(length, found_names); + } + Type::FmtString(length, elements) => { + elements.find_numeric_type_vars(found_names); + named_generic_is_numeric(length, found_names); + } + } + } + /// True if this type can be used as a parameter to `main` or a contract function. /// This is only false for unsized types like slices or slices that do not make sense /// as a program input such as named generics or mutable references. @@ -725,7 +848,7 @@ impl Type { Type::FmtString(_, _) | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Function(_, _, _) | Type::MutableReference(_) | Type::Forall(_, _) @@ -767,7 +890,7 @@ impl Type { | Type::Unit | Type::Constant(_) | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Error => true, Type::FmtString(_, _) @@ -810,7 +933,7 @@ impl Type { | Type::Constant(_) | Type::Slice(_) | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _) + | Type::NamedGeneric(_, _, _) | Type::Function(_, _, _) | Type::FmtString(_, _) | Type::Error => true, @@ -847,7 +970,7 @@ impl Type { pub fn generic_count(&self) -> usize { match self { Type::Forall(generics, _) => generics.len(), - Type::TypeVariable(type_variable, _) | Type::NamedGeneric(type_variable, _) => { + Type::TypeVariable(type_variable, _) | Type::NamedGeneric(type_variable, _, _) => { match &*type_variable.borrow() { TypeBinding::Bound(binding) => binding.generic_count(), TypeBinding::Unbound(_) => 0, @@ -876,12 +999,42 @@ impl Type { /// Return the generics and type within this `Type::Forall`. /// Panics if `self` is not `Type::Forall` - pub fn unwrap_forall(&self) -> (Cow, &Type) { + pub fn unwrap_forall(&self) -> (Cow, &Type) { match self { Type::Forall(generics, typ) => (Cow::Borrowed(generics), typ.as_ref()), - other => (Cow::Owned(Generics::new()), other), + other => (Cow::Owned(GenericTypeVars::new()), other), } } + + // TODO(https://github.com/noir-lang/noir/issues/5156): Bring back this method when we remove implicit numeric generics + // It has been commented out as to not trigger clippy for an unused method + // pub(crate) fn kind(&self) -> Kind { + // match self { + // Type::NamedGeneric(_, _, kind) => kind.clone(), + // Type::Constant(_) => Kind::Numeric(Box::new(Type::Integer( + // Signedness::Unsigned, + // IntegerBitSize::ThirtyTwo, + // ))), + // Type::FieldElement + // | Type::Array(_, _) + // | Type::Slice(_) + // | Type::Integer(_, _) + // | Type::Bool + // | Type::String(_) + // | Type::FmtString(_, _) + // | Type::Unit + // | Type::Tuple(_) + // | Type::Struct(_, _) + // | Type::Alias(_, _) + // | Type::TypeVariable(_, _) + // | Type::TraitAsType(_, _, _) + // | Type::Function(_, _, _) + // | Type::MutableReference(_) + // | Type::Forall(_, _) + // | Type::Quoted(_) + // | Type::Error => Kind::Normal, + // } + // } } impl std::fmt::Display for Type { @@ -961,7 +1114,7 @@ impl std::fmt::Display for Type { } Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name) => match &*binding.borrow() { + Type::NamedGeneric(binding, name, _) => match &*binding.borrow() { TypeBinding::Bound(binding) => binding.fmt(f), TypeBinding::Unbound(_) if name.is_empty() => write!(f, "_"), TypeBinding::Unbound(_) => write!(f, "{name}"), @@ -1213,7 +1366,7 @@ impl Type { fn get_inner_type_variable(&self) -> Option> { match self { - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _) => Some(var.1.clone()), + Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => Some(var.1.clone()), _ => None, } } @@ -1324,7 +1477,7 @@ impl Type { } } - (NamedGeneric(binding, _), other) | (other, NamedGeneric(binding, _)) + (NamedGeneric(binding, _, _), other) | (other, NamedGeneric(binding, _, _)) if !binding.borrow().is_unbound() => { if let TypeBinding::Bound(link) = &*binding.borrow() { @@ -1334,7 +1487,7 @@ impl Type { } } - (NamedGeneric(binding_a, name_a), NamedGeneric(binding_b, name_b)) => { + (NamedGeneric(binding_a, name_a, _), NamedGeneric(binding_b, name_b, _)) => { // Bound NamedGenerics are caught by the check above assert!(binding_a.borrow().is_unbound()); assert!(binding_b.borrow().is_unbound()); @@ -1661,7 +1814,7 @@ impl Type { let fields = fields.substitute_helper(type_bindings, substitute_bound_typevars); Type::FmtString(Box::new(size), Box::new(fields)) } - Type::NamedGeneric(binding, _) | Type::TypeVariable(binding, _) => { + Type::NamedGeneric(binding, _, _) | Type::TypeVariable(binding, _) => { substitute_binding(binding) } // Do not substitute_helper fields, it can lead to infinite recursion @@ -1739,7 +1892,7 @@ impl Type { generic_args.iter().any(|arg| arg.occurs(target_id)) } Type::Tuple(fields) => fields.iter().any(|field| field.occurs(target_id)), - Type::NamedGeneric(binding, _) | Type::TypeVariable(binding, _) => { + Type::NamedGeneric(binding, _, _) | Type::TypeVariable(binding, _) => { match &*binding.borrow() { TypeBinding::Bound(binding) => binding.occurs(target_id), TypeBinding::Unbound(id) => *id == target_id, @@ -1794,7 +1947,7 @@ impl Type { def.borrow().get_type(args).follow_bindings() } Tuple(args) => Tuple(vecmap(args, |arg| arg.follow_bindings())), - TypeVariable(var, _) | NamedGeneric(var, _) => { + TypeVariable(var, _) | NamedGeneric(var, _, _) => { if let TypeBinding::Bound(typ) = &*var.borrow() { return typ.follow_bindings(); } @@ -1823,7 +1976,7 @@ impl Type { } } - pub fn from_generics(generics: &Generics) -> Vec { + pub fn from_generics(generics: &GenericTypeVars) -> Vec { vecmap(generics, |var| Type::TypeVariable(var.clone(), TypeVariableKind::Normal)) } } @@ -2020,7 +2173,14 @@ impl std::fmt::Debug for Type { } Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name) => write!(f, "{}{:?}", name, binding), + Type::NamedGeneric(binding, name, kind) => match kind { + Kind::Normal => { + write!(f, "{} -> {:?}", name, binding) + } + Kind::Numeric(typ) => { + write!(f, "({} : {}) -> {:?}", name, typ, binding) + } + }, Type::Constant(x) => x.fmt(f), Type::Forall(typevars, typ) => { let typevars = vecmap(typevars, |var| format!("{:?}", var)); diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index a95fc0e7ace..7eb2afe804a 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -945,7 +945,7 @@ impl<'interner> Monomorphizer<'interner> { HirType::TraitAsType(..) => { unreachable!("All TraitAsType should be replaced before calling convert_type"); } - HirType::NamedGeneric(binding, _) => { + HirType::NamedGeneric(binding, _, _) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { return Self::convert_type(binding, location); } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 78b2c056dfd..c7bbd6ad044 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -32,9 +32,9 @@ use crate::hir_def::{ stmt::HirStatement, }; use crate::token::{Attributes, SecondaryAttribute}; -use crate::{ - Generics, Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind, -}; +use crate::GenericTypeVars; +use crate::Generics; +use crate::{Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind}; /// An arbitrary number to limit the recursion depth when searching for trait impls. /// This is needed to stop recursing for cases such as `impl Foo for T where T: Eq` @@ -547,7 +547,12 @@ impl NodeInterner { self.definition_to_type.insert(definition_id, typ); } - pub fn push_empty_trait(&mut self, type_id: TraitId, unresolved_trait: &UnresolvedTrait) { + pub fn push_empty_trait( + &mut self, + type_id: TraitId, + unresolved_trait: &UnresolvedTrait, + generics: Generics, + ) { let self_type_typevar_id = self.next_type_variable_id(); let new_trait = Trait { @@ -555,13 +560,7 @@ impl NodeInterner { name: unresolved_trait.trait_def.name.clone(), crate_id: unresolved_trait.crate_id, location: Location::new(unresolved_trait.trait_def.span, unresolved_trait.file_id), - generics: vecmap(&unresolved_trait.trait_def.generics, |_| { - // Temporary type variable ids before the trait is resolved to its actual ids. - // This lets us record how many arguments the type expects so that other types - // can refer to it with generic arguments before the generic parameters themselves - // are resolved. - TypeVariable::unbound(TypeVariableId(0)) - }), + generics, self_type_typevar_id, self_type_typevar: TypeVariable::unbound(self_type_typevar_id), methods: Vec::new(), @@ -576,6 +575,7 @@ impl NodeInterner { pub fn new_struct( &mut self, typ: &UnresolvedStruct, + generics: Generics, krate: CrateId, local_id: LocalModuleId, file_id: FileId, @@ -585,13 +585,6 @@ impl NodeInterner { // Fields will be filled in later let no_fields = Vec::new(); - let generics = vecmap(&typ.struct_def.generics, |_| { - // Temporary type variable ids before the struct is resolved to its actual ids. - // This lets us record how many arguments the type expects so that other types - // can refer to it with generic arguments before the generic parameters themselves - // are resolved. - TypeVariable::unbound(TypeVariableId(0)) - }); let location = Location::new(typ.struct_def.span, file_id); let new_struct = StructType::new(struct_id, name, location, no_fields, generics); @@ -600,7 +593,11 @@ impl NodeInterner { struct_id } - pub fn push_type_alias(&mut self, typ: &UnresolvedTypeAlias) -> TypeAliasId { + pub fn push_type_alias( + &mut self, + typ: &UnresolvedTypeAlias, + generics: Generics, + ) -> TypeAliasId { let type_id = TypeAliasId(self.type_aliases.len()); self.type_aliases.push(Shared::new(TypeAlias::new( @@ -608,7 +605,7 @@ impl NodeInterner { typ.type_alias_def.name.clone(), Location::new(typ.type_alias_def.span, typ.file_id), Type::Error, - vecmap(&typ.type_alias_def.generics, |_| TypeVariable::unbound(TypeVariableId(0))), + generics, ))); type_id @@ -1407,7 +1404,7 @@ impl NodeInterner { trait_id: TraitId, trait_generics: Vec, impl_id: TraitImplId, - impl_generics: Generics, + impl_generics: GenericTypeVars, trait_impl: Shared, ) -> Result<(), (Span, FileId)> { self.trait_implementations.insert(impl_id, trait_impl.clone()); @@ -1825,7 +1822,7 @@ fn get_type_method_key(typ: &Type) -> Option { Type::Unit => Some(Unit), Type::Tuple(_) => Some(Tuple), Type::Function(_, _, _) => Some(Function), - Type::NamedGeneric(_, _) => Some(Generic), + Type::NamedGeneric(_, _, _) => Some(Generic), Type::Quoted(quoted) => Some(Quoted(*quoted)), Type::MutableReference(element) => get_type_method_key(element), Type::Alias(alias, _) => get_type_method_key(&alias.borrow().typ), diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 72b1ea05ec2..315c8cf5d64 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -197,7 +197,7 @@ fn parameter_name_recovery() -> impl NoirParser { } fn top_level_statement_recovery() -> impl NoirParser { - none_of([Token::Semicolon, Token::RightBrace, Token::EOF]) + none_of([Token::RightBrace, Token::EOF]) .repeated() .ignore_then(one_of([Token::Semicolon])) .map(|_| TopLevelStatement::Error) diff --git a/compiler/noirc_frontend/src/parser/parser/function.rs b/compiler/noirc_frontend/src/parser/parser/function.rs index 4db5637f6a7..3e686ee4c85 100644 --- a/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/compiler/noirc_frontend/src/parser/parser/function.rs @@ -5,11 +5,14 @@ use super::{ self_parameter, where_clause, NoirParser, }; use crate::ast::{ - FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, NoirFunction, Param, Visibility, + FunctionDefinition, FunctionReturnType, ItemVisibility, NoirFunction, Param, Visibility, }; -use crate::parser::labels::ParsingRuleLabel; use crate::parser::spanned; use crate::token::{Keyword, Token}; +use crate::{ + ast::{UnresolvedGeneric, UnresolvedGenerics}, + parser::labels::ParsingRuleLabel, +}; use chumsky::prelude::*; @@ -76,16 +79,31 @@ fn function_modifiers() -> impl NoirParser<(bool, ItemVisibility, bool)> { }) } +pub(super) fn numeric_generic() -> impl NoirParser { + keyword(Keyword::Let) + .ignore_then(ident()) + .then_ignore(just(Token::Colon)) + .then(parse_type()) + .map(|(ident, typ)| UnresolvedGeneric::Numeric { ident, typ }) +} + +pub(super) fn generic_type() -> impl NoirParser { + ident().map(UnresolvedGeneric::Variable) +} + +pub(super) fn generic() -> impl NoirParser { + generic_type().or(numeric_generic()) +} + /// non_empty_ident_list: ident ',' non_empty_ident_list /// | ident /// /// generics: '<' non_empty_ident_list '>' /// | %empty -pub(super) fn generics() -> impl NoirParser> { - ident() +pub(super) fn generics() -> impl NoirParser { + generic() .separated_by(just(Token::Comma)) .allow_trailing() - .at_least(1) .delimited_by(just(Token::Less), just(Token::Greater)) .or_not() .map(|opt| opt.unwrap_or_default()) @@ -193,6 +211,7 @@ mod test { // fn func_name(x: impl Eq) {} with error Expected an end of input but found end of input // "fn func_name(x: impl Eq) {}", "fn func_name(x: impl Eq, y : T) where T: SomeTrait + Eq {}", + "fn func_name(x: [Field; N]) {}", ], ); @@ -209,6 +228,11 @@ mod test { // A leading plus is not allowed. "fn func_name(f: Field, y : T) where T: + SomeTrait {}", "fn func_name(f: Field, y : T) where T: TraitX + {}", + // Test ill-formed numeric generics + "fn func_name(y: T) {}", + "fn func_name(y: T) {}", + "fn func_name(y: T) {}", + "fn func_name(y: T) {}", ], ); } diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index f4845625b87..9251eb3db6b 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -50,7 +50,10 @@ pub(crate) fn remove_experimental_warnings(errors: &mut Vec<(CompilationError, F }); } -pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { +pub(crate) fn get_program( + src: &str, + use_legacy: bool, +) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { let root = std::path::Path::new("/"); let fm = FileManager::new(root); @@ -82,7 +85,7 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation &mut context, program.clone().into_sorted(), root_file_id, - false, + use_legacy, &[], // No macro processors )); } @@ -90,7 +93,7 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation } pub(crate) fn get_program_errors(src: &str) -> Vec<(CompilationError, FileId)> { - get_program(src).2 + get_program(src, false).2 } #[test] @@ -833,7 +836,7 @@ fn check_trait_as_type_as_two_fn_parameters() { } fn get_program_captures(src: &str) -> Vec> { - let (program, context, _errors) = get_program(src); + let (program, context, _errors) = get_program(src, false); let interner = context.def_interner; let mut all_captures: Vec> = Vec::new(); for func in program.into_sorted().functions { @@ -1195,7 +1198,7 @@ fn resolve_fmt_strings() { } fn check_rewrite(src: &str, expected: &str) { - let (_program, mut context, _errors) = get_program(src); + let (_program, mut context, _errors) = get_program(src, false); let main_func_id = context.def_interner.find_function("main").unwrap(); let program = monomorphize(main_func_id, &mut context.def_interner).unwrap(); assert!(format!("{}", program) == expected); @@ -1326,14 +1329,20 @@ fn for_loop_over_array() { hello(array); } "#; - assert_eq!(get_program_errors(src).len(), 0); + let errors = get_program_errors(src); + assert_eq!(get_program_errors(src).len(), 1); + + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UseExplicitNumericGeneric { .. }) + )); } // Regression for #4545 #[test] fn type_aliases_in_main() { let src = r#" - type Outer = [u8; N]; + type Outer = [u8; N]; fn main(_arg: Outer<1>) {} "#; assert_eq!(get_program_errors(src).len(), 0); @@ -1446,6 +1455,425 @@ fn specify_method_types_with_turbofish() { assert_eq!(errors.len(), 0); } +#[test] +fn struct_numeric_generic_in_function() { + let src = r#" + struct Foo { + inner: u64 + } + + fn bar() { } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { .. }), + )); +} + +#[test] +fn struct_numeric_generic_in_struct() { + let src = r#" + struct Foo { + inner: u64 + } + + struct Bar { } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::DefinitionError( + DefCollectorErrorKind::UnsupportedNumericGenericType { .. } + ), + )); +} + +#[test] +fn bool_numeric_generic() { + let src = r#" + fn read() -> Field { + if N { + 0 + } else { + 1 + } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { .. }), + )); +} + +#[test] +fn numeric_generic_binary_operation_type_mismatch() { + let src = r#" + fn foo() -> bool { + let mut check: bool = true; + check = N; + check + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeMismatchWithSource { .. }), + )); +} + +#[test] +fn bool_generic_as_loop_bound() { + let src = r#" + fn read() { + let mut fields = [0; N]; + for i in 0..N { + fields[i] = i + 1; + } + assert(fields[0] == 1); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { .. }), + )); + + let CompilationError::TypeError(TypeCheckError::TypeMismatch { + expected_typ, expr_typ, .. + }) = &errors[1].0 + else { + panic!("Got an error other than a type mismatch"); + }; + + assert_eq!(expected_typ, "Field"); + assert_eq!(expr_typ, "bool"); +} + +#[test] +fn numeric_generic_in_function_signature() { + let src = r#" + fn foo(arr: [Field; N]) -> [Field; N] { arr } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_as_struct_field_type() { + let src = r#" + struct Foo { + a: Field, + b: N, + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); +} + +#[test] +fn normal_generic_as_array_length() { + let src = r#" + struct Foo { + a: Field, + b: [Field; N], + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + // TODO(https://github.com/noir-lang/noir/issues/5156): This should be switched to a hard type error rather than + // the `UseExplicitNumericGeneric` once implicit numeric generics are removed. + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UseExplicitNumericGeneric { .. }), + )); +} + +#[test] +fn numeric_generic_as_param_type() { + let src = r#" + fn foo(x: I) -> I { + let _q: I = 5; + x + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 3); + // Error from the parameter type + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); + // Error from the let statement annotated type + assert!(matches!( + errors[1].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); + // Error from the return type + assert!(matches!( + errors[2].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); +} + +#[test] +fn numeric_generic_used_in_nested_type_fail() { + let src = r#" + struct Foo { + a: Field, + b: Bar, + } + struct Bar { + inner: N + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::NumericGenericUsedForType { .. }), + )); +} + +#[test] +fn normal_generic_used_in_nested_array_length_fail() { + let src = r#" + struct Foo { + a: Field, + b: Bar, + } + struct Bar { + inner: [Field; N] + } + "#; + let errors = get_program_errors(src); + // TODO(https://github.com/noir-lang/noir/issues/5156): This should be switched to a hard type error once implicit numeric generics are removed. + assert_eq!(errors.len(), 0); +} + +#[test] +fn numeric_generic_used_in_nested_type_pass() { + // The order of these structs should not be changed to make sure + // that we are accurately resolving all struct generics before struct fields + let src = r#" + struct NestedNumeric { + a: Field, + b: InnerNumeric + } + struct InnerNumeric { + inner: [u64; N], + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_used_in_trait() { + let src = r#" + struct MyType { + a: Field, + b: Field, + c: Field, + d: T, + } + + impl Deserialize for MyType { + fn deserialize(fields: [Field; N], other: T) -> Self { + MyType { a: fields[0], b: fields[1], c: fields[2], d: other } + } + } + + trait Deserialize { + fn deserialize(fields: [Field; N], other: T) -> Self; + } + "#; + let errors = get_program_errors(src); + // We want to make sure that `N` in `impl Deserialize` does + // not trigger `expected type, found numeric generic parameter N` as the trait + // does in fact expect a numeric generic. + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_in_trait_impl_with_extra_impl_generics() { + let src = r#" + trait Default { + fn default() -> Self; + } + + struct MyType { + a: Field, + b: Field, + c: Field, + d: T, + } + + // Make sure that `T` is placed before `N` as we want to test that the order of the generics is correctly maintained. + // `N` is used first in the trait impl generics (`Deserialize for MyType`). + // We want to make sure that the compiler correctly accounts for that `N` has a numeric kind + // while `T` has a normal kind. + impl Deserialize for MyType where T: Default { + fn deserialize(fields: [Field; N]) -> Self { + MyType { a: fields[0], b: fields[1], c: fields[2], d: T::default() } + } + } + + trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_used_in_where_clause() { + let src = r#" + trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; + } + + fn read() -> T where T: Deserialize { + let mut fields: [Field; N] = [0; N]; + for i in 0..N { + fields[i] = i as Field + 1; + } + T::deserialize(fields) + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn numeric_generic_used_in_turbofish() { + let src = r#" + fn double() -> u32 { + // Used as an expression + N * 2 + } + + fn double_numeric_generics_test() { + // Example usage of a numeric generic arguments. + assert(double::<9>() == 18); + assert(double::<7 + 8>() == 30); + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn constant_used_with_numeric_generic() { + let src = r#" + struct ValueNote { + value: Field, + } + + trait Serialize { + fn serialize(self) -> [Field; N]; + } + + impl Serialize<1> for ValueNote { + fn serialize(self) -> [Field; 1] { + [self.value] + } + } + "#; + let errors = get_program_errors(src); + assert!(errors.is_empty()); +} + +#[test] +fn normal_generic_used_when_numeric_expected_in_where_clause() { + let src = r#" + trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; + } + + fn read() -> T where T: Deserialize { + T::deserialize([0, 1]) + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeMismatch { .. }), + )); + + let src = r#" + trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; + } + + fn read() -> T where T: Deserialize { + let mut fields: [Field; N] = [0; N]; + for i in 0..N { + fields[i] = i as Field + 1; + } + T::deserialize(fields) + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::VariableNotDeclared { .. }), + )); +} + +// TODO(https://github.com/noir-lang/noir/issues/5156): Remove this test once we ban implicit numeric generics +#[test] +fn implicit_numeric_generics_elaborator() { + let src = r#" + struct BoundedVec { + storage: [T; MaxLen], + len: u64, + } + + impl BoundedVec { + + // Test that we have an implicit numeric generic for "Len" as well as "MaxLen" + pub fn extend_from_bounded_vec(&mut self, _vec: BoundedVec) { + // We do this to avoid an unused variable warning on `self` + let _ = self.len; + for _ in 0..Len { } + } + + pub fn push(&mut self, elem: T) { + assert(self.len < MaxLen, "push out of bounds"); + self.storage[self.len] = elem; + self.len += 1; + } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 4); + + for error in errors.iter() { + if let CompilationError::ResolverError(ResolverError::UseExplicitNumericGeneric { ident }) = + &errors[0].0 + { + assert!(matches!(ident.0.contents.as_str(), "MaxLen" | "Len")); + } else { + panic!("Expected ResolverError::UseExplicitNumericGeneric but got {:?}", error); + } + } +} + #[test] fn quote_code_fragments() { // This test ensures we can quote (and unquote/splice) code fragments diff --git a/test_programs/compile_success_empty/numeric_generics/src/main.nr b/test_programs/compile_success_empty/numeric_generics/src/main.nr index 1e03a382fed..340c18c2a1d 100644 --- a/test_programs/compile_success_empty/numeric_generics/src/main.nr +++ b/test_programs/compile_success_empty/numeric_generics/src/main.nr @@ -36,4 +36,3 @@ fn foo(mut s: MyStruct<2+1>) -> MyStruct<10/2-2> { s.data[0] = s.data[0] + 1; s } - diff --git a/test_programs/compile_success_empty/numeric_generics_explicit/Nargo.toml b/test_programs/compile_success_empty/numeric_generics_explicit/Nargo.toml new file mode 100644 index 00000000000..bc3d43498db --- /dev/null +++ b/test_programs/compile_success_empty/numeric_generics_explicit/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "numeric_generics_explicit" +type = "bin" +authors = [""] +compiler_version = ">=0.30.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr b/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr new file mode 100644 index 00000000000..7c4f7761ff6 --- /dev/null +++ b/test_programs/compile_success_empty/numeric_generics_explicit/src/main.nr @@ -0,0 +1,111 @@ +// Regression that a global of the same name does not trigger a duplicate definition error +global N = 1000; + +fn main() { + let a = id([1, 2]); + let b = id([1, 2, 3]); + + let itWorks1 = MyStruct { data: a }; + assert(itWorks1.data[1] == 2); + let itWorks2 = MyStruct { data: b }; + assert(itWorks2.data[1] == 2); + + let c = [1, 2]; + let itAlsoWorks = MyStruct { data: c }; + assert(itAlsoWorks.data[1] == 2); + + assert(foo(itWorks2).data[0] == itWorks2.data[0] + 1); + + double_numeric_generics_test(); + + let my_type = PublicStorage::read::(); + assert(my_type.a == 1); + assert(my_type.b == 2); + assert(my_type.c == 3); + + let foo = baz::<10>(); + assert(foo.data == [1; 10]); +} + +// Used in the signature of a function +fn id(x: [Field; I]) -> [Field; I] { + x +} + +// Used as a field of a struct +struct MyStruct { + data: [Field; S], +} + +// Used in an impl +impl MyStruct { + fn insert(mut self: Self, index: Field, elem: Field) -> Self { + // Regression test for numeric generics on impls + assert(index as u32 < S); + + self.data[index] = elem; + self + } +} + +fn foo(mut s: MyStruct<2+1>) -> MyStruct<10/2-2> { + s.data[0] = s.data[0] + 1; + s +} + +fn baz() -> MyStruct { + MyStruct { data: [1; N] } +} + +fn double() -> u32 { + // Used as an expression + N * 2 +} + +fn double_numeric_generics_test() { + // Example usage of a numeric generic arguments. + assert(double::<9>() == 18); + assert(double::<123>() == 246); + assert(double::<7 + 8>() == 30); +} + +struct MyType { + a: Field, + b: Field, + c: Field, +} + +impl Deserialize for MyType { + fn deserialize(fields: [Field; N]) -> Self { + MyType { a: fields[0], b: fields[1], c: fields[2] } + } +} + +trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; +} + +struct PublicStorage {} + +impl PublicStorage { + fn read() -> T where T: Deserialize { + // Used as a type within a function body + let mut fields: [Field; N] = [0; N]; + // Used a loop bound + for i in 0..N { + fields[i] = i as Field + 1; + } + T::deserialize(fields) + } +} + +// Check that we can thread numeric generics into nested structs +// and also that we can handle nested structs with numeric generics +// which are declared after the parent struct +struct NestedNumeric { + a: Field, + b: InnerNumeric +} +struct InnerNumeric { + inner: [u32; N], +} diff --git a/test_programs/compile_success_empty/regression_4635/src/main.nr b/test_programs/compile_success_empty/regression_4635/src/main.nr index 23918e30785..350b60ba3f7 100644 --- a/test_programs/compile_success_empty/regression_4635/src/main.nr +++ b/test_programs/compile_success_empty/regression_4635/src/main.nr @@ -8,7 +8,7 @@ impl FromField for Field { } } -trait Deserialize { +trait Deserialize { fn deserialize(fields: [Field; N]) -> Self; } diff --git a/test_programs/compile_success_empty/trait_generics/src/main.nr b/test_programs/compile_success_empty/trait_generics/src/main.nr index 30b2e79d579..56ce7e8970c 100644 --- a/test_programs/compile_success_empty/trait_generics/src/main.nr +++ b/test_programs/compile_success_empty/trait_generics/src/main.nr @@ -29,7 +29,7 @@ impl MyInto for Field { /// Serialize example -trait Serializable { +trait Serializable { fn serialize(self) -> [Field; N]; } diff --git a/test_programs/execution_success/regression_4088/src/main.nr b/test_programs/execution_success/regression_4088/src/main.nr index 9e4d7892fc3..12a7afca68c 100644 --- a/test_programs/execution_success/regression_4088/src/main.nr +++ b/test_programs/execution_success/regression_4088/src/main.nr @@ -1,4 +1,4 @@ -trait Serialize { +trait Serialize { fn serialize(self) -> [Field; N]; } @@ -12,7 +12,7 @@ impl Serialize<1> for ValueNote { } } -fn check(serialized_note: [Field; N]) { +fn check(serialized_note: [Field; N]) { assert(serialized_note[0] == 0); } diff --git a/test_programs/execution_success/regression_4124/src/main.nr b/test_programs/execution_success/regression_4124/src/main.nr index 2b0e65a0b6c..6caea017798 100644 --- a/test_programs/execution_success/regression_4124/src/main.nr +++ b/test_programs/execution_success/regression_4124/src/main.nr @@ -1,6 +1,6 @@ use std::option::Option; -trait MyDeserialize { +trait MyDeserialize { fn deserialize(fields: [Field; N]) -> Self; } @@ -10,7 +10,7 @@ impl MyDeserialize<1> for Field { } } -pub fn storage_read() -> [Field; N] { +pub fn storage_read() -> [Field; N] { std::unsafe::zeroed() } diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index f2da161267d..47fa585a593 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -61,8 +61,8 @@ const IGNORED_BRILLIG_TESTS: [&str; 11] = [ /// Certain features are only available in the elaborator. /// We skip these tests for non-elaborator code since they are not /// expected to work there. This can be removed once the old code is removed. -const IGNORED_NEW_FEATURE_TESTS: [&str; 3] = - ["macros", "wildcard_type", "type_definition_annotation"]; +const IGNORED_NEW_FEATURE_TESTS: [&str; 4] = + ["macros", "wildcard_type", "type_definition_annotation", "numeric_generics_explicit"]; fn read_test_cases( test_data_dir: &Path, diff --git a/tooling/nargo_cli/tests/stdlib-tests.rs b/tooling/nargo_cli/tests/stdlib-tests.rs index 7fd396d6961..bf6614860e2 100644 --- a/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/tooling/nargo_cli/tests/stdlib-tests.rs @@ -32,7 +32,7 @@ fn run_stdlib_tests() { let (mut context, dummy_crate_id) = prepare_package(&file_manager, &parsed_files, &dummy_package); - let result = check_crate(&mut context, dummy_crate_id, true, false, false); + let result = check_crate(&mut context, dummy_crate_id, false, false, false); report_errors(result, &context.file_manager, true, false) .expect("Error encountered while compiling standard library"); diff --git a/tooling/nargo_fmt/src/items.rs b/tooling/nargo_fmt/src/items.rs index 7f998f45b59..80b641fd830 100644 --- a/tooling/nargo_fmt/src/items.rs +++ b/tooling/nargo_fmt/src/items.rs @@ -74,7 +74,8 @@ impl<'me, T> Items<'me, T> { let mut different_line = false; let leading = self.visitor.slice(start..end); - let leading_trimmed = leading.trim(); + // Trim any possible whitespace before and after a comma separator + let leading_trimmed = leading.trim().trim_start_matches(',').trim(); let starts_with_block_comment = leading_trimmed.starts_with("/*"); let ends_with_block_comment = leading_trimmed.ends_with("*/"); diff --git a/tooling/nargo_fmt/src/utils.rs b/tooling/nargo_fmt/src/utils.rs index 2c5c3085e66..020f411ae2f 100644 --- a/tooling/nargo_fmt/src/utils.rs +++ b/tooling/nargo_fmt/src/utils.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use crate::items::HasItem; use crate::rewrite; use crate::visitor::{FmtVisitor, Shape}; -use noirc_frontend::ast::{Expression, Ident, Param, Visibility}; +use noirc_frontend::ast::{Expression, Ident, Param, UnresolvedGeneric, Visibility}; use noirc_frontend::hir::resolution::errors::Span; use noirc_frontend::lexer::Lexer; use noirc_frontend::token::Token; @@ -80,6 +80,7 @@ pub(crate) fn find_comment_end(slice: &str, is_last: bool) -> usize { std::cmp::max(find_comment_end(slice) + block, separator_index + 1) } (_, Some(newline)) if newline > separator_index => newline + 1, + (None, None) => 0, _ => slice.len(), } } else if let Some(newline_index) = newline_index { @@ -170,6 +171,26 @@ impl HasItem for Ident { } } +impl HasItem for UnresolvedGeneric { + fn span(&self) -> Span { + self.span() + } + + fn format(self, visitor: &FmtVisitor, _shape: Shape) -> String { + match self { + UnresolvedGeneric::Variable(_) => visitor.slice(self.span()).into(), + UnresolvedGeneric::Numeric { ident, typ } => { + let mut result = "".to_owned(); + result.push_str(&ident.0.contents); + result.push_str(": "); + let typ = rewrite::typ(visitor, _shape, typ); + result.push_str(&typ); + result + } + } + } +} + pub(crate) fn first_line_width(exprs: &str) -> usize { exprs.lines().next().map_or(0, |line: &str| line.chars().count()) } diff --git a/tooling/nargo_fmt/tests/expected/fn.nr b/tooling/nargo_fmt/tests/expected/fn.nr index 961e67faf1c..4dde9a1b3ec 100644 --- a/tooling/nargo_fmt/tests/expected/fn.nr +++ b/tooling/nargo_fmt/tests/expected/fn.nr @@ -62,6 +62,10 @@ fn main( pub fn from_baz(x: [Field; crate::foo::MAGIC_NUMBER]) {} +fn id(x: [Field; I]) -> [Field; I] {} + +fn id_two(x: [Field; I]) -> [Field; I] {} + fn whitespace_before_generics(foo: T) {} fn more_whitespace_before_generics(foo: T) {} diff --git a/tooling/nargo_fmt/tests/input/fn.nr b/tooling/nargo_fmt/tests/input/fn.nr index 03806b0fef9..16ed95a540d 100644 --- a/tooling/nargo_fmt/tests/input/fn.nr +++ b/tooling/nargo_fmt/tests/input/fn.nr @@ -45,6 +45,11 @@ fn main( pub fn from_baz(x: [Field; crate::foo::MAGIC_NUMBER]) {} +fn id< T , let I : Field > ( x : [ Field ; I ] ) -> [Field; I ] { } + +fn id_two(x: [Field ; I]) -> [ Field; I] {} + fn whitespace_before_generics < T > (foo: T) {} fn more_whitespace_before_generics <