Skip to content

Commit

Permalink
fix: type-check turbofish in trait before function call (noir-lang#6416)
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite authored Oct 31, 2024
1 parent 59c1a18 commit f8fd813
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 36 deletions.
102 changes: 72 additions & 30 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -413,19 +413,20 @@ impl<'context> Elaborator<'context> {
unresolved_turbofish: Option<Vec<UnresolvedType>>,
span: Span,
) -> Option<Vec<Type>> {
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)
})
}

Expand All @@ -436,21 +437,33 @@ impl<'context> Elaborator<'context> {
unresolved_turbofish: Option<Vec<UnresolvedType>>,
span: Span,
) -> Vec<Type> {
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<Kind>,
generics: Vec<Type>,
unresolved_turbofish: Option<Vec<UnresolvedType>>,
span: Span,
) -> Vec<Type> {
self.resolve_item_turbofish_generics(
"trait",
trait_name,
trait_generic_kinds,
generics,
unresolved_turbofish,
span,
)
}

pub(super) fn resolve_alias_turbofish_generics(
Expand All @@ -459,32 +472,52 @@ impl<'context> Elaborator<'context> {
generics: Vec<Type>,
unresolved_turbofish: Option<Vec<UnresolvedType>>,
span: Span,
) -> Vec<Type> {
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<Kind>,
generics: Vec<Type>,
unresolved_turbofish: Option<Vec<UnresolvedType>>,
span: Span,
) -> Vec<Type> {
let Some(turbofish_generics) = unresolved_turbofish else {
return generics;
};

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,
});
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<Kind>,
turbofish_generics: Vec<UnresolvedType>,
) -> Vec<Type> {
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)
})
}

Expand Down Expand Up @@ -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(),
}
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub const WILDCARD_TYPE: &str = "_";

pub(super) struct TraitPathResolution {
pub(super) method: TraitMethod,
pub(super) item: Option<PathResolutionItem>,
pub(super) errors: Vec<PathResolutionError>,
}

Expand Down Expand Up @@ -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(),
});
}
Expand All @@ -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,
})
}
Expand Down Expand Up @@ -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(),
});
}
Expand Down
6 changes: 0 additions & 6 deletions compiler/noirc_frontend/src/hir/type_check/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}`")]
Expand Down Expand Up @@ -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());
Expand Down
57 changes: 57 additions & 0 deletions compiler/noirc_frontend/src/tests/turbofish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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<T> {
fn foo(_x: T) -> Self;
}
impl<T> Foo<T> for i32 {
fn foo(_x: T) -> Self {
1
}
}
fn main() {
let _: i32 = Foo::<bool>::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");
}

0 comments on commit f8fd813

Please sign in to comment.