Let a ghost term name generic- and Self-typed variables - #232
Draft
coord-e wants to merge 3 commits into
Draft
Conversation
coord-e
force-pushed
the
claude/generic-parameter-self-reference-8ak4yh
branch
from
August 20, 2026 06:35
149a0ea to
86fd516
Compare
coord-e
force-pushed
the
claude/generic-parameter-self-reference-8ak4yh
branch
from
August 20, 2026 11:49
86fd516 to
f981e67
Compare
coord-e
force-pushed
the
claude/generic-parameter-self-reference-8ak4yh
branch
from
August 20, 2026 11:54
f981e67 to
0878392
Compare
`invariant!` builds its `#[thrust::formula_fn]` from a closure and the context the closure was written in: the in-scope generics are re-declared on the function and instantiated via turbofish, `Self` becomes the impl's self type or a synthetic type parameter in a trait, and the receiver `self` becomes a `__thrust_self` parameter. None of that is particular to an invariant -- it is what any formula written inside a function body needs to survive being lifted out of it. Move it to `formula_fn_lifting`, which takes the parameters and the body and returns the item plus the expression naming it, and leave `invariant` with the part that is its own: reading the closure and emitting the marker call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ6XNNsSBdPkAzrWHftvqV
A formula lifted out of a function body becomes a free function, where `self` is not a legal parameter name, so one naming the receiver arrives under a synthetic name that stands for the value debug info records as `self`. The loop-invariant path knew that rule inline; the ghost path did not know it at all, and looked up the synthetic name as if a variable of that name were live. Name the rule and have both paths read the parameter through it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ6XNNsSBdPkAzrWHftvqV
A ghost term is lifted into a free `#[thrust::formula_fn]`, which inherits neither the
enclosing function's generics nor `Self`, so a term could only name variables of
concrete type. `#[thrust_macros::context]` already threads that context into
`invariant!`; thread it into `ghost!` too, through the same lifting:
#[thrust_macros::context]
impl Counter {
fn record(&mut self, x: i64) {
self.count += 1;
self.seen = thrust_macros::ghost!(
|self: &mut Self, x: i64| -> Seq<Int> { (*self).1.push(x) }
);
}
}
The introduced value is parameter `0` of the lifted function, so it passes through as
an ordinary parameter: a value type naming `Self` or a generic is rewritten along with
the rest, while the `__ghost_marker::<_, T>` turbofish keeps the type as written, the
marker call being in the host's own scope.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJ6XNNsSBdPkAzrWHftvqV
coord-e
force-pushed
the
claude/generic-parameter-self-reference-8ak4yh
branch
from
August 20, 2026 12:03
0878392 to
2d34fe7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows #220, on top of #231. Three commits, each standing on its own:
4154ba3invariant!carried intoformula_fn_lifting— no behaviour changeeeb87fb2d34fe7ghost!, plus testsThe gap
A ghost term is lifted into a free
#[thrust::formula_fn], which inherits neither the enclosing function's generics norSelf, so a term could only name variables of concrete type — the last of #220's known gaps.#[thrust_macros::context]already threads that context intoinvariant!; it now threads it intoghost!too.How it works
formula_fn_liftingholds what both macros need to lift a formula out of a body:Selfis rewritten to theimpl's self type, or to a synthetic type parameter instantiated with the realSelfin a trait;selfis renamed to a__thrust_selfparameter, which the analyzer resolves back throughannot_fn::lifted_param_source_name.For
ghost!the introduced value is parameter0, so it passes through that lifting as an ordinary parameter: a value type namingSelfor a generic is rewritten along with the rest, while the__ghost_marker::<_, T>turbofish keeps the type as written, since the marker call stays in the host's scope.Tests
ghost_selfselfghost_genericEach as a
pass/failpair; in both, thefailfile differs only in which live variable the term names.Known gaps
Selfin a generic or traitimplis unsupported, unchanged frominvariant!— both now go through the sameTODO, so a fix covers them together.