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

feat(traits): Type checking for Trait impl method signatures #2652

Merged
merged 12 commits into from
Sep 19, 2023
9 changes: 6 additions & 3 deletions compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::fmt::Display;

use crate::token::{Attributes, Token};
Expand Down Expand Up @@ -688,10 +689,12 @@ impl Display for FunctionDefinition {
}

impl FunctionReturnType {
pub fn get_type(&self) -> &UnresolvedTypeData {
pub fn get_type(&self) -> Cow<UnresolvedType> {
match self {
FunctionReturnType::Default(_span) => &UnresolvedTypeData::Unit,
FunctionReturnType::Ty(typ) => &typ.typ,
FunctionReturnType::Default(span) => {
Cow::Owned(UnresolvedType { typ: UnresolvedTypeData::Unit, span: Some(*span) })
}
FunctionReturnType::Ty(typ) => Cow::Borrowed(typ),
}
}
}
Expand Down
138 changes: 116 additions & 22 deletions compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
import::{resolve_imports, ImportDirective},
path_resolver::StandardPathResolver,
};
use crate::hir::type_check::{type_check_func, TypeChecker};
use crate::hir::type_check::{type_check_func, TypeCheckError, TypeChecker};
use crate::hir::Context;
use crate::hir_def::traits::{TraitConstant, TraitFunction, TraitType};
use crate::node_interner::{FuncId, NodeInterner, StmtId, StructId, TraitId, TypeAliasId};
use crate::{
ExpressionKind, FunctionReturnType, Generics, Ident, LetStatement, Literal, NoirFunction,
NoirStruct, NoirTrait, NoirTypeAlias, ParsedModule, Shared, StructType, TraitItem,
TraitItemType, Type, TypeBinding, UnresolvedGenerics, UnresolvedType,
ExpressionKind, Generics, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait,
NoirTypeAlias, ParsedModule, Shared, StructType, TraitItem, Type, TypeBinding,
TypeVariableKind, UnresolvedGenerics, UnresolvedType,
};
use fm::FileId;
use iter_extended::vecmap;
Expand Down Expand Up @@ -244,8 +245,8 @@

// Type check all of the functions in the crate
type_check_functions(&mut context.def_interner, file_func_ids, errors);
type_check_functions(&mut context.def_interner, file_trait_impls_ids, errors);
type_check_functions(&mut context.def_interner, file_method_ids, errors);
type_check_functions(&mut context.def_interner, file_trait_impls_ids, errors);
}
}

Expand Down Expand Up @@ -458,7 +459,7 @@
_crate_id: CrateId,
_unresolved_trait: &UnresolvedTrait,
_errors: &mut [FileDiagnostic],
) -> Vec<TraitItemType> {
) -> Vec<TraitType> {
// TODO
vec![]
}
Expand All @@ -467,17 +468,18 @@
_crate_id: CrateId,
_unresolved_trait: &UnresolvedTrait,
_errors: &mut [FileDiagnostic],
) -> Vec<TraitItemType> {
) -> Vec<TraitConstant> {
// TODO
vec![]
}

fn resolve_trait_methods(
context: &mut Context,
trait_id: TraitId,
crate_id: CrateId,
unresolved_trait: &UnresolvedTrait,
errors: &mut Vec<FileDiagnostic>,
) -> Vec<TraitItemType> {
) -> Vec<TraitFunction> {
let interner = &mut context.def_interner;
let def_maps = &mut context.def_maps;

Expand All @@ -499,19 +501,23 @@
body: _,
} = item
{
let the_trait = interner.get_trait(trait_id);
let self_type = Type::TypeVariable(
the_trait.borrow().self_type_typevar.clone(),
TypeVariableKind::Normal,
);

let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file);
resolver.set_self_type(Some(self_type));

let arguments = vecmap(parameters, |param| resolver.resolve_type(param.1.clone()));
let resolved_return_type = match return_type {
FunctionReturnType::Default(_) => None,
FunctionReturnType::Ty(unresolved_type) => {
Some(resolver.resolve_type(unresolved_type.clone()))
}
};
let resolved_return_type = resolver.resolve_type(return_type.get_type().into_owned());

