diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 75c95c06d09..f1631286336 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -194,7 +194,7 @@ impl<'context> Elaborator<'context> { let expr_id = self.interner.push_expr(ident); self.interner.push_expr_location(expr_id, call_expr_span, self.file); let ident = old_value.ident.clone(); - let typ = self.type_check_variable(ident, expr_id); + let typ = self.type_check_variable(ident, expr_id, None); self.interner.push_expr_type(expr_id, typ.clone()); capture_types.push(typ); fmt_str_idents.push(expr_id); @@ -291,16 +291,25 @@ impl<'context> Elaborator<'context> { Some(method_ref) => { // Automatically add `&mut` if the method expects a mutable reference and // the object is not already one. - if let HirMethodReference::FuncId(func_id) = &method_ref { - if *func_id != FuncId::dummy_id() { - let function_type = self.interner.function_meta(func_id).typ.clone(); - - self.try_add_mutable_reference_to_object( - &function_type, - &mut object_type, - &mut object, - ); + let func_id = match &method_ref { + HirMethodReference::FuncId(func_id) => *func_id, + HirMethodReference::TraitMethodId(method_id, _) => { + let id = self.interner.trait_method_id(*method_id); + let definition = self.interner.definition(id); + let DefinitionKind::Function(func_id) = definition.kind else { + unreachable!("Expected trait function to be a DefinitionKind::Function") + }; + func_id } + }; + + if func_id != FuncId::dummy_id() { + let function_type = self.interner.function_meta(&func_id).typ.clone(); + self.try_add_mutable_reference_to_object( + &function_type, + &mut object_type, + &mut object, + ); } // These arguments will be given to the desugared function call. @@ -322,6 +331,7 @@ impl<'context> Elaborator<'context> { let generics = method_call.generics.map(|option_inner| { option_inner.into_iter().map(|generic| self.resolve_type(generic)).collect() }); + let turbofish_generics = generics.clone(); let method_call = HirMethodCallExpression { method, object, arguments, location, generics }; @@ -335,7 +345,8 @@ impl<'context> Elaborator<'context> { self.interner, ); - let func_type = self.type_check_variable(function_name, function_id); + let func_type = + self.type_check_variable(function_name, function_id, turbofish_generics); // Type check the new call now that it has been changed from a method call // to a function call. This way we avoid duplicating code. diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 810e1b90743..17c11b88f4a 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -5,6 +5,7 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::ERROR_IDENT, hir::{ + def_collector::dc_crate::CompilationError, resolution::errors::ResolverError, type_check::{Source, TypeCheckError}, }, @@ -330,9 +331,9 @@ 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)); + 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); + let typ = self.type_check_variable(expr, id, generics); self.interner.push_expr_type(id, typ.clone()); (id, typ) } @@ -384,14 +385,19 @@ impl<'context> Elaborator<'context> { } } - pub(super) fn type_check_variable(&mut self, ident: HirIdent, expr_id: ExprId) -> Type { + pub(super) fn type_check_variable( + &mut self, + ident: HirIdent, + expr_id: ExprId, + generics: Option>, + ) -> Type { let mut bindings = TypeBindings::new(); // Add type bindings from any constraints that were used. // We need to do this first since otherwise instantiating the type below // will replace each trait generic with a fresh type variable, rather than // the type used in the trait constraint (if it exists). See #4088. - if let ImplKind::TraitMethod(_, constraint, _) = &ident.impl_kind { + if let ImplKind::TraitMethod(_, constraint, assumed) = &ident.impl_kind { let the_trait = self.interner.get_trait(constraint.trait_id); assert_eq!(the_trait.generics.len(), constraint.trait_generics.len()); @@ -401,6 +407,16 @@ impl<'context> Elaborator<'context> { bindings.insert(param.id(), (param.clone(), arg.clone())); } } + + // If the trait impl is already assumed to exist we should add any type bindings for `Self`. + // Otherwise `self` will be replaced with a fresh type variable, which will require the user + // to specify a redundant type annotation. + if *assumed { + bindings.insert( + the_trait.self_type_typevar_id, + (the_trait.self_type_typevar.clone(), constraint.typ.clone()), + ); + } } // An identifiers type may be forall-quantified in the case of generic functions. @@ -409,10 +425,21 @@ impl<'context> Elaborator<'context> { // variable to handle generic functions. let t = self.interner.id_type_substitute_trait_as_type(ident.id); + let definition = self.interner.try_definition(ident.id); + let function_generic_count = definition.map_or(0, |definition| match &definition.kind { + DefinitionKind::Function(function) => { + self.interner.function_modifiers(function).generic_count + } + _ => 0, + }); + + let span = self.interner.expr_span(&expr_id); + let location = self.interner.expr_location(&expr_id); // This instantiates a trait's generics as well which need to be set // when the constraint below is later solved for when the function is // finished. How to link the two? - let (typ, bindings) = t.instantiate_with_bindings(bindings, self.interner); + let (typ, bindings) = + self.instantiate(t, bindings, generics, function_generic_count, span, location); // Push any trait constraints required by this definition to the context // to be checked later when the type of this variable is further constrained. @@ -447,6 +474,39 @@ impl<'context> Elaborator<'context> { typ } + fn instantiate( + &mut self, + typ: Type, + bindings: TypeBindings, + turbofish_generics: Option>, + function_generic_count: usize, + span: Span, + location: Location, + ) -> (Type, TypeBindings) { + match turbofish_generics { + Some(turbofish_generics) => { + if turbofish_generics.len() != function_generic_count { + let type_check_err = TypeCheckError::IncorrectTurbofishGenericCount { + expected_count: function_generic_count, + actual_count: turbofish_generics.len(), + span, + }; + self.errors.push((CompilationError::TypeError(type_check_err), location.file)); + typ.instantiate_with_bindings(bindings, self.interner) + } else { + // Fetch the count of any implicit generics on the function, such as + // for a method within a generic impl. + let implicit_generic_count = match &typ { + Type::Forall(generics, _) => generics.len() - function_generic_count, + _ => 0, + }; + typ.instantiate_with(turbofish_generics, self.interner, implicit_generic_count) + } + } + None => typ.instantiate_with_bindings(bindings, self.interner), + } + } + fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { let location = Location::new(path.span(), self.file); diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index 9a54e76b8a4..336c1cedbb6 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -399,8 +399,6 @@ impl<'interner> TypeChecker<'interner> { // variable to handle generic functions. let t = self.interner.id_type_substitute_trait_as_type(ident.id); - let span = self.interner.expr_span(expr_id); - let definition = self.interner.try_definition(ident.id); let function_generic_count = definition.map_or(0, |definition| match &definition.kind { DefinitionKind::Function(function) => { @@ -409,6 +407,7 @@ impl<'interner> TypeChecker<'interner> { _ => 0, }); + let span = self.interner.expr_span(expr_id); // This instantiates a trait's generics as well which need to be set // when the constraint below is later solved for when the function is // finished. How to link the two?