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: trait inheritance #6252

Merged
merged 29 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f2bd51f
Parse trait inheritance
asterite Oct 8, 2024
be1ae07
Make sure nargo_fmt doesn't break with trait inheritance
asterite Oct 8, 2024
19016be
Resolve trait parent bounds
asterite Oct 8, 2024
1bdbcf4
Embed ResolvedTraitBound in TraitConstraint
asterite Oct 8, 2024
3937bd0
Solve trait bounds and lookup methods in parent types
asterite Oct 8, 2024
95fa2c6
Use filter_map
asterite Oct 9, 2024
3b3565e
Merge branch 'master' into ab/trait-inheritance
asterite Oct 9, 2024
c65f865
Add assumed trait implementations for parent traits
asterite Oct 9, 2024
57a538f
Detect trait cycles
asterite Oct 9, 2024
dc50605
Avoid looping forever in case there are cycles
asterite Oct 9, 2024
4b65f50
Add a failing test for trait parents with generics
asterite Oct 9, 2024
436f0be
Add a failing test for when a trait impl is missing a parent implemen…
asterite Oct 9, 2024
38dd4e7
Merge branch 'master' into ab/trait-inheritance
asterite Oct 9, 2024
c32803e
Make it work with generics
asterite Oct 9, 2024
3f8ae2d
Refactor
asterite Oct 9, 2024
9153f27
Check parent traits are implemented
asterite Oct 10, 2024
810b87f
Add a test program
asterite Oct 10, 2024
6701a42
Merge branch 'master' into ab/trait-inheritance
asterite Oct 10, 2024
3278cc6
Correct way to format trait name with generics
asterite Oct 10, 2024
5d79bd5
Correct parent check regarding generics
asterite Oct 10, 2024
7b34a78
Add one more test
asterite Oct 10, 2024
fdce653
Improve missing trait bound error message
asterite Oct 10, 2024
e7ea16f
Add some docs
asterite Oct 10, 2024
8c5f3f4
Simpler way to construct a Diagnostic
asterite Oct 10, 2024
81b7f3b
Apply suggestions from code review
asterite Oct 10, 2024
f69a6ab
Add supertrait and supertraits to cspell
asterite Oct 10, 2024
216fc2e
Extract `bind_ordered_generics`
asterite Oct 10, 2024
566fce7
Remove more duplicated code
asterite Oct 10, 2024
5ca1dee
Add subtrait to cspell
asterite Oct 10, 2024
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
43 changes: 33 additions & 10 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
rc::Rc,
};

use crate::{ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, StructField};
use crate::{ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, StructField, TypeBindings};
use crate::{
ast::{
BlockExpression, FunctionKind, GenericTypeArgs, Ident, NoirFunction, NoirStruct, Param,
Expand Down Expand Up @@ -1033,6 +1033,25 @@ impl<'context> Elaborator<'context> {
return;
};

if the_trait.trait_bounds.is_empty() {
return;
}

let impl_trait = the_trait.name.to_string();
let the_trait_file = the_trait.location.file;

let mut bindings = TypeBindings::new();
for (param, arg) in the_trait.generics.iter().zip(trait_impl.resolved_trait_generics.iter())
{
// Avoid binding t = t
if !arg.occurs(param.type_var.id()) {
bindings.insert(
param.type_var.id(),
(param.type_var.clone(), param.kind(), arg.clone()),
);
}
}
jfecher marked this conversation as resolved.
Show resolved Hide resolved

// Note: we only check if the immediate parents are implemented, we don't check recursively.
// Why? If a parent isn't implemented, we get an error. If a parent is implemented, we'll
// do the same check for the parent, so this trait's parents parents will be checked, so the
Expand All @@ -1043,6 +1062,13 @@ impl<'context> Elaborator<'context> {
continue;
};

let parent_trait_bound = ResolvedTraitBound {
trait_generics: parent_trait_bound
.trait_generics
.map(|typ| typ.substitute(&bindings)),
..parent_trait_bound
};

if self
.interner
.try_lookup_trait_implementation(
Expand All @@ -1053,17 +1079,14 @@ impl<'context> Elaborator<'context> {
)
.is_err()
{
let mut the_trait = parent_trait.name.to_string();
if !parent_trait_bound.trait_generics.is_empty() {
the_trait.push('<');
the_trait.push_str(&parent_trait_bound.trait_generics.to_string());
the_trait.push('>');
}

let missing_trait =
format!("{}{}", parent_trait.name, parent_trait_bound.trait_generics);
self.push_err(ResolverError::TraitNotImplemented {
the_trait,
typ: trait_impl.object_type.to_string(),
impl_trait: impl_trait.clone(),
missing_trait,
type_missing_trait: trait_impl.object_type.to_string(),
span: trait_impl.object_type.span,
missing_trait_location: Location::new(parent_trait_bound.span, the_trait_file),
});
}
}
Expand Down
25 changes: 16 additions & 9 deletions compiler/noirc_frontend/src/hir/resolution/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use acvm::FieldElement;
pub use noirc_errors::Span;
use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic};
use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic, Location};
use thiserror::Error;

