Skip to content

fix: trait where clause check fixes#9369

Merged
asterite merged 6 commits intomasterfrom
ab/trait-where-clause-associated-type-constraint
Jul 31, 2025
Merged

fix: trait where clause check fixes#9369
asterite merged 6 commits intomasterfrom
ab/trait-where-clause-associated-type-constraint

Conversation

@asterite
Copy link
Collaborator

@asterite asterite commented Jul 31, 2025

Description

Problem

Resolves #9367
Resolves #7024

Summary

First issue

The first issue has this code:

pub trait Foo<U> {}

pub trait Baz<T, U>
where
    T: Foo<U>,
{}

pub struct HasFoo1 {}
impl Foo<()> for HasFoo1 {}

pub struct HasBaz1 {}
impl Baz<HasFoo1, ()> for HasBaz1 {}

fn main() {}
  • For impl Baz<HasFoo1, ()> for HasBaz1 {} we'll need to see if T: Foo<U> holds, where T is HasFoo1 and U is ().
  • The constraint to solve, when debugging this, is T'3: Foo<U'4>
  • We can also see that after binding the ordered generics in Baz<HasFoo1, ()> we end up with these bindings: '4: (), '3: HasFoo1
  • We were correctly applying bindings to the constraint type (T) so it became HasFoo1, but we didn't do so for the entire constraint, meaning that the constraint remained as HasFoo1: Foo<U'4> and failed as there is no such generic impl.
  • The fix was to also apply bindings (well, substitute) to the trait bound (and generally to the entire constraint) so that the constraint becomes HasFoo1: Foo<()> which now holds.

Second issue

This is the code:

pub trait BarTrait {}

pub trait Foo {
    type Bar;
}

pub trait Baz<T>
where
    T: Foo,
    <T as Foo>::Bar: BarTrait,
{}

pub struct HasBarTrait1 {}
impl BarTrait for HasBarTrait1 {}

pub struct HasFoo1 {}
impl Foo for HasFoo1 {
    type Bar = HasBarTrait1;
}

pub struct HasBaz1 {}
impl Baz<HasFoo1> for HasBaz1 {}

fn main() {}
  • Here we start by checking Baz<HasFoo1>. For that we need to check that T: Foo and <T as Foo>::Bar: BarTrait hold.
  • We first check T'4: Foo where we having '4: HasFoo1 in bindings, so we end up checking HasFoo1: Foo which succeeds
  • Actually, because Foo has an associated type we check HasFoo1: Foo<Bar = <T as Foo>::Bar'6> where <T as Foo>::Bar'6 is an implicitly added named generic
  • In a recent PR we started replacing these implicitly added named generics with type variables so that the trait impl lookup succeeds. In this case we end up replacing <T as Foo>::Bar'6 with '10, so we are really checking HasFoo1: Foo<Bar = '10>, which succeeds.
  • Next we check the next constraint, <T as Foo>::Bar'6: BarTrait. The problem here is that this will fail as the constraint type is an unbound named generic. It's actually the implicitly added named generic!
  • The solution here is to first remember that we replaced <T as Foo>::Bar'6 with '10. We do this by storing it in bindings: '6: '10.
  • Next, after we check HasFoo1: Foo<Bar = '10>, try_lookup_trait_implementation will return these bindings: '10: HasBarTrait1. We were ignoring these returned bindings but not anymore: we add them to the existing bindings.
  • Finally, when we need to solve <T as Foo>::Bar'6: BarTrait we apply bindings. Because we have '6: '10, '10: HasBarTrait1 we end up solving HasBarTrait1: BarTrait which now holds! 🎉

Sorry for the long explanation but since I'm starting to understand how bindings work I wanted to convince myself that the code I'm writing makes sense.

Additional Context

As a bonus, if the where clause has this order:

where
    <T as Foo>::Bar: BarTrait,
    T: Foo,

it would fail in Noir but actually succeed in Rust. I don't know how they do it but I implemented something for that here: we reorder the where clause so that "simpler" constraint types come before more complex ones. In this case T is simpler than <T as Foo>::Bar so we end up putting before it, and then it works.

Documentation

Check one:

  • No documentation needed.
  • Documentation included in this PR.
  • [For Experimental Features] Documentation to be submitted in a separate PR.

PR Checklist

  • I have tested the changes locally.
  • I have formatted the changes with Prettier and/or cargo fmt on default settings.

@asterite asterite changed the title Ab/trait where clause associated type constraint fix: trait bound fixes Jul 31, 2025
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Test Suite Duration'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 76a61e5 Previous: 9846e1e Ratio
test_report_AztecProtocol_aztec-packages_noir-projects_aztec-nr 4 s 3 s 1.33
test_report_AztecProtocol_aztec-packages_noir-projects_noir-protocol-circuits_crates_rollup-lib 3 s 2 s 1.50

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

@asterite asterite requested a review from a team July 31, 2025 15:12
@asterite asterite changed the title fix: trait bound fixes fix: trait where clause check fixes Jul 31, 2025
Copy link
Contributor

