Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: type-check turbofish in trait before function call #6416

Merged
merged 6 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
}
Loading