let name = name.clone();
// TODO
let generics: Generics = vec![];
let span: Span = name.span();
let f = TraitItemType::Function {
let f = TraitFunction {
name,
generics,
arguments,
Expand Down Expand Up @@ -552,16 +558,16 @@
context.def_interner.push_empty_trait(*trait_id, unresolved_trait);
}
for (trait_id, unresolved_trait) in traits {
let mut items: Vec<TraitItemType> = vec![];
// Resolve order
// 1. Trait Types ( Trait contants can have a trait type, therefore types before constants)
items.append(&mut resolve_trait_types(context, crate_id, &unresolved_trait, errors));
// 2. Trait Constants ( Trait's methods can use trait types & constants, threfore they should be after)
items.append(&mut resolve_trait_constants(context, crate_id, &unresolved_trait, errors));
// 1. Trait Types ( Trait constants can have a trait type, therefore types before constants)
let _ = resolve_trait_types(context, crate_id, &unresolved_trait, errors);
// 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, errors);
// 3. Trait Methods
items.append(&mut resolve_trait_methods(context, crate_id, &unresolved_trait, errors));
let methods = resolve_trait_methods(context, trait_id, crate_id, &unresolved_trait, errors);

context.def_interner.update_trait(trait_id, |trait_def| {
trait_def.set_items(items);
trait_def.set_methods(methods);
});
}
}
Expand Down Expand Up @@ -690,6 +696,12 @@
errors,
);

let mut new_resolver =
Resolver::new(interner, &path_resolver, &context.def_maps, trait_impl.file_id);
new_resolver.set_self_type(Some(self_type.clone()));

check_methods_signatures(&mut new_resolver, &impl_methods, trait_id, errors);
jfecher marked this conversation as resolved.
Show resolved Hide resolved

let trait_definition_ident = &trait_impl.trait_impl_ident;
let key = (self_type.clone(), trait_id);
if let Some(prev_trait_impl_ident) = interner.get_previous_trait_implementation(&key) {
Expand All @@ -701,7 +713,7 @@
errors.push(err.into_file_diagnostic(trait_impl.methods.file_id));
} else {
let _func_ids =
interner.add_trait_implementaion(&key, trait_definition_ident, &trait_impl.methods);

Check warning on line 716 in compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (implementaion)
}

methods.append(&mut impl_methods);
Expand All @@ -709,6 +721,88 @@

methods
}

// TODO(vitkov): Move this out of here and into type_check

Check warning on line 725 in compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (vitkov)
fn check_methods_signatures(
resolver: &mut Resolver,
impl_methods: &Vec<(FileId, FuncId)>,
trait_id: TraitId,
errors: &mut Vec<FileDiagnostic>,
) {
let the_trait_shared = resolver.interner.get_trait(trait_id);
let the_trait = the_trait_shared.borrow();

let self_type = resolver.get_self_type().expect("trait impl must have a Self type");

// Temporarily bind the trait's Self type to self_type so we can type check
let _ = the_trait.self_type_typevar.borrow_mut().bind_to(self_type.clone(), the_trait.span);

for (file_id, func_id) in impl_methods {
let meta = resolver.interner.function_meta(func_id);
let func_name = resolver.interner.function_name(func_id).to_owned();

let mut typecheck_errors = Vec::new();

Check warning on line 744 in compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (typecheck)

// `method` is None in the case where the impl block has a method that's not part of the trait.
// If that's the case, a `MethodNotInTrait` error has already been thrown, and we can ignore
// the impl method, since there's nothing in the trait to match its signature against.
if let Some(method) =
the_trait.methods.iter().find(|method| method.name.0.contents == func_name)
{
let function_typ = meta.typ.instantiate(resolver.interner);

if let Type::Function(params, _, _) = function_typ.0 {
if method.arguments.len() == params.len() {
// Check the parameters of the impl method against the parameters of the trait method
for (parameter_index, ((expected, actual), (hir_pattern, _, _))) in
method.arguments.iter().zip(&params).zip(&meta.parameters.0).enumerate()
{
expected.unify(actual, &mut typecheck_errors, || {

Check warning on line 760 in compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (typecheck)
TypeCheckError::TraitMethodParameterTypeMismatch {
method_name: func_name.to_string(),
expected_typ: expected.to_string(),
actual_typ: actual.to_string(),
parameter_span: hir_pattern.span(),
parameter_index: parameter_index + 1,
}
});
}
} else {
errors.push(
DefCollectorErrorKind::MismatchTraitImplementationNumParameters {
actual_num_parameters: meta.parameters.0.len(),
expected_num_parameters: method.arguments.len(),
trait_name: the_trait.name.to_string(),
method_name: func_name.to_string(),
span: meta.location.span,
}
.into_file_diagnostic(*file_id),
);
}
}
jfecher marked this conversation as resolved.
Show resolved Hide resolved

// Check that impl method return type matches trait return type:
let resolved_return_type =
resolver.resolve_type(meta.return_type.get_type().into_owned());

method.return_type.unify(&resolved_return_type, &mut typecheck_errors, || {

Check warning on line 788 in compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (typecheck)
let ret_type_span =
meta.return_type.get_type().span.expect("return type must always have a span");

TypeCheckError::TypeMismatch {
expected_typ: method.return_type.to_string(),
expr_typ: meta.return_type().to_string(),
expr_span: ret_type_span,
}
});

extend_errors(errors, *file_id, typecheck_errors);

Check warning on line 799 in compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (typecheck)
}
}

the_trait.self_type_typevar.borrow_mut().unbind(the_trait.self_type_typevar_id);
}

