Skip to content

Identify annotation quantified variables by a system-unique ID - #313

Merged
coord-e merged 4 commits into
mainfrom
claude/gifted-bohr-3i88gy
Sep 30, 2026
Merged

coord-e merged 4 commits into
mainfrom
claude/gifted-bohr-3i88gy

Conversation

@coord-e

@coord-e coord-e commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Fixes #312.

A variable bound by forall/exists in an annotation was represented as Term::FormulaQuantifiedVar(Sort, String), tied to its quantifier by the source name alone. pre!(f(..))/post!(f(..), ..) build the closure's annotation formula with its parameters replaced by argument terms from the caller's annotation. When an argument contains the caller's quantified variable (such as the i in Option::map's spec exists(|i| opt == Some(i) && pre!(f(i)))) and the closure's formula has a quantifier with the same name, the two can't be told apart, and the inner quantifier binds both. Here is an example:

let half = thrust_macros::closure!(
    requires(exists(|i: i64| x == i + i)),
    |x: i64| -> i64 { assert!(x != 3); x },
);
let _ = Some(3).map(half);

This verified as safe, although the closure panics on 3.

Change

  • Term::FormulaQuantifiedVar(Sort, String) → Term::UserQuantifiedVar(Sort, UserQuantifiedVarId). Formula::Exists/Forall bind Vec<(UserQuantifiedVarId, Sort)>.
  • UserQuantifiedVarId is issued by System::new_user_quantified_var, so it is unique in the CHC system like PredVarId. The annotation translator gets one per quantified variable through Analyzer::generate_user_quantified_var.
  • SMT-LIB and debug output print the ID as q$<n>. The q$ prefix keeps these names apart from the generated clause variables, as in Keep spec quantifier binders from capturing clause variables #295.

Testing

  • With PCSat, the example above is rejected with Unsat, and Some(4).map(half) verifies. No test is added for it.
  • cargo test, cargo fmt --check and cargo clippy -D warnings pass locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_0188UiLozh1wCkmzZxJvzUTS

`pre!`/`post!` substitute the call's arguments into the closure's
annotation formula, and substitution goes under binders without
renaming them. When an argument mentions an outer binder, such as the
`i` in `Option::map`'s `exists(|i| opt == Some(i) && pre!(f(i)))`, and
the closure's formula binds a variable of the same name, the inner
binder captures it. A closure requiring `exists(|i: i64| x == i + i)`
then accepted any argument, so `Some(3).map(half)` verified although
the closure panics on it.

Name each binder after its HirId so no two annotations share a binder.

Fixes #312

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188UiLozh1wCkmzZxJvzUTS
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-30T22:36:46.542891Z 6d92151 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

A variable bound by `forall`/`exists` in an annotation was identified by
its source name alone, and `pre!`/`post!` put the closure's annotation
formula under the caller annotation's quantifier. A variable of the
caller's quantifier passed as an argument then met a quantifier of the
closure's formula with the same name, and was bound by it.

Replace `Term::FormulaQuantifiedVar(Sort, String)` with
`Term::UserQuantifiedVar(Sort, UserQuantifiedVarId)`, where the ID is
issued by the CHC system like `PredVarId`, so that variables of
different quantifiers are never confused.

Fixes #312

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188UiLozh1wCkmzZxJvzUTS
@coord-e coord-e changed the title Name spec quantifier binders uniquely across annotations Identify annotation quantified variables by a system-unique ID Sep 30, 2026
@coord-e

coord-e commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

test failed on 163b579 in tests/ui/fail/slice_split_first_mut_loop.rs: verification error: Timeout(60s) under PCSat, where Unsat is expected. The other 385 tests passed.

I don't think this PR causes it. The test has no forall/exists, and with comments stripped the SMT-LIB it emits is byte-identical on main and on this branch (776 lines). So the solver gets the same query either way. The same test passed on this PR's previous commit (54251fd) and in my local run. It has been unstable before: it was removed and restored in c8f600a, and its timeout was extended in bf7ddd3. No fix exists for the instability yet. I'm re-running the failed job once.


Generated by Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188UiLozh1wCkmzZxJvzUTS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188UiLozh1wCkmzZxJvzUTS
@coord-e
coord-e merged commit 87fca3b into main Sep 30, 2026
6 checks passed
@coord-e
coord-e deleted the claude/gifted-bohr-3i88gy branch September 30, 2026 22:43
coeff-aij added a commit to coeff-aij/thrust that referenced this pull request Oct 2, 2026
…antified variables take upstream's UserQuantifiedVarId

The fork's laws (closure_unnest, trait laws in pred_inst) and the candidate atoms'
skolem slots now use system-issued ids in place of name-keyed binders. Each id is
issued with the source name or law variable it stands for; with logging on
(RUST_LOG at info or below) the SMT-LIB2 output starts with a comment naming
what each q$<n> stands for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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

2 participants