From f8fd813b09ce870364700659e3ea8499ab51105e Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 31 Oct 2024 19:47:26 -0300 Subject: [PATCH] fix: type-check turbofish in trait before function call (#6416) --- .../noirc_frontend/src/elaborator/patterns.rs | 102 ++++++++++++------ .../noirc_frontend/src/elaborator/types.rs | 4 + .../src/hir/type_check/errors.rs | 6 -- .../noirc_frontend/src/tests/turbofish.rs | 57 ++++++++++ 4 files changed, 133 insertions(+), 36 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index a953c54dc4b..9e60adcbc6f 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -17,7 +17,7 @@ use crate::{ stmt::HirPattern, }, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind}, - Kind, ResolvedGeneric, Shared, StructType, Type, TypeAlias, TypeBindings, + Kind, Shared, StructType, Type, TypeAlias, TypeBindings, }; use super::{Elaborator, ResolverMeta}; @@ -413,19 +413,20 @@ impl<'context> Elaborator<'context> { unresolved_turbofish: Option>, span: Span, ) -> Option> { - let direct_generics = self.interner.function_meta(func_id).direct_generics.clone(); + let direct_generic_kinds = + vecmap(&self.interner.function_meta(func_id).direct_generics, |generic| generic.kind()); unresolved_turbofish.map(|unresolved_turbofish| { - if unresolved_turbofish.len() != direct_generics.len() { + if unresolved_turbofish.len() != direct_generic_kinds.len() { let type_check_err = TypeCheckError::IncorrectTurbofishGenericCount { - expected_count: direct_generics.len(), + expected_count: direct_generic_kinds.len(), actual_count: unresolved_turbofish.len(), span, }; self.push_err(type_check_err); } - self.resolve_turbofish_generics(&direct_generics, unresolved_turbofish) + self.resolve_turbofish_generics(direct_generic_kinds, unresolved_turbofish) }) } @@ -436,21 +437,33 @@ impl<'context> Elaborator<'context> { unresolved_turbofish: Option>, span: Span, ) -> Vec { - let Some(turbofish_generics) = unresolved_turbofish else { - return generics; - }; - - if turbofish_generics.len() != generics.len() { - self.push_err(TypeCheckError::GenericCountMismatch { - item: format!("struct {}", struct_type.name), - expected: generics.len(), - found: turbofish_generics.len(), - span, - }); - return generics; - } + let kinds = vecmap(&struct_type.generics, |generic| generic.kind()); + self.resolve_item_turbofish_generics( + "struct", + &struct_type.name.0.contents, + kinds, + generics, + unresolved_turbofish, + span, + ) + } - self.resolve_turbofish_generics(&struct_type.generics, turbofish_generics) + pub(super) fn resolve_trait_turbofish_generics( + &mut self, + trait_name: &str, + trait_generic_kinds: Vec, + generics: Vec, + unresolved_turbofish: Option>, + span: Span, + ) -> Vec { + self.resolve_item_turbofish_generics( + "trait", + trait_name, + trait_generic_kinds, + generics, + unresolved_turbofish, + span, + ) } pub(super) fn resolve_alias_turbofish_generics( @@ -459,6 +472,26 @@ impl<'context> Elaborator<'context> { generics: Vec, unresolved_turbofish: Option>, span: Span, + ) -> Vec { + let kinds = vecmap(&type_alias.generics, |generic| generic.kind()); + self.resolve_item_turbofish_generics( + "alias", + &type_alias.name.0.contents, + kinds, + generics, + unresolved_turbofish, + span, + ) + } + + pub(super) fn resolve_item_turbofish_generics( + &mut self, + item_kind: &'static str, + item_name: &str, + item_generic_kinds: Vec, + generics: Vec, + unresolved_turbofish: Option>, + span: Span, ) -> Vec { let Some(turbofish_generics) = unresolved_turbofish else { return generics; @@ -466,7 +499,7 @@ impl<'context> Elaborator<'context> { if turbofish_generics.len() != generics.len() { self.push_err(TypeCheckError::GenericCountMismatch { - item: format!("alias {}", type_alias.name), + item: format!("{item_kind} {item_name}"), expected: generics.len(), found: turbofish_generics.len(), span, @@ -474,17 +507,17 @@ impl<'context> Elaborator<'context> { return generics; } - self.resolve_turbofish_generics(&type_alias.generics, turbofish_generics) + self.resolve_turbofish_generics(item_generic_kinds, turbofish_generics) } pub(super) fn resolve_turbofish_generics( &mut self, - generics: &[ResolvedGeneric], + kinds: Vec, turbofish_generics: Vec, ) -> Vec { - let generics_with_types = generics.iter().zip(turbofish_generics); - vecmap(generics_with_types, |(generic, unresolved_type)| { - self.resolve_type_inner(unresolved_type, &generic.kind()) + let kinds_with_types = kinds.into_iter().zip(turbofish_generics); + vecmap(kinds_with_types, |(kind, unresolved_type)| { + self.resolve_type_inner(unresolved_type, &kind) }) } @@ -581,10 +614,19 @@ impl<'context> Elaborator<'context> { generics } - PathResolutionItem::TraitFunction(_trait_id, Some(generics), _func_id) => { - // TODO: https://github.com/noir-lang/noir/issues/6310 - self.push_err(TypeCheckError::UnsupportedTurbofishUsage { span: generics.span }); - Vec::new() + PathResolutionItem::TraitFunction(trait_id, Some(generics), _func_id) => { + let trait_ = self.interner.get_trait(trait_id); + let kinds = vecmap(&trait_.generics, |generic| generic.kind()); + let trait_generics = + vecmap(&kinds, |kind| self.interner.next_type_variable_with_kind(kind.clone())); + + self.resolve_trait_turbofish_generics( + &trait_.name.to_string(), + kinds, + trait_generics, + Some(generics.generics), + generics.span, + ) } _ => Vec::new(), } @@ -602,7 +644,7 @@ impl<'context> Elaborator<'context> { id: self.interner.trait_method_id(trait_path_resolution.method.method_id), impl_kind: ImplKind::TraitMethod(trait_path_resolution.method), }, - None, + trait_path_resolution.item, ) } else { // If the Path is being used as an Expression, then it is referring to a global from a separate module diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 2879204d3ee..c8a16a6cd9b 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -48,6 +48,7 @@ pub const WILDCARD_TYPE: &str = "_"; pub(super) struct TraitPathResolution { pub(super) method: TraitMethod, + pub(super) item: Option, pub(super) errors: Vec, } @@ -553,6 +554,7 @@ impl<'context> Elaborator<'context> { let constraint = the_trait.as_constraint(path.span); return Some(TraitPathResolution { method: TraitMethod { method_id: method, constraint, assumed: true }, + item: None, errors: Vec::new(), }); } @@ -573,6 +575,7 @@ impl<'context> Elaborator<'context> { let constraint = the_trait.as_constraint(path.span); Some(TraitPathResolution { method: TraitMethod { method_id: method, constraint, assumed: false }, + item: Some(path_resolution.item), errors: path_resolution.errors, }) } @@ -601,6 +604,7 @@ impl<'context> Elaborator<'context> { if let Some(method) = the_trait.find_method(path.last_name()) { return Some(TraitPathResolution { method: TraitMethod { method_id: method, constraint, assumed: true }, + item: None, errors: Vec::new(), }); } diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index 3b4ab148ef7..cb69053b72a 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -174,8 +174,6 @@ pub enum TypeCheckError { StringIndexAssign { span: Span }, #[error("Macro calls may only return `Quoted` values")] MacroReturningNonExpr { typ: Type, span: Span }, - #[error("turbofish (`::<_>`) usage at this position isn't supported yet")] - UnsupportedTurbofishUsage { span: Span }, #[error("`{name}` has already been specified")] DuplicateNamedTypeArg { name: Ident, prev_span: Span }, #[error("`{item}` has no associated type named `{name}`")] @@ -443,10 +441,6 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { error.add_secondary("Hint: remove the `!` from the end of the function name.".to_string(), *span); error }, - TypeCheckError::UnsupportedTurbofishUsage { span } => { - let msg = "turbofish (`::<_>`) usage at this position isn't supported yet"; - Diagnostic::simple_error(msg.to_string(), "".to_string(), *span) - }, TypeCheckError::DuplicateNamedTypeArg { name, prev_span } => { let msg = format!("`{name}` has already been specified"); let mut error = Diagnostic::simple_error(msg.to_string(), "".to_string(), name.span()); diff --git a/compiler/noirc_frontend/src/tests/turbofish.rs b/compiler/noirc_frontend/src/tests/turbofish.rs index 3bde3e17c80..3e34ea9521b 100644 --- a/compiler/noirc_frontend/src/tests/turbofish.rs +++ b/compiler/noirc_frontend/src/tests/turbofish.rs @@ -375,6 +375,18 @@ fn use_generic_type_alias_with_partial_generics_with_turbofish_in_method_call_er "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::TypeMismatch { + expected_typ, + expr_typ, + expr_span: _, + }) = &errors[0].0 + else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; + + assert_eq!(expected_typ, "bool"); + assert_eq!(expr_typ, "Field"); } #[test] @@ -399,4 +411,49 @@ fn use_generic_type_alias_with_partial_generics_with_turbofish_in_method_call_er "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::TypeMismatch { + expected_typ, + expr_typ, + expr_span: _, + }) = &errors[0].0 + else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; + + assert_eq!(expected_typ, "i32"); + assert_eq!(expr_typ, "bool"); +} + +#[test] +fn trait_function_with_turbofish_on_trait_gives_error() { + let src = r#" + trait Foo { + fn foo(_x: T) -> Self; + } + + impl Foo for i32 { + fn foo(_x: T) -> Self { + 1 + } + } + + fn main() { + let _: i32 = Foo::::foo(1); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::TypeError(TypeCheckError::TypeMismatch { + expected_typ, + expr_typ, + expr_span: _, + }) = &errors[0].0 + else { + panic!("Expected a type mismatch error, got {:?}", errors[0].0); + }; + + assert_eq!(expected_typ, "bool"); + assert_eq!(expr_typ, "Field"); }