Conversation
coeff-aij
commented
May 24, 2026
coeff-aij
commented
May 24, 2026
Comment on lines
+1836
to
+1837
| pub forall_sorts: Vec<ForallSortIdx>, | ||
| pub num_forall_sort_idx: ForallSortIdx, |
Collaborator
Author
There was a problem hiding this comment.
forall_sorts is actually redundant because we know it only contains the range 0..num_forall_sort_idx.
coeff-aij
force-pushed
the
forall-sort
branch
4 times, most recently
from
May 29, 2026 06:19
2f6c77f to
9824e3a
Compare
coeff-aij
force-pushed
the
forall-sort
branch
3 times, most recently
from
June 3, 2026 15:27
d459b92 to
4f84b7a
Compare
constrained with trait bounds
…<T>` for `T` in the body)
The example was Unsat on its own account, independently of the predicate routing bug fixed in the previous commit: `Y(-1)` violated `repeat`'s precondition `T::p(*x)` at `repeat(&mut y, 5)`. Call `y.g()` first, whose `ensures(Self::p(!self))` establishes the precondition. With both fixed, PCSat still times out inferring the invariant of the loop in the generic `repeat`, so spell it out the way simple_loop_self_mut.rs does: `T::p(*b)` plus the prophecy link `!b == !x.at_entry()`, with `x` rebound to `b` so the invariant can name both the current `&mut` and the entry value. The fail twin tests/ui/fail/traits/simple_loop_call_multi.rs drops the `y.g()` call again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
simple_loop_self_mut.rs put `requires(Self::p(*self, !self, x))` on `f`, a precondition on the callee's own prophecy. Each `a.f(v)` in the loop reborrows `a` with a fresh, universally quantified final value, so no invariant can establish that for an abstract `p` (the commit adding the file, 52ad0ae, already marked it as not supported). Use the `&mut` analogue of simple_loop_self.rs instead: `p(self, x)` with `requires(Self::p(*self, x))` and `ensures(Self::p(!self, result))`. PCSat still needs the loop invariant spelled out, so the loop carries `invariant!(|b: &mut T, ...| T::p(*b, v) && !b == !a.at_entry())` with `a` rebound to `b`, because an `invariant!` cannot name both views of the same `&mut` argument. Its fail twin uses `v = b.f(v) + 1`. The simple_loop_self twin weakens the precondition to `true`; the rebuilt PCSat refutes it in 0.3s where the previous build timed out. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The tests keep their header comment describing which adapter pattern each one isolates; the prefix added nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A generic function's spec calling `<Bar<T> as Foo>::valid(..)`, the predicate of a generic impl on a type that still contains the owner's type parameter, resolves through `Instance::try_resolve` to the impl item and uses its `define-fun` body; only a call on the type parameter itself (`T::valid`) falls back to a forall predicate. This is the intended behaviour of the routing changed in 4b4f0ac; the pair records it (both the qualified-path and the `Bar::<T>::valid` spelling resolve the same way). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The impl predicate bodies of these tests are SMT strings that refer to the inner type's trait predicates by their hash-suffixed names (`q_p_<hash><a0>`). The hash comes from the def path, which includes the crate name and hence the file name, so 323a3b8 left every reference stale and PCSat failed with `q_p_<old hash><a0> is not bound`. Regenerated with .experimental/extract-predicate-hashes.py for the eight affected pairs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The file, type and function names say what each test isolates; drop the "Probe:" headers and the inline explanations, keep the one-line Rust renderings of the SMT predicate bodies, and shorten the one remaining FIXME. Remove option_field_reborrow_refmut: it only worked around a solver timeout that the current PCSat build no longer has and matches no adapter pattern. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
They were written to report macro and annotation typing problems, not to guard behaviour, and the problems they describe are settled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each pass file verifies against the `Iterator` specification it declares, and its fail twin breaks only the property under test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Ghost<T>` was known to the analyzer only through `impl<T: Model> Model for Ghost<T>`, so `resolve_model_ty` had to normalize `<Ghost<T> as Model>::Ty` to learn anything about it. In a function generic over `T` that normalization fails -- without a `T: Model` bound rustc cannot select the impl -- and the fallback hands back the unmodeled Rust type, a `PhantomData` newtype, which lowers to the singleton sort `(own (),)`. A parameter of singleton sort is not bound in `relate_sub_param_types`, while the refinement lifted from `#[requires(g == v)]` still names it, so building the entry obligation panicked with `unbound var $0`. Mark the struct and lower it structurally, the way `Closure<T>` already is: in the logic a `Ghost<T>` is its content, so `model_adt` returns the content type directly and no trait selection is involved. The `Model` impl stays, since a specification parameter still lowers to `<Ghost<T> as Model>::Ty` and has to name it; a TODO on both sides records that the two have to agree. The `fail` twin still reports nothing: a generic function's parameter predicates never occur in a clause head, so its body is discharged vacuously and its callers constrain a separate pair -- the same gap the `fn_poly*` tests sit in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
two_loops.rs and multi_params.rs time out while the loop invariants are being inferred; these variants supply them by hand, as annot_simple_loop_self.rs does for simple_loop_self.rs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The inference versions time out; these supply the inner loop's invariant by hand, as annot_simple_loop_self.rs does for simple_loop_self.rs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… args Generic function calls at concrete type arguments built a fresh function type whose predicate variables were never constrained: the body was analyzed once, with placeholder args, constraining a different set of predicate variables. Call sites therefore learned nothing about the callee, so assertions on returned values were left unchecked (unsound). Re-analyze the monomorphized body at each concrete instantiation (DefTy::Generic now uses DeferredDefMode::Analyze) so the fresh contract predicate variables are constrained by the body. Basic-block types are registered per analysis instance (AnalysisKey), so nested analyses of the same def (e.g. recursive generics) no longer clobber each other, and calls still carrying type parameters keep using the placeholder contract. This fixes the known-bug in adt_generic_enum_helper_return and makes 18 previously silently-accepted fail tests report Unsat.
`InstantiationKey` carries `caller_def_id`, so a generic body is re-analyzed once per (type arguments, calling function) rather than once per monomorphization. Record why it cannot simply be dropped -- the caller's `owner_fn_id` is what interprets a `ParamTy`'s index, so removing it would silently conflate type parameters declared in different items -- and what would have to change for this to become a monomorphization cache. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two shapes that no test covered, both of which a change to how a def is routed between the concrete and the generic analysis would silently alter. A generic function whose signature does not mention its type parameter is analyzed with a concrete contract, so a caller that is itself generic still learns its result. Routing such a def to the generic analysis instead would leave the contract to a re-analysis that a call at type arguments which are still type parameters never triggers, and the caller would accept anything from that call onwards. An annotated generic function contributes its contract at such a call site, where an inferred one does not. The existing fn_poly_annot tests all call from `main` at concrete arguments, which is the case that does not distinguish the two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`collect_forall_defaults` decided which `default_<sort>` constants to declare by scanning the stored clause AST for `Term::ForallDefault`, and treated `Term::ArrayEmpty` as a leaf. But the SMT-LIB2 writer synthesises `Term::default_for(elem)` for an empty array at print time, so an array over an abstract element sort emitted `default_a0` with no declaration and CoAR rejected the file with `default_a0 is not bound`. Ask `default_for` itself which defaults the writer will reference, so the two cannot drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… impl Lifting a formula out of an impl method replaced the type `Self` with the impl's self type, turning the projection `Self::Item` into `<Wrap<I>>::Item`. `Self` in a trait impl says which trait to look in; a bare ADT does not, so rustc rejected the lifted `#[thrust::formula_fn]` with `E0223 ambiguous associated type` — and the type annotations of nearby ghost terms failed to infer as a consequence. Carry the implemented trait along and emit `<Wrap<I> as Iterator>::Item`. The trait-method branch is unaffected: it substitutes a type parameter, whose bounds resolve the projection on their own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Registering the contract of an `Fn`-bounded type parameter read the parameter's index in the generics of the function being analysed. A bound declared on an impl block was taken straight from `predicates_of(impl)`, which no instantiation has touched, so an external call into that impl looked the impl's own parameter up in the caller's table and hit `unknown type param idx`. Resolve such a parameter through the arguments the analysis runs with before using it, for both the sort it builds and the key it registers under. A parameter that resolves to another generic caller's parameter is now recorded against that one; a parameter that resolves to a concrete callable needs no parameter-keyed contract at all, since the callable carries its own. `build_closure_type_for_param` tried to do this by instantiating the `ParamTy` itself, which is an identity by construction -- instantiating a `ParamTy` can only yield a `ParamTy`, never the concrete argument. Bind the type instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An `FnMut` closure receives its upvars behind a `&mut`, and its precondition took that whole `Mut` pair. The pair's prophecy is still unconstrained where the precondition has to be discharged -- the borrow resolves it only after the call -- so `pre!(f(..))`, which names the upvars as they are, could never reach the obligation the call raises, and every `FnMut` precondition needed a `forall` over the prophecy before it said anything at all. Take the current value of the upvars in the precondition instead. A precondition is a property of the state the call starts from, and the two states stay related by the postcondition, which is unchanged. `closure_trait_call` now reports the `Fn` trait it resolved, so the projection keys off that rather than off the shape of the receiver type. Specs that spelled the receiver out as `Mut::new(f, g)` still mean what they meant, since the projection drops `g`. The two-call tests gain the precondition they were missing: their upvars have to satisfy it in every state the calls start from, which needs one binder now instead of two. Their `fail` twins keep their old specification -- with the quantified precondition the solver does not answer the negative direction within 180s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twelve pass/fail pairs whose specifications verify in both directions on this branch: a ghost `Seq` grown by a loop (`ghost_seq_loop`, and its `forall` variant), the same history carried through a trait loop (`traits/ghost_produced`, `traits/ghost_step_chain`, `traits/ghost_count`), fold specified with an `Fn` and that history (`traits/fold_fn_ghost`, `_noiter`, `_call`, and `_call_law`, the last discharging a call site by assuming an induction principle), and Map with the closure's precondition stated three ways (`traits/map_fn_uncond_pre` unconditionally, `traits/map_fn_concrete_item` at a concrete item type, and `traits/map_ext_total_pre` over the produced history with a preservation law). Each was run here after copying, all 24 files green. The remaining exploration on iterator-adapters is not adopted: `traits/map` and `traits/map_fn` are the superseded non-inductive Map invariant and still Unsat, and `traits/fold`, `traits/fold_fn`, `traits/fuse`, `traits/map_no_closure` and `traits/skip` have no fail twin to pin the other direction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The twin used to drop the empty-history precondition, which is the base case of the loop invariant: it goes Unsat even when nothing carries `item_ok` across an iteration, so it never pinned the property the pass file is named for. Removing `next`'s `item_ok` postcondition instead takes away the fact's only source. Weakening the loop invariant or shifting the postcondition's index both leave the solver at Unknown -- refuting those needs it to reason about every interpretation of an abstract predicate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`trait_assoc_type_spec` asked Thrust to prove `nonempty`'s postcondition from an empty default body. A default method is checked once against the abstract predicate, so the claim has to hold for every implementor, and nothing in the trait says `produces` is non-empty -- the `pass` file was claiming a capability Thrust does not have (re-checking default bodies per impl) rather than pinning one it does. Declaring `nonempty` without a body moves the obligation to the impl, where `produces` is concrete, and the fail twin's `false` predicate still refutes it. `trait_default_method_spec` keeps the rejected shape as its own pair, so the distinction between the two is pinned rather than lost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An empty array at a type-parameter element sort needs some value of that sort to sit in the cells past the length. The value is arbitrary and nothing may observe it, but the query still has to name it, and the name was spelled `(declare-const default_aN aN)`. Under `(set-logic HORN)` a top-level `declare-const` is a symbol the solver is being asked to *build*, and there is no way to build a value of an uninterpreted sort, so the query stopped at the parser instead of reaching the solver at all. The universal reading is the one that matches the intent: nothing may observe the padding, so the clauses have to hold whatever it is. Spell it as a nullary `declare-forall-fun`, which is the form the solver already provides for a constant of an abstract sort. This is what kept a generic `FromIterator<T> for Vec<T>` from being verified: `Seq::singleton(i)` at an abstract `Item` reaches the empty array, so the whole collect query died at the parser. Also let the sort collector recurse into the default term the writer synthesises for an empty array, matching the collector that decides which defaults to declare. The two disagreed; the emitted declarations are identical across every tracked test either way, so this only keeps them from drifting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Repeated runs of thrust-rustc on the same input could emit .smt2 files that differed only in the numbering/ordering of universally quantified clause variables and in the order of forall-fun (predicate variable) and datatype declarations. The declared and clause set was always the same; only how it got labeled and ordered varied between runs. Three call sites iterated a hash-based container to decide the order in which fresh output identifiers get assigned, and Rust's default hasher is randomly seeded per process: - `bind_locals` returned function-parameter bindings in a `HashMap`; `type_return`/`type_goto`/`install_inherited_bb_ty` iterated it to assign clause variables, so the same parameter could get a different `TermVarIdx` on every run. Switched to a `BTreeMap` keyed on the (`#[orderable]`) `FunctionParamIdx`, so variables are always assigned in parameter-index order. - `register_enum_defs`'s `EnumCollector` gathered referenced enum `DefId`s in a `HashSet` before registering their datatype declarations; registration order is emission order. Switched to `FxIndexSet`, which keeps first-visit order instead of hash order. - `refine_local_defs` collected trait-method spec keys (refined first, so their predicate variables get the lowest numbers) in a `HashSet`. Switched to a `Vec`; each key is only ever added once, so this only drops the incidental hash order, not any deduplication the analysis relied on. Each change swaps only the container type, not what gets inserted into it or when — the fix is at the point where output identifiers are assigned, not in the smt2 printer, so no reordering of meaning happens downstream of it. Verified with `THRUST_SOLVER=/bin/false` (no docker, no solver): all 168 tests/ui/pass and 164 tests/ui/fail files dump byte-identical `thrust_output.smt2` across 5 runs each, and a 10-run check across four differently-shaped inputs (a two-parameter leaf function, a generic trait impl, a many-predicate function, and a generic trait method) comes back identical too. Before the fix, the same 5-run sweep on main (2bf022d) showed 45 of the 168 pass files varying. For all 45, the datatype/predicate-declaration/clause/define-fun counts and total line counts against this branch's output match exactly, and the raw diffs that motivated the fix are consistent variable-identity swaps (e.g. a clause's `v21`/`v22` trading places along with the equations that name them), never an added, dropped, or altered declaration or clause. No measured performance cost: a 168-file no-solver sweep takes the same ~19.7s on both this branch and main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`3a3fbb1` stabilised the assignment of output identifiers, which is enough for byte-identical dumps on `main`. On this lineage it is not: 108 of the 477 `tests/ui` files still emitted a different `.smt2` on every run. Two containers decide an emission order and neither was touched by that commit, both holding `ForallPred`s in a `HashSet`: - `System::forall_pred_vars` is iterated to emit the `declare-forall-fun` blocks, so the blocks permuted. - `System::compute_dependency`'s value sets are handed to `DepExistsPredVarDef`, whose `Display` iterates them to list a `declare-dep-exists-fun`'s dependencies, so those lists permuted. Both become `BTreeSet`, which orders a `ForallPred` by its symbol and then by its type-parameter and parameter sorts. The symbol is built by `refine::stable_def_id_symbol` as `q_<name>_<def-path hash>`, so the key is a function of the predicate's own definition path: it does not move between runs, and — unlike a `DefId` index or an insertion counter — it does not move when an unrelated item elsewhere in the crate is added or reordered either. The dependency sets are ordered where they are built rather than sorted in `fmt`, so the printer still just walks what it is handed. With this, every container the printer iterates is a `Vec`, an `IndexVec` or a `BTreeSet`; the `HashMap`s left in `System` (`type_params_reverse`, and `compute_dependency`'s outer map) are only ever looked up by key. Verified with `THRUST_OUTPUT_DIR` and `THRUST_SOLVER=/bin/false` (no solver, no docker), honouring each file's `//@compile-flags`, with the comparator counting an absent dump as its own outcome rather than as agreement: all 477 `tests/ui` files dump byte-identical `.smt2` across 11 runs in three sweeps spanning two rebuilds, and 0 produced no output. Before, the same sweep showed 108 varying. Content is unchanged. Comparing every one of the 477 dumps before and after, once the dependency lists are sorted for comparison, the whole multiset of lines is identical, and so are the declaration set, the clause count, the `define-fun`/`define-fun-rec` counts and the total line count. 383 files are byte-identical either way; the other 94 are the same lines in a different order, with the `assert` sequence itself unmoved. The pre-existing variance was ordering only: across 5 runs of the old binary, no file's set of `q_*`, `p<N>`, `a<N>`, `v<N>` or datatype identifiers differed. No measured performance cost: a single-run 477-file sweep takes 73.6s with the ordering imposed against 75.5s without it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
upstream 9569c71 brings native SMT sequences: `Seq<T>` lowers to `(Seq T)` with `seq.++` / `seq.unit` / `seq.nth` / `seq.len` / `seq.extract` instead of the `(array, length)` tuple, and the hand-written `seq_concat` definition the writer used to emit is gone. Fourteen files conflicted. How each was resolved: src/chc/smtlib2.rs (1 hunk) -- upstream deletes the `define-fun-rec seq_concat` loop, and the forall-predicate definitions this branch prints sit inside the same removed block. Kept the forall-predicate loop, dropped the seq_concat definitions. src/chc/format_context.rs (2 hunks) -- same shape. Kept this branch's `ArrayEmpty` arm, which recurses into `Term::default_for(elem)` so an empty array's abstract padding is declared, and took upstream's new `SeqEmpty` arm beside it; dropped the `SeqConcat` arm and the `seq_concat` formatter, and kept `forall_pred`. src/chc.rs (3 hunks) -- combined: `Sort::Forall` stays in the scalar arm of `walk` while `Sort::Seq` joins `Box`/`Mut`; `instantiate_params` keeps this branch's `forall_sort_resolver` parameter and gains a `Sort::Seq` arm; `SeqConcatTerm` is deleted with upstream. src/rty.rs (2 hunks) -- took HEAD, which drops upstream's `free_ty_params` and `unify_ty_params` on `Type`. The conflict presents an empty HEAD side, so it reads as an upstream addition, but these were deliberately removed on this branch when type-parameter unification was replaced (6e3bf1b). Restoring them does not compile: the per-constructor `free_ty_params` / `unify_ty_params` on PointerType, TupleType, ArrayType, EnumType and FunctionType no longer exist, and taking upstream's side yields thirteen E0599s. Nothing in the merged tree calls either function; the only references are their own recursive calls. tests/ui/{pass,fail}/{slice_first_mut,slice_last_mut,slice_methods, slice_methods_mut,vec_2}.rs (1 hunk each) -- one line every time, this branch's `COAR_IMAGE=coar:latest` against upstream rewriting the same `//@rustc-env:` line without a pin. Took upstream's: every one of these ten is now a native-sequence test, and the pinned image predates the sequence support, so keeping the pin would guarantee a parse abort rather than pin anything. Running them locally now needs a sequence-capable COAR_IMAGE in the environment. This commit does not build on its own; the two follow-ups complete it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`collect_forall_defaults` is this branch's, and upstream never touched it, so
the merge took it unchanged and it kept an arm for a term that no longer
exists. Dropping `SeqConcat` is forced; adding `SeqEmpty` is the choice.
`ArrayEmpty` recurses into `Term::default_for(elem)` because an empty array
has to name a padding value, and at a type-parameter element sort that value
is a forall default which must then be declared. `SeqEmpty` deliberately does
not: the native empty sequence prints as `(as seq.empty (Seq X))`, which is
well-sorted with no padding value, so there is nothing to collect.
`Sort::instantiate_params`, resolved in the merge, is the other half of this
and is worth naming because it would not have failed to compile. Its match
ends in `_ => {}`, so had the `Sort::Seq` arm been left out, a type parameter
occurring under a sequence sort would simply not have been instantiated. The
conflict happened to cover that arm; nothing else would have caught it.
The rest of the branch survives the two new variants. Every other match on
`chc::Sort` or `chc::Term` that ends in a catch-all is either upstream's, and
already handles `Sort::Seq` explicitly ahead of it (`fmt_sort_impl`,
`Function::sort`), or is a question a sequence answers correctly by falling
through: `deref` and `tuple_elem` panic, `as_tuple` / `as_datatype` /
`into_datatype` return `None`, and `is_singleton` is false for a sequence
whose length is not fixed. `Sort::walk` lists every constructor and has no
catch-all at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Sort::instantiate_params` ended in `_ => {}`. Every constructor that carries
a sort inside it was already named, so the arm only ever caught the scalars,
but nothing made that true: adding a constructor with a sort inside it would
have compiled and silently left type parameters under it uninstantiated.
Native sequences were exactly that case. `Sort::Seq` needed its arm, and it
got one only because the surrounding hunk happened to conflict during the
merge. Nothing else would have reported it.
Naming `Null | Int | Bool | String` costs nothing and makes the compiler
report the next one. `Sort::walk_impl` next door already reads this way.
Emission is unchanged: every ui test dumped with the driver built before and
after this commit, 468 queries, byte-identical, exit codes identical.
The other eleven catch-all arms over `chc::Sort` or `chc::Term` in the tree
are left alone. All eleven are upstream's own code and carry no local
divergence, so enumerating them would only widen the diff and cost a conflict
at the next merge; upstream already handles `Sort::Seq` explicitly ahead of
the catch-all where it matters (`fmt_sort_impl`, `Function::sort`), and the
rest are partial accessors and printers that a sequence answers correctly by
falling through.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
This PR introduces an extension to SMT-LIB2 to encode unknown type parameters (like
Tinf<T: PartialOrd>()) as a universally quantified sort.For example, a universally quantified sort
a0is first declared using(declare-forall-sort a0)and then used everywhere like a normal sort.This feature lays the groundwork for the future verification of generic functions involving unknown types.
Example