use crate::{
Expand Down Expand Up @@ -150,8 +150,14 @@ pub enum ResolverError {
AttributeFunctionIsNotAPath { function: String, span: Span },
#[error("Attribute function `{name}` is not in scope")]
AttributeFunctionNotInScope { name: String, span: Span },
#[error("The trait `{the_trait}` is not implemented for `{typ}")]
TraitNotImplemented { the_trait: String, typ: String, span: Span },
#[error("The trait `{missing_trait}` is not implemented for `{type_missing_trait}")]
TraitNotImplemented {
impl_trait: String,
missing_trait: String,
type_missing_trait: String,
span: Span,
missing_trait_location: Location,
},
}

impl ResolverError {
Expand Down Expand Up @@ -581,12 +587,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic {
*span,
)
},
ResolverError::TraitNotImplemented { the_trait, typ, span} => {
Diagnostic::simple_error(
format!("The trait `{the_trait}` is not implemented for `{typ}"),
String::new(),
*span,
)
ResolverError::TraitNotImplemented { impl_trait, missing_trait: the_trait, type_missing_trait: typ, span, missing_trait_location} => {
let mut diagnostic = Diagnostic::simple_error(
format!("The trait bound `{typ}: {the_trait}` is not satisfied"),
format!("The trait `{the_trait}` is not implemented for `{typ}")
, *span);
diagnostic.add_secondary_with_file(format!("required by this bound in `{impl_trait}"), missing_trait_location.span, missing_trait_location.file);
diagnostic
},
}
}
Expand Down
105 changes: 14 additions & 91 deletions compiler/noirc_frontend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod bound_checks;
mod imports;
mod name_shadowing;
mod references;
mod traits;
mod turbofish;
mod unused_items;
mod visibility;
Expand Down Expand Up @@ -3359,110 +3360,32 @@ fn error_if_attribute_not_in_scope() {
}

#[test]
fn trait_inheritance() {
fn arithmetic_generics_rounding_pass() {
let src = r#"
pub trait Foo {
fn foo(self) -> Field;
}

pub trait Bar {
fn bar(self) -> Field;
}

pub trait Baz: Foo + Bar {
fn baz(self) -> Field;
}

pub fn foo<T>(baz: T) -> (Field, Field, Field) where T: Baz {
(baz.foo(), baz.bar(), baz.baz())
}

fn main() {}
"#;
assert_no_errors(src);
}

#[test]
fn trait_inheritance_with_generics() {
let src = r#"
trait Foo<T> {
fn foo(self) -> T;
}

trait Bar<U>: Foo<U> {
fn bar(self);
}

pub fn foo<T>(x: T) -> i32 where T: Bar<i32> {
x.foo()
}

fn main() {}
"#;
assert_no_errors(src);
}

#[test]
fn trait_inheritance_with_generics_2() {
let src = r#"
pub trait Foo<T> {
fn foo(self) -> T;
}

pub trait Bar<T, U>: Foo<T> {
fn bar(self) -> (T, U);
}

pub fn foo<T>(x: T) -> i32 where T: Bar<i32, i32> {
x.foo()
fn main() {
// 3/2*2 = 2
round::<3, 2>([1, 2]);
}

fn main() {}
fn round<let N: u32, let M: u32>(_x: [Field; N / M * M]) {}
"#;
assert_no_errors(src);
}

#[test]
fn trait_inheritance_dependency_cycle() {
let src = r#"
trait Foo: Bar {}
trait Bar: Foo {}
fn main() {}
"#;
let errors = get_program_errors(src);
assert_eq!(errors.len(), 1);

assert!(matches!(
errors[0].0,
CompilationError::ResolverError(ResolverError::DependencyCycle { .. })
));
assert_eq!(errors.len(), 0);
}

#[test]
fn trait_inheritance_missing_parent_implementation() {
fn arithmetic_generics_rounding_fail() {
let src = r#"
pub trait Foo {}

pub trait Bar: Foo {}

pub struct Struct {}

impl Bar for Struct {}

fn main() {
let _ = Struct {}; // silence Struct never constructed warning
// Do not simplify N/M*M to just N
// This should be 3/2*2 = 2, not 3
round::<3, 2>([1, 2, 3]);
}

fn round<let N: u32, let M: u32>(_x: [Field; N / M * M]) {}
"#;

let errors = get_program_errors(src);
assert_eq!(errors.len(), 1);

let CompilationError::ResolverError(ResolverError::TraitNotImplemented {
the_trait, typ, ..
}) = &errors[0].0
else {
panic!("Expected a TraitNotImplemented error, got {:?}", &errors[0].0);
};

assert_eq!(the_trait, "Foo");
assert_eq!(typ, "Struct");
}
Loading
Loading