@jfecher jfecher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@asterite asterite enabled auto-merge July 31, 2025 19:15
@asterite asterite added this pull request to the merge queue Jul 31, 2025
Merged via the queue into master with commit cb6b7e3 Jul 31, 2025
103 checks passed
@asterite asterite deleted the ab/trait-where-clause-associated-type-constraint branch July 31, 2025 19:59
github-merge-queue bot pushed a commit to AztecProtocol/aztec-packages that referenced this pull request Aug 4, 2025
Automated pull of nightly from the
[noir](https://github.com/noir-lang/noir) programming language, a
dependency of Aztec.
BEGIN_COMMIT_OVERRIDE
fix: forbid self-referencing type aliases
(noir-lang/noir#9103)
chore: add a mem2reg test for when all references need to be invalidated
(noir-lang/noir#9377)
fix(ssa): Do not check ArrayGet/Set as unreachable for Brillig
(noir-lang/noir#9376)
chore: use SSA parser in all mem2reg tests
(noir-lang/noir#9372)
fix: trait where clause check fixes
(noir-lang/noir#9369)
fix: Correct doc comments for SSA passes
(noir-lang/noir#9371)
fix: prevent `SignedField::from(i128::MIN)` from crashing
(noir-lang/noir#9366)
fix: allow constants in the type-system to be negative
(noir-lang/noir#9360)
feat: show circuit output as a value of the program's return type
(noir-lang/noir#9364)
feat: add `FunctionDefinition::visibility`
(noir-lang/noir#9363)
chore(docs): Add example for `$crate` in docs
(noir-lang/noir#9361)
fix: Prevent accidental tuple sharing in comptime code
(noir-lang/noir#9313)
fix: perserve purities after SSA normalization
(noir-lang/noir#9355)
fix: modulo overflow in comptime
(noir-lang/noir#9348)
fix: handle short-syntax for trait constraints on trait generics
(noir-lang/noir#9167)
chore: enhance trait constraint comment
(noir-lang/noir#9358)
fix: replace implicitly added named generics with fresh type vars in
check_trait_impl_where_clause_matches_trait_where_clause
(noir-lang/noir#9352)
fix: push definition trait constraints after trait item constraint
(noir-lang/noir#9354)
chore(ci): Update status of noir_json_parser
(noir-lang/noir#9351)
fix(ssa): Keep reference count increments for array set values
(noir-lang/noir#9344)
chore: remove unused `compile_workspace`
(noir-lang/noir#9353)
chore: try printing byte arrays as strings in the SSA interpreter
(noir-lang/noir#9346)
feat(lsp): allow opening noir stdlib files
(noir-lang/noir#9339)
fix: do u128 operations with u128, not i128
(noir-lang/noir#9345)
chore(acir): ACIR parser error handling for blackbox inputs/outputs
(noir-lang/noir#9342)
fix: prevent invalid types in test/fuzz functions
(noir-lang/noir#9343)
chore(lsp): avoid redundant type checking
(noir-lang/noir#9337)
feat(acir): Parse ACIR memory and call opcodes
(noir-lang/noir#9331)
fix(ssa_gen): Add constraint on slice length before popping
(noir-lang/noir#9323)
chore: impl for u16 conversions
(noir-lang/noir#9314)
fix: substitute bindings in type before canonicalization
(noir-lang/noir#9328)
fix(ssa_interpreter): `push_back` and `pop_back` to slices with padding
(noir-lang/noir#9320)
fix: wildcard type should be allowed in lambda parameter types
(noir-lang/noir#9325)
chore: graceful handling of SIGPIPE
(noir-lang/noir#9075)
feat: return unsolvable opcode from `CircuitSimulator`
(noir-lang/noir#8943)
fix: allow nested fmtstr (noir-lang/noir#9309)
feat: Initial ACIR parser (arithmetic exprs and black box functions)
(noir-lang/noir#9316)
fix(mem2reg): Register aliases when the `IfElse` result in a reference
(noir-lang/noir#9305)
fix: Make Ssa-gen use existing reference when compiling `&mut
foo.bar.baz` (noir-lang/noir#9307)
fix: top-level item in dependency isn't always visible
(noir-lang/noir#9295)
fix(ssa-interpreter): Return error if slice length is 0 during popping
(noir-lang/noir#9308)
chore: Release Noir(1.0.0-beta.9)
(noir-lang/noir#9184)
chore(LSP): simplify code lens request handling
(noir-lang/noir#9279)
chore: add regression tests for #6383
(noir-lang/noir#9302)
fix: disallow `_` in signatures and struct members
(noir-lang/noir#9301)
fix: check associated types after validating where clause when looking
up trait impls, plus some unification fixes
(noir-lang/noir#9265)
chore: Add fmtstr to coercions list
(noir-lang/noir#9300)
chore: Add a helper function `fmtstr::as_quoted_str`
(noir-lang/noir#9293)
chore(docs): Copy Type Coercions docs into v1.0.0-beta.8 versioned docs
(noir-lang/noir#9298)
feat: only inject "out of bounds" checks in brillig
(noir-lang/noir#9200)
END_COMMIT_OVERRIDE

---------

Co-authored-by: AztecBot <tech@aztecprotocol.com>
Co-authored-by: Maxim Vezenov <mvezenov@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Trait bound not satisfied when constraint refers to two generics Trait definition where clause doesn't support associated type constraints

2 participants