Skip to content

Propagate Self trait bound into invariant_context formula fns#131

Merged
coord-e merged 2 commits into
mainfrom
claude/epic-pasteur-7buhri
Jun 16, 2026
Merged

Propagate Self trait bound into invariant_context formula fns#131
coord-e merged 2 commits into
mainfrom
claude/epic-pasteur-7buhri

Conversation

@coord-e

@coord-e coord-e commented Jun 15, 2026

Copy link
Copy Markdown
Owner

follow-up of #99

Problem

A loop invariant inside a trait default method can refer to Self, the trait's associated types (Self::Item) and its predicates (Self::step). #[thrust_macros::invariant_context] rewrites such an invariant! into _invariant_with_context!, which lowers it to a free #[thrust::formula_fn] where Self is replaced by a synthetic generic __ThrustSelf (instantiated with the real Self via turbofish, legal in expression position).

The rewrite was incomplete:

  • SelfRewriter only visited the formula function's parameters, leaving Self untouched in the body and in the propagated where-clause predicates.
  • It only matched the bare path Self, not the leading segment of Self::Item / Self::step.
  • The synthetic __ThrustSelf carried no bound beyond Model — in particular not the host's implicit Self: Foo trait bound.

So an invariant naming a trait associated type or predicate failed to compile.

Fix

In expand_invariant:

  • Rewrite Self__ThrustSelf in the body and the propagated where-clause predicates too, not just the parameters.
  • SelfRewriter now rewrites the leading segment of any path, covering both bare Self and qualified Self::Item / Self::step.
  • Mirror the host's implicit Self: Trait bound onto the synthetic generic, so the trait's associated types and predicates stay resolvable.
  • Drop where-predicates that the rewrite duplicates against the synthetic generic's own Model bounds.

Tests

Paired UI tests exercising a Self-typed loop invariant in a trait default method (associated type + predicate):

  • tests/ui/pass/loop_invariant_trait_self.rs — verifies (//@check-pass).
  • tests/ui/fail/loop_invariant_trait_self.rs — too-strong invariant, fails with Unsat.

Both fail to compile without the fix (E0401), confirming they cover the bug.

Verification

Ran the full suite locally with the pinned CI toolchain (Z3 4.13.0 + the pinned COAR/PCSat image via Docker):

  • cargo test234 passed (was 232; +2 new tests), no regressions.
  • cargo fmt --all -- --check — clean.
  • cargo clippy --all-targets -- -D warnings — clean.

https://claude.ai/code/session_01YDgGqc9Prd1Vqjy2p2Ns8H


Generated by Claude Code

claude and others added 2 commits June 15, 2026 16:38
A loop invariant inside a trait default method is rewritten into a free
`formula_fn` in which `Self` is replaced by a synthetic generic
(`__ThrustSelf`) instantiated with the real `Self` via turbofish. The
rewrite previously only touched the formula function's parameters, and the
synthetic generic carried no bound beyond `Model`. As a result an invariant
that named a trait associated type (`Self::Item`) or called a trait
predicate (`Self::step`) failed to compile (E0220 / E0599 — or E0401, since
`Self` was left untouched in the body and where clause).

Fix `expand_invariant` to:

- rewrite `Self` to the synthetic generic in the body and the propagated
  where-clause predicates too (not just the parameters), and rewrite the
  leading segment of qualified paths like `Self::Item` / `Self::step`;
- mirror the host's implicit `Self: Trait` bound onto the synthetic generic
  so the trait's associated types and predicates stay resolvable;
- drop where-predicates that the rewrite duplicates against the synthetic
  generic's own `Model` bounds.

Add paired pass/fail UI tests exercising a `Self`-typed loop invariant in a
trait default method.

https://claude.ai/code/session_01YDgGqc9Prd1Vqjy2p2Ns8H
@coord-e coord-e force-pushed the claude/epic-pasteur-7buhri branch from 7cd1949 to addc3c6 Compare June 16, 2026 14:06
@coord-e coord-e marked this pull request as ready for review June 16, 2026 14:07
@coord-e coord-e requested a review from Copilot June 16, 2026 14:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: addc3c614f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

rewriter.visit_where_predicate_mut(pred);
}
def_params.push(quote!(#synth));
def_wheres.extend(type_lowering.model_where_predicates_for(&synth));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add Model bounds for rewritten Self projections

When an invariant closure parameter itself uses an associated type, e.g. |item: Self::Item| in a trait default method, the rewrite above turns it into __ThrustSelf::Item and lower_params will emit <__ThrustSelf::Item as thrust_models::Model>::Ty. This block only adds Model bounds for __ThrustSelf, not for any rewritten projections, so such invariants still fail to compile unless the trait separately constrains every associated type as Model; the macro needs to propagate projection bounds for the rewritten closure parameter types as well.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

will be fixed later

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes #[thrust_macros::invariant_context] expansion for loop invariants inside trait default methods by correctly propagating Self usage into the generated #[thrust::formula_fn] context, ensuring associated types and trait predicates remain resolvable after lowering.

Changes:

  • Rewrite Self__ThrustSelf not only in invariant formula parameters, but also in the formula body and propagated where-predicates.
  • Enhance SelfRewriter to rewrite the leading Self segment in qualified paths (e.g., Self::Item, Self::step).
  • Add UI tests covering Self-typed loop invariants in trait default methods (pass + expected Unsat fail).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
thrust-macros/src/invariant.rs Expands Self rewriting to body/where-preds, updates SelfRewriter, and mirrors the enclosing trait bound onto __ThrustSelf.
tests/ui/pass/loop_invariant_trait_self.rs New passing UI test for trait default method invariant referring to Self-trait predicate.
tests/ui/fail/loop_invariant_trait_self.rs New failing UI test expecting Unsat for an intentionally too-strong invariant in the same scenario.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +191 to +197
if let Some(FnOuterItem::ItemTrait(item_trait)) =
context.and_then(|context| context.outer.as_ref())
{
let trait_ident = &item_trait.ident;
let (_, ty_generics, _) = item_trait.generics.split_for_impl();
def_wheres.push(syn::parse_quote!(#synth: #trait_ident #ty_generics));
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

will be fixed later

@coord-e coord-e merged commit a2fcf1d into main Jun 16, 2026
7 checks passed
@coord-e coord-e deleted the claude/epic-pasteur-7buhri branch June 16, 2026 14:16
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.

3 participants