fn resolve_free_functions(
interner: &mut NodeInterner,
crate_id: CrateId,
Expand Down
37 changes: 33 additions & 4 deletions compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashSet;

use fm::FileId;
use noirc_errors::{FileDiagnostic, Location};

Expand Down Expand Up @@ -212,8 +214,11 @@
}
}

// set of function ids that have a corresponding method in the trait
let mut func_ids_in_trait = HashSet::new();

for item in &trait_def.items {
// TODO(Maddiaa): Investigate trait implementations with attributes see: https://github.com/noir-lang/noir/issues/2629

Check warning on line 221 in compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (Maddiaa)
if let TraitItem::Function {
name,
generics,
Expand All @@ -223,13 +228,19 @@
body,
} = item
{
let is_implemented = unresolved_functions
// List of functions in the impl block with the same name as the method
// `matching_fns.len() == 0` => missing method impl
// `matching_fns.len() > 1` => duplicate definition (collect_functions will throw a Duplicate error)
let matching_fns: Vec<_> = unresolved_functions
.functions
.iter()
.any(|(_, _, func_impl)| func_impl.name() == name.0.contents);
if !is_implemented {
.filter(|(_, _, func_impl)| func_impl.name() == name.0.contents)
.collect();

if matching_fns.is_empty() {
match body {
Some(body) => {
// if there's a default implementation for the method, use it
let method_name = name.0.contents.clone();
let func_id = context.def_interner.push_empty_fn();
context.def_interner.push_function_definition(method_name, func_id);
Expand All @@ -241,20 +252,38 @@
where_clause,
return_type,
));
func_ids_in_trait.insert(func_id);
unresolved_functions.push_fn(self.module_id, func_id, impl_method);
}
None => {
let error = DefCollectorErrorKind::TraitMissedMethodImplementation {
let error = DefCollectorErrorKind::TraitMissingMethod {
trait_name: trait_def.name.clone(),
method_name: name.clone(),
trait_impl_span: trait_impl.object_type_span,
};
errors.push(error.into_file_diagnostic(self.file_id));
}
}
} else {
for (_, func_id, _) in &matching_fns {
func_ids_in_trait.insert(*func_id);
}
jfecher marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

// Emit MethodNotInTrait error for methods in the impl block that
// don't have a corresponding method signature defined in the trait
for (_, func_id, func) in &unresolved_functions.functions {
if !func_ids_in_trait.contains(func_id) {
let error = DefCollectorErrorKind::MethodNotInTrait {
trait_name: trait_def.name.clone(),
impl_method: func.name_ident().clone(),
};
errors.push(error.into_file_diagnostic(self.file_id));
}
}

unresolved_functions
}

Expand Down
Loading