3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-08-02 20:23:27 +00:00
Commit graph

3786 commits

Author SHA1 Message Date
Nikolaj Bjorner
47ec61793c allow single argument versions of concat, union, intersect, xor for get_info 2026-08-01 10:54:40 -07:00
Nikolaj Bjorner
50f2747136 Add sequence length constraints to seq monadic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1a1a0632-f993-4bee-b4f8-a87f960836a1
2026-08-01 10:27:32 -07:00
Nikolaj Bjorner
4fb6b90630 Use expr refs for seq monadic atoms
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1a1a0632-f993-4bee-b4f8-a87f960836a1
2026-08-01 10:16:55 -07:00
Nikolaj Bjorner
6e1ca006f1 add undo trail 2026-08-01 10:09:56 -07:00
Nikolaj Bjorner
516a413559 guard against arity mismatch
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-08-01 09:35:52 -07:00
Nikolaj Bjorner
1ba30df028 seq_monadic: add unsat-core extraction with minimization toggle
check() now records the dependencies of an unsat subset in m_core, exposed via
ptr_vector<u_dependency> const& core(). On l_false it calls minimize_core:
with the new m_min_core flag (set_min_core, default true) it deletion-minimizes
to a minimal unsat subset containing only constraints that participate in the
contradiction; with the flag off it returns all membership dependencies.

Add unit tests asserting the core omits irrelevant constraints (e.g. x in a*,
x in ~a*, y in b* has core {x-constraints} only), exercising both flag states.
The test harness runs with minimization disabled by default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 57b9b87e-950a-49ea-bbb3-ed585646a5a9
2026-07-31 20:53:30 -07:00
Nikolaj Bjorner
1e09b4e6ae seq_monadic: replace solve_and with incremental add()/check() API
Replace the batch solve_and(mems) with an incremental interface:
  void add(expr* term, expr* regex, u_dependency* d);
  lbool check();
add() stores each (term, regex, dependency) triple in
m_memberships (vector<tuple<expr_ref, expr_ref, u_dependency*>>); the
dependency is retained for future unsat-core tracking and may be nullptr.
check() decides the accumulated conjunction (the former solve_and body) and
consumes the memberships; an empty conjunction is sat. Update the unit tests
and benchmark to the add()/check() API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 57b9b87e-950a-49ea-bbb3-ed585646a5a9
2026-07-31 20:31:44 -07:00
Nikolaj Bjorner
773c5496b9 seq_monadic: replace model out-param with gen-model toggle + get_model()
Remove the obj_map<expr,expr*>* model out-parameter from solve/solve_and/
decide_dnf. Add an m_gen_model flag (default true) with set_gen_model(bool),
store the extracted model in m_model (reset at the top of decide_dnf), and
expose get_model(). Switch live_states out to expr_ref_vector and the
product_nonempty state vectors to ptr_vector<expr>. Simplify the concat case
of parse_term with all_of. Disable model generation in the sat-only unit
tests and the benchmark.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 57b9b87e-950a-49ea-bbb3-ed585646a5a9
2026-07-31 20:14:59 -07:00
Nikolaj Bjorner
5650fb2460 Simplify seq_monadic: bool returns, is_uninterp_const, style cleanups
- decompose and live_states now return bool instead of a bool& ok out-param.
- Use is_uninterp_const(t) in parse_term.
- Use braced pair init and structured bindings over obj_map iteration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 57b9b87e-950a-49ea-bbb3-ed585646a5a9
2026-07-31 19:51:55 -07:00
davedets
4ee79af337
Disable "-Wnoctad-maybe-unsupported", add and fix "-Wdeprecated-copy-with-user-provided-copy". (#10332)
This is another PR towards the goal of getting Z3 to compile cleanly
when included via FetchContents into clang-tidy, which uses a pretty
strict set of warnings. (2 more flags after this!)

The first of these was -Wctad-maybe-unsupported. That has to do with
"class template argument deduction" -- the flag requires template
deduction guides to be explicitly provided if templated types are used
in situations that requires argument deduction. This fired for various
uses of templated types in the utils directory.

I decided that this should be a case of if it ain't broke, don't fix it,
and explicitly disabled the warning in the Z3 build (which will override
the setting if the flag is enabled in a larger build including Z3, like
clang).

-----

The second flag has to do with the C++ "rule of 3". Here is Google's AI
summary (inf_s_integer is a class in Z3 that triggered the warning):

_This warning means your inf_s_integer class defines a custom copy
assignment operator but lacks a user-defined copy constructor, which the
C++ standard deprecates to encourage the "Rule of Three". To fix this,
explicitly declare and = default the copy constructor in your class
definition._

_...example of how to fix..._

_This updates your code to modern C++ standards, cleanly silencing the
warning._

This seemed like a good standard to follow, and didn't require too many
changes, so I propose them.
2026-07-31 19:34:08 -07:00
Nikolaj Bjorner
6212042e80 Cache derivative_cofactors calls in seq_monadic
Memoize derivative_cofactors per regex in an owning cache so each regex's
cofactors are computed once per top-level solve. The cache is reset at the
start of solve()/solve_and() and freed in the destructor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 57b9b87e-950a-49ea-bbb3-ed585646a5a9
2026-07-31 19:20:52 -07:00
Nikolaj Bjorner
168b51d332 Remove var_extra from seq_monadic API
Per-variable extra constraints are now expressed as additional (var in R')
memberships passed to solve_and, so the var_extra parameter is dropped from
solve/solve_and and the internal decide_dnf. Tests updated to route extra
constraints through solve_and.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 57b9b87e-950a-49ea-bbb3-ed585646a5a9
2026-07-31 14:31:35 -07:00
Nikolaj Bjorner
656d1e6bcb Refactor guard_set out of seq_monadic into guard_set.h/.cpp
Extract the guard_set class (element-value set over derivative cofactor
guards) from the anonymous namespace in seq_monadic.cpp into a
self-contained guard_set.h / guard_set.cpp pair.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 57b9b87e-950a-49ea-bbb3-ed585646a5a9
2026-07-31 14:17:05 -07:00
Nikolaj Bjorner
568218e1ed add notes
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-31 13:59:27 -07:00
Margus Veanes
784ca5eb38
Fix constant element lifetime in seq monadic decomposition (#10329)
## Summary

Pin constant elements extracted while parsing sequence terms so they
remain alive throughout monadic decomposition.

Without these references, character ASTs could be reclaimed while still
stored as raw pointers in decomposition atoms, causing assertion
failures, fast-fail exits, and access violations during derivative
construction.

The fix eliminates 26 previously observed process failures in the regex
benchmark corpus. No stack-depth bailout is needed for these failures.
2026-07-31 13:20:44 -07:00
Margus Veanes
683cb4ec03
seq_monadic: add light Antimirov cofactor mode (#10323)
Light-weight Antimirov cofactors for seq_monadic
================================================

Overview
--------

The seq_monadic solver explores symbolic regex derivatives when
computing live
states and product transitions. Its original transition representation
used
Brzozowski cofactors.

This change adds a light-weight Antimirov representation. It first
computes the
existing Brzozowski cofactors and then decomposes targets whose outer
shape is

    s1 | ... | sn

or

    (s1 | ... | sn) . tail

into separate transitions. Concatenations are maintained in
right-associative
form, so a distributable union occurs as the head of the concatenation.
Targets are reconstructed with mk_regex_concat to preserve
normalization.

After decomposition, transitions with the same target are merged by
disjoining
their guards. The existing path-aware cofactor traversal and range
predicates
are therefore retained.

Example
-------

For

    r = .* a .{k}

the Brzozowski cofactors have the shape

    [a,  r | .{k}]
    [^a, r]

The light-weight Antimirov transformation produces

    [., r]
    [a, .{k}]

This preserves the language while avoiding the deterministic subset
states
that grow exponentially on this family.

Modes
-----

seq_monadic exposes two transition modes:

    light_antimirov   default
    brzozowski        retained as an explicit option

The implementation is seq_rewriter::light_ant_derivative_cofactors.

Correctness
-----------

The seq_monadic unit tests run in both modes. They cover character and
generic
element sequences, multiple and repeated variables, variable
constraints,
bounded loops, conjunctions of memberships, and witness construction. A
focused test checks the cofactor transformation above. The complete
94-test
unit suite used during evaluation passed. The benchmark harness is not
registered as a normal unit test; after detaching it, all 93 registered
tests
pass.

Benchmark evaluation
--------------------

The final optimized comparison used all 1,545 SMT2 files under
C:\git\bench\inputs\regexes. Each file was run in a separate process
with a
15-second timeout. There were 1,513 cases where both modes completed
without a
process failure or timeout.

                                    Brzozowski       Light-Ant
    paired solver time                 94.98 s          63.26 s
    median solver time                  1.734 ms         0.710 ms
    derivative calls                    5.70 M           3.39 M
    cofactors                           13.33 M           6.99 M
    live states                          2.74 M           0.44 M
    product states                     652.8 K         642.8 K

Light-Ant reduced paired solver time by 33.4%, derivative calls by
40.5%,
cofactors by 47.5%, and live states by 84.0%. It was faster on 1,091
cases;
Brzozowski was faster on 421 cases.

Light-Ant changed 131 Brzozowski undef results to sat. There were no
reverse
verdict changes and no mismatches against known sat/unsat statuses. Both
modes
had two timeouts. Process failures decreased from 30 to 27.

By corpus, paired solver time improved by 39.1% on ClemensRegex and by
11.9% on
MargusRegex.

Alternatives considered
-----------------------

Direct use of full Antimirov derivatives was also evaluated. It
introduced
large performance outliers, particularly around intersections, and
produced
additional undef results and timeouts. Disabling intersection-over-union
distribution improved some of these cases but remained slower and less
robust
than the light-weight transformation. The direct full-Ant mode was
therefore
removed from this change.

Copilot-Session: a2ce3573-4e15-4a4a-afb5-21e3cb04e4a2
2026-07-31 10:34:34 -07:00
Nikolaj Bjorner
7fde79f889 add facility to propagate all fixed instead of just changed 2026-07-30 20:28:28 -07:00
Nikolaj Bjorner
dbc1939ffa admit more ho pattern rules, prepare for cgr 2026-07-30 20:28:28 -07:00
Nikolaj Bjorner
52fbd1ca81 prepare ho-matcher for congruences
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-30 20:28:27 -07:00
Nikolaj Bjorner
639d7d147b
Update seq_monadic.cpp 2026-07-30 20:20:32 -07:00
davedets
c49eb07c3e
Fix remaining clang warnings for fallthrough switch cases (in debug build). (#10314)
https://github.com/Z3Prover/z3/pull/10284 changed the UNREACHABLE macro
in debug.h to use __builtin_unreachable() to indicate to the compiler
that the
code leading it should be considered unreachable.

In https://github.com/Z3Prover/z3/pull/10295 Copilot figured out that
this was wrong: the unreachable macro makes calls, which shouldn't be
elided, and whose arguments should be evaluated. It partially fixed the
problem by declaring invoke_exit_action to be `[[noreturn]]`. This
eliminated warnings in non-debug builds, but when Z3_DEBUG is enabled,
`UNREACHABLE` ends with an invocation of `INVOKE_DEBUGER()`. This can
call `invoke_debugger()`, which *can* actually return (so it can't be
given the `[[noreturn]]` attribute. So we still get warnings in debug
builds.

This PR fixes those, creating a Z3_unreachable_case macro, which is just
a combination of `UNREACHABLE()` and `Z3_fallthrough`. It uses that in
the places that give warnings.

It seemed better to me to still have these cases have a single macro,
rather than `UNREACHABLE` followed by an explicit `Z3_fallthrough`; this
seemed to me like it would confuse people -- "how can you fall through
if this code is unreachable?" But I'm open to alternative suggestions!
2026-07-30 19:24:46 -07:00
davedets
7502e3c409
Remove new instances of unused local vars and functions (#10309)
A few seem to have crept in.  Verified with both dbg and release builds.
2026-07-30 13:41:20 -07:00
Nikolaj Bjorner
5b12b607e2 Remove seq_split regex factorization module
Remove the seq_split (regex sigma-splitting / factorization) module and all
code that uses it:
- delete src/ast/rewriter/seq_split.{h,cpp} and src/test/seq_split.cpp
- drop the m_split member, include, and split/simplify_split/split_membership
  wrappers from seq_rewriter
- remove the regex factorization propagation block from seq_regex.cpp
- remove the seq.regex_factorization_enabled/threshold parameters and their
  theory_seq_params fields
- drop the files from the rewriter/test CMake lists and the test registry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 57b9b87e-950a-49ea-bbb3-ed585646a5a9
2026-07-29 15:38:21 -07:00
Margus Veanes
0972dd2141
seq_monadic: self-contained monadic-decomposition regex membership solver (generic elements, witnesses, Boolean combinations) (#10296)
## Summary

Adds `seq_monadic` (`src/ast/rewriter/seq_monadic.{h,cpp}`), a
self-contained,
rewriter-level decision procedure for regex membership of a term that is
a
concatenation of sequence variables and constant elements — e.g. `x·a·x
∈ R`,
including repeated and multiple variables. It uses a whole-language
*monadic
decomposition* plus automaton product-reachability; it is minterm-free
and does
**not** use Nielsen word-equation splitting or `seq_split`.

The component is **purely additive**: it is not wired into any solver
path, so
default behavior is unchanged. It ships with a unit test and an opt-in
benchmark
harness that is inert unless `Z3_SEQ_BENCH_DIR` is set.

## What it does

- `x·u ∈ R  ⇔  ⋁_q ( x reaches q in A_R  ∧  u ∈ q )` over the derivative
  automaton; `reach(q)` is never materialized as a regex (avoids the
state-elimination blowup). A variable's constraint is decided by a lazy
product-reachability search over tuples of derivative states, with
transitions
= the product of `brz_derivative_cofactors` branches and
pairwise-conjoined
  `seq::range_predicate` guards.
- **Generic in the element sort**: characters use the exact
`range_predicate`
algebra; any other element sort uses a candidate-basis over the element
values
the guards mention (sound and complete for the
`{true,false,=,<=,and,or,not}`
  guard grammar the derivatives emit).
- **Concrete witnesses**: on sat it reconstructs a witness value (a
sequence of
concrete elements, not predicates) per variable from the accepting
product path.
- **Boolean combinations**: `solve_and` decides a conjunction of
memberships
jointly, so a variable shared across memberships is constrained
consistently.
This is the natural extension since `¬(t∈R) ≡ t∈~R`, `∨` = union of
DNFs, and
  `∧` = product of DNFs.

Also de-duplicates the char-guard → `range_predicate` translator into a
single
public `seq::guard_to_range_predicate` in `seq_range_collapse` (it was
previously
duplicated there and in `seq_monadic`).

## Testing

- `tst_seq_monadic`: single / multiple / repeated variables, nested
complement,
bounded loops, per-variable constraints, a generic `(Seq Int)` section,
witness
  verification (substitute the model back and re-decide membership), and
  `solve_and` cases that are individually sat but jointly unsat.
- Full unit suite `test-z3 /a` passes (93/93).

## Evaluation (offline harness, not part of CI)

On a regex-membership benchmark corpus, restricted to files carrying a
genuine
`(set-info :status)`, the solver decides 318 and 316 are correct
(99.4%); the
only 2 disagreements are a length limitation (`|x|=2k`) that is outside
the
membership fragment.

## Known limitations / follow-ups

- Pathological deeply-nested, high-multiplicity regexes can overflow the
recursive derivative stack; a recursion-depth guard to degrade to
`unknown` is
  a natural follow-up.
- Out-of-fragment constraints (word equations, length / Parikh) are not
handled,
  by design — this decides regex membership only.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Bjorner <nikolaj@cs.stanford.edu>
Copilot-Session: 916db256-43c6-4067-b6f4-fa8d2cf2f37f
2026-07-29 15:16:54 -07:00
davedets
d46fbad3b6
Make implicit switch case fall-throughs explicit (#10284)
This is another PR towards the goal of getting Z3 to compile cleanly
when included via FetchContents into clang-tidy, which uses a pretty
strict set of warnings.

This PR enable the "-Wimplicit-fallthrough" warning, then fix all the
warnings this gets
in the clang build, by:
* Augmenting UNREACHABLE to add __builtin_unreachable(), which
suppresses
warnings for fallthrough in that cse.
* Adding Z3_fallthrough in many cases, to make it clear that
fallthroughs are intentional.
* Adding [[noreturn]] to functions that throw, so the compiler knows
they don't fall through to the next case.
* In a couple of cases, there's a fall-through to a default case, which
does "break", or "return nullptr". In those cases, I duplicated the
action in the preceding case, to make it more self-contained, and robust
in the face of change.

In some cases, I am concerned about whether the warnings are identifying
real bugs. For example, the fall-throughs in these files seem at least a
little suspect:
nnf.cpp
seq_rewriter.cpp
lar_solver.cpp 

while very probably correct, also seem at least a tiny bit suspect.
However, this PR does *not* attempt to change any behavior, only to
silence the warnings. It would be great if somebody with more knowledge
of the code could vet these cases. If vetted, the explicit presence of
the Z3_fallthrough would reassure future readers of the code that the
fall-through is intentional, not accidental.
2026-07-29 09:05:42 -07:00
Nikolaj Bjorner
1c89937473 term_enumeration: add tuple iterator over a vector of sorts
Expose enum_tuples(sorts) which produces an iterator over vectors of
terms, one term per input sort, dovetailing the per-sort streams so all
combinations are enumerated even when individual streams are infinite.

Each sort is enumerated by a self-contained sort_stream owning its own
grammar and bottom_up_enumerator seeded from the user productions, so
array sorts (with their fresh select ops and bound vars) do not collide
across sorts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cb1e958b-f89b-4407-958d-8e7ecf172bbc
2026-07-28 14:48:17 -07:00
Copilot
d652bd3fc6
Fix set.size soundness: cardinality lower bound for concrete distinct members (#10246)
`set.size` reported `sat` for `(= 1 (set.size (set.union (set.singleton
1) (set.singleton 2))))` — a soundness bug, not just a performance gap.
The returned model did not satisfy the constraint.

## Root cause

In `theory_finite_set_size::run_solver()`, the cardinality sub-solver
enumerates propositional models of the membership structure and sums
fresh "slack" variables to represent `set.size`. All slacks were bounded
only by `>= 0`, so the arithmetic solver could freely assign `size = 1`
even when two distinct concrete members were independently asserted:

- **Model M₁** (`{1}` active, `{2}` inactive) — element 1 is a concrete
witness → slack must be `≥ 1`
- **Model M₂** (`{1}` inactive, `{2}` active) — element 2 is a concrete
witness → slack must be `≥ 1`

Without this enforcement: `slack₁ = 1, slack₂ = 0` satisfied `size = 1`.
With it: `size ≥ 2`.

## Changes

- **`src/smt/theory_finite_set_size.cpp`** — In `run_solver()`, after
retrieving the propositional model, check if exactly one singleton
equivalence class is active *and* a positive `set.in` assertion exists
for that element in an active set. If so, assert `slack >= 1` instead of
`>= 0`. This is sound: the concrete member witnesses the slot contains
at least one element.

- **`src/ast/rewriter/finite_set_axioms.cpp`** — Add the missing
monotonicity lower bounds for union:
  ```
  |A ∪ B| ≥ |A|    and    |A ∪ B| ≥ |B|
  ```
These complement the existing upper bound `|A ∪ B| ≤ |A| + |B|` and let
the arithmetic solver derive `|{1} ∪ {2}| ≥ 2` directly from the
singleton axiom `|{i}| = 1`.

## Verified cases

| Query | Before | After |
|---|---|---|
| `(= 1 (set.size (set.union (set.singleton 1) (set.singleton 2))))` |
`sat`  | `unsat` ✓ |
| `(= 2 (set.size (set.union (set.singleton 1) (set.singleton 2))))` |
`sat` ✓ | `sat` ✓ |
| `set.in 1 s ∧ set.in 2 s ∧ (= 1 (set.size s))` | `sat`  | `unsat` ✓ |
| `¬(>= (set.size (set.union ...)) 2)` | `sat`  | `unsat` ✓ |

<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #10232

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Bjorner <nikolaj@cs.stanford.edu>
2026-07-27 15:05:49 -07:00
Copilot
c1fa2dbfd6
Fix elim-term-ite simplifier soundness: missing auxiliary variable constraints (#10244)
The `elim-term-ite` **simplifier** (via `set-simplifier` /
`addSimplifier`) was unsound: it reported `sat` with a spurious model on
trivially unsat inputs by replacing term-ITEs with fresh auxiliary
variables but never asserting their defining constraints, leaving them
unconstrained.

```smt2
(declare-const x Real)
(declare-const b Bool)
(set-simplifier elim-term-ite)
(assert (and (> x 10.0) (< x (ite b 1.0 2.0))))
(check-sat)
; was: sat (x=11 — spurious)
; now: unsat
```

## Changes

- **`src/ast/normal_forms/elim_term_ite.cpp` — `reduce_app`**: When
`defined_names::mk_name` returns `false` (name already cached for a
given ITE), the old code returned `BR_FAILED`, leaving the ITE
unreplaced in subsequent formulas. Now unconditionally sets `result =
new_r` and returns `BR_DONE`, mirroring the tactic version's behavior.

- **`src/ast/simplifiers/elim_term_ite.h` — `reduce()`**: After
rewriting each formula, the newly created definitions accumulated in
`m_rewriter.new_defs()` are now added to `m_fmls`. Previously these were
silently discarded, making auxiliary variables completely unconstrained.

<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #10239

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-27 08:22:57 -07:00
Copilot
f7fe461eb8
euf_arith_plugin: implement uminus instead of NOT_IMPLEMENTED_YET (#10243)
`euf-completion` crashed with an assertion violation on any unary
negation of a nonlinear product (e.g. `(= (- (* a a)) a)`), because
`register_node` hit `NOT_IMPLEMENTED_YET()` in the `is_uminus` branch.

## Changes

- **`src/ast/euf/euf_arith_plugin.cpp`**: Replace
`NOT_IMPLEMENTED_YET()` with the natural rewrite `-x ↦ (-1) * x`,
consistent with how subtraction is already handled (`x - y ↦ x + (-1 *
y)`). Creates the `-1` numeral of the correct sort, builds the
multiplication enode, and merges via `push_merge`.

- **`src/test/euf_arith_plugin.cpp`**: Add `test4` exercising the exact
repro shape — negation of a nonlinear product merged with a variable.

```smt2
(declare-const a Real)
(set-simplifier euf-completion)
(assert (= (- (* a a)) a))
(check-sat)  ; previously: ASSERTION VIOLATION — now: sat
```

<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #10240

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-27 08:21:56 -07:00
Nikolaj Bjorner
48f1676f2b Fix drain_backtrack to pop trail scopes and compile
The drain_backtrack destructor added in d247df72a called
m_backtrack.pop(), which does not exist on ptr_vector (breaking the
build) and, even as pop_back(), would only drop the work item without
popping the backtracking trail scope it owns - reintroducing the
trail-scope leak that #10196 fixed. Use backtrack(), which pops the trail
scope for in_scope items and then removes the item.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 96a14756-2ffe-4cc3-87e7-49fda1b6113a
2026-07-23 11:19:12 -07:00
Nikolaj Bjorner
d247df72a2 drain backtrack trail using scoped guarantees
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-23 10:19:32 -07:00
Nikolaj Bjorner
c9d3dc959d Fix trail-scope leak in ho_matcher on early exit (#10196)
The higher-order matcher shares the solver's trail stack. On early-exit
paths (resource/rlimit reached via m.inc(), or search budget exhausted)
work items were left on m_backtrack with their pushed scopes never popped,
leaking a trail scope. This desynchronized the trail scope count from the
solver scope level and later tripped SASSERT(ilvl <= m_scope_lvl) /
unsound backtracking in smt::context::reinit_clauses.

Restore the trail before exit on every path using an RAII guard
(scoped_trail_level) and drain the backtrack stack after the main search
loop so m_backtrack is left empty for the next call. This avoids wrapping
large blocks in try/catch (which would mask underlying bugs such as
creating non-well-sorted expressions) while still guaranteeing the trail
is restored on both normal and exceptional exits. The sort-mismatch check
and UNREACHABLE() in refine_ho_match are preserved so ill-sorted terms
remain surfaced rather than silently discarded.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 96a14756-2ffe-4cc3-87e7-49fda1b6113a
2026-07-23 10:19:32 -07:00
1sgtpepper
53fa8f9cc4
Fix unsigned BV-to-FP exponent narrowing (#10189)
## Summary

Include the equal-width case in symbolic unsigned BV-to-FP exponent
saturation,
preventing positive exponents from being interpreted as negative by the
rounder.

Adds regression coverage for overflow and in-range values.

Fixes #7135.

## Testing

- `./build-review/test-z3 fpa`
- `./build-review/test-z3 /a` (92 passed)
- Symbolic-versus-numeral differential matrix (50 cases across five
width boundaries and all rounding modes)
- Exact-head fork `CI` and `OCaml Binding CI` workflows (passed)
2026-07-23 08:57:15 -07:00
davedets
35b0b42d2e
Use macros to disable semi-colon warnings for blocks of macros. (#10192)
This is another PR towards the goal of getting Z3 to compile cleanly
when included via FetchContents into clang-tidy, which uses a pretty
strict set of warnings.

This PR completes the job started by
https://github.com/Z3Prover/z3/pull/10169. It adds `-Wextra-semi` to the
set of CLANG_ONLY_WARNINGS, and adds
```
START_DISABLE_EXTRA_SEMI_WARNING;
...macro invocations with trailing semis...
END_DISABLE_WARNING;
```
around all the blocks of macro invocations that provoked warnings.

(Additionally, in realclosure.h, there was one block of macro
invocations that did *not* follow the trailing-semi pattern; changed
that to look like all the others).
2026-07-22 18:01:50 -07:00
Copilot
847ee63b2d
Fix invalid model from int_to_bv missing modular reduction in bv2int_translator (#10185)
With `smt.bv.solver=2`, `int_to_bv(x)` was translated to the raw integer
`x` without normalizing it to `[0, 2^N)`. Bitwise operations like
`bvxor(A, B)` are translated as `A + B - 2·band(A, B)`, which is only
valid when both operands are in `[0, 2^N)`. When `x` was negative or ≥
2^N, the LP solver could assign a band value inconsistent with bit
semantics, producing a model that fails validation.

## Changes

- **`OP_INT2BV` translation** (`translate_bv`): Apply `umod(e, 0)`
instead of passing the raw integer argument through. This normalizes the
value to `[0, 2^N)` before it participates in any bitwise arithmetic.

  ```cpp
  // Before
  case OP_INT2BV:
      r = arg(0);  // raw integer, may be negative or ≥ 2^N

  // After
  case OP_INT2BV:
      r = umod(e, 0);  // normalized to [0, 2^N)
  ```

- **`amod` shortcut**: Add a fast-path for `mod(t, N)` when the divisor
already equals `N` — the result is already in `[0, N)`, so wrapping it
again with another `mod(_, N)` is unnecessary and avoids expression
bloat.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-22 10:34:23 -07:00
Copilot
9ac438b199
Fix nested symbolic re.range under re.++ ignoring solver timeout (#10181)
Symbolic `re.range` nested under `re.++` caused Z3 to loop indefinitely,
ignoring timeouts. Direct symbolic range membership was fixed
previously, but the nested case reaches a different code path — the
Brzozowski derivative engine in `derive_range`.

## Root cause

`derive_range` in `seq_derive.cpp` only handled concrete unit-string
bounds. For symbolic bounds it returned a stuck `re.derivative(ele,
re.range(lo, hi))` term. When nested under concatenation, this stuck
term cycled infinitely:

1. `is_nullable` of the stuck term → `is_nullable_symbolic_regex` →
emits `re.in_re("", re.derivative(...))`
2. That `in_re` triggers `propagate_in_re` → new `accept` predicate
3. New `accept` computes another derivative → another stuck term →
repeat

## Fix

Replace the stuck fallback with a proper symbolic ITE. By SMT-LIB
semantics, `re.range(lo, hi)` accepts character `c` iff both bounds are
single-character strings and `lo[0] ≤ c ≤ hi[0]`. The derivative with
respect to `ele` becomes:

```
ite(len(lo)=1 ∧ len(hi)=1 ∧ lo[0] ≤ ele ∧ ele ≤ hi[0], ε, ∅)
```

Length conditions are omitted for bounds already known to be concrete
single-character strings.

```smt2
(set-logic ALL)
(declare-const s String)
(assert (str.in_re "a" (re.++ re.all (re.range s "c"))))
(check-sat)
; Previously hung indefinitely; now returns sat in ~10ms
```

## Changes

- **`src/ast/rewriter/seq_derive.cpp`** — `derive_range`: replace stuck
`re.derivative` fallback with symbolic ITE using `str.nth_i` and length
guards for non-concrete bounds
- **`src/test/seq_rewriter.cpp`** — add solver-level regression test
(case 21) for nested symbolic `re.range` under `re.++`

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-22 10:30:45 -07:00
davedets
e2df18faaf
Test PR for disabling semicolon warnings (#10169)
This is another PR towards the goal of getting Z3 to compile cleanly
when included via FetchContents into clang-tidy, which uses a pretty
strict set of warnings.

https://github.com/Z3Prover/z3/pull/10020 deleted a number of
unnecessary (and, by a strict interpretation, illegal) semicolons. These
were detected by adding -Wextra-semi to CLANG_ONLY_WARNINGS during
testing. The PR did not eliminate all such unnecessary/illegal
semi-colons; Nikolaj Bjorner argued that some IDEs would be confused by
doing so for macro invocations, which otherwise look like function
calls. So it left those, and did not include adding -Wextra-semi to
CLANG_ONLY_WARNINGS in the PR.

I'm worried that not having -Wextra-semi will allow instances of the
semis we'd like to eliminate to creep back in. This PR introduces a
mechanism to disable the warnings for regions with such macro
invocations, and uses that mechanism for one file. If accepted, a
subsequent PR would use this mechanism in all the remaining places, so
that there is a clean clang build with -Wextra-semi.

I verified that:
* It compiles cleanly for if the enable/disable macros have null
definitions, as they would for non-clang compilations.
* It compiles cleanly for a clang build without -Wextra-semis.
* It compiles successfully, albeit with *many* warnings, for a clang
build with-Wextra-semis -- but none of those errors are for the regions
in ast.h where the warning is disabled.
2026-07-22 10:28:13 -07:00
Michael Tautschnig
09aaadf963
Sequence AST-creating arguments in rewriters for cross-compiler determinism (#10165)
### Problem

The order of evaluation of function arguments is unspecified in C++
(arguments are indeterminately sequenced since C++17). Compilers use
this freedom differently:

```c++
static int f(int i) { printf("%d ", i); return i; }
static void g(int, int, int) { printf("\n"); }
int main() { g(f(1), f(2), f(3)); }
```
| compiler/target | output |
|---|---|
| gcc 13, x86_64 | `3 2 1` |
| gcc 13, aarch64 | `1 2 3` |
| clang 18, x86_64 | `1 2 3` |

Z3 has many call sites where **two or more arguments each create AST
nodes**, e.g. (before this PR, `bv_rewriter.cpp:876`):

```c++
result = m.mk_ite(c, m_mk_extract(high, low, t), m_mk_extract(high, low, e));
```

The two extract nodes are hash-consed and receive their AST ids in
evaluation order, so the id assignment differs between
compilers/targets. AST ids feed heuristic tie-breaking throughout the
solver (`bool_rewriter`'s `m_order_eq` equality-operand ordering,
id-based sorts in `array_rewriter`, case-split ordering, ...), so
**byte-identical input takes different solver paths depending on the
compiler and architecture z3 was built with**.

### Evidence

Investigated while chasing cross-platform proof-time instability in
CBMC/mldsa-native CI (diffblue/cbmc#8991), on byte-identical ~12 MB SMT2
instances (bit-vectors + arrays + quantifiers), with the `string_hash`
fix from #10163 applied to isolate this effect. Z3 4.15.3, gcc 13 on
x86_64 Linux and aarch64 Linux (Graviton):

* one instance: **17 s on x86_64 vs 1633 s on aarch64** (both `unsat`; a
sibling instance shows the reverse direction). Run-to-run within one
host: ±1 %.
* Instrumenting `ast_manager::register_node_core` with an order
fingerprint (running hash over `(node hash, node id)`) shows both
architectures construct **identical AST sequences up to registration
#41,789**, where x86_64 creates `(extract[0:0] #xFFFFFFFF)` before
`(extract[0:0] #xFFFFFFFE)` and aarch64 the other way around — from
identical call stacks at the `mk_ite`-over-two-`mk_extract` site quoted
above. All divergence between the two hosts flows from such events
(pointer/ASLR effects experimentally excluded: fingerprints are
invariant under `setarch -R` and across repeated runs).
* Sequencing that one site by hand moved the first divergence to
#248,118 — the analogous `mk_ite(c, mk_select(...), mk_select(...))`
site in `array_rewriter.cpp`. Sequencing that one, too, moved it to
#248,411, inside `nnf:👿:process_iff_xor` — i.e. the next layer of
the same onion.
* With the whole `ast/rewriter` layer swept (this PR), the instrumented
builds produce **identical AST construction traces on both architectures
throughout the entire rewriter phase** of this 546k-line industrial
instance; the first divergence left is the NNF one.

### Fix

Following the precedent of 37904b9e8, e113d39aa, 360193098, 93ff8c76d,
9b88aaf13 ("parameter evaluation order", `bool_rewriter`/`seq_rewriter`)
and the existing comments in `seq_rewriter.cpp` ("introduce temporaries
to ensure deterministic evaluation order..."), this PR hoists
AST-creating arguments into named temporaries with a defined evaluation
order, across `src/ast/rewriter/` — 126 call sites in 17 files. The
transformation is purely sequencing: it selects one of the two valid C++
evaluation orders and makes it the same everywhere. (Temporaries are raw
pointers in rewriter-local scope, matching the precedent commits;
nothing can trigger GC between creation and consumption.)

The sites were found with a small AST-argument scanner (statement-level
call sites whose argument list contains ≥ 2 top-level arguments that
each contain an AST-creating call); I am happy to share/contribute the
script. Known remaining work, deliberately out of scope here to keep the
diff reviewable:

* 41 sites in `src/ast/rewriter/` that need manual treatment (inside
`if` conditions, ternaries, or multi-statement expressions) — list
available on request;
* `src/ast/normal_forms/nnf.cpp` (`process_iff_xor`, proven divergent by
the trace above), ~7 sites in `src/ast/simplifiers/`, ~3 in
`src/ast/converters/`, ~9 in `src/ast/`;
* other theory/solver layers (`src/smt/`, `src/sat/`, ...) — divergences
there only matter after search starts, where paths have usually already
split, but a full sweep would be needed for bit-reproducibility across
compilers.

Together with #10163, this is a step towards z3 builds whose behaviour
does not depend on the compiler or target architecture — which matters
for verification CI that runs identical proofs on heterogeneous
platforms and expects comparable runtimes.

---------

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
2026-07-21 19:03:02 -07:00
z3prover-ci-bot[bot]
7f7f8ae59b
[snapshot-regression-fix] fpa: fp.to_bv of large-exponent values should be unspecified, not unknown (#10174)
## Summary

Fixes a completeness regression where a **satisfiable** floating-point
problem is answered `unknown`.

- **Originating discussion:**
https://github.com/Z3Prover/bench/discussions/3232
- **Benchmark:** `iss-3199/bug-1.smt2` (from
https://github.com/Z3Prover/z3/issues/3199)

### Divergence

```diff
--- bug-1.expected.out (expected)
+++ produced (current z3)
@@ -1 +1 @@
-sat
+unknown
```

The benchmark:

```smt2
(declare-fun x () (_ FloatingPoint 40 60))
(declare-fun y () (_ BitVec 8))
(assert (= ((_ fp.to_ubv 8) RTP x) y #x00))
(check-sat)
```

`(get-info :reason-unknown)` reports `"exponents over 31 bits are not
supported"`.

### Root cause

The problem is clearly `sat` (e.g. `x = 0.0`, `y = #x00`; and for any
out-of-range `x`, `fp.to_ubv` is unspecified so `= #x00` is
satisfiable). z3 bit-blasts and finds a model that assigns `x` a value
with a very large binary exponent (the `FloatingPoint 40 60` sort has a
40-bit exponent field). During model reconstruction,
`fpa_util::is_considered_uninterpreted` performs a range check by
calling `mpf_manager::to_sbv_mpq`, which **throws** `"exponents over 31
bits are not supported"` when the unpacked exponent does not fit into an
`int` (guard added in `eacde16b` for #3199). The tactic framework
catches that exception and turns the whole result into `unknown`,
instead of treating the conversion as out-of-range/unspecified.

### Fix

A finite FP value whose (unbiased) binary exponent is at least the
target bitwidth `bv_sz` has magnitude `>= 2^bv_sz` and therefore cannot
fit into a `bv_sz`-bit signed or unsigned integer — the conversion is
unspecified. Short-circuit this case using the exponent, before reaching
`to_sbv_mpq`, in the two range-check paths:

- `src/ast/fpa_decl_plugin.cpp`
(`fpa_util::is_considered_uninterpreted`) — return `true` (considered
uninterpreted / out of range).
- `src/ast/rewriter/fpa_rewriter.cpp` (`fpa_rewriter::mk_to_bv`) —
return the unspecified result.

This is consistent with the existing precise logic for every exponent it
covers (those values are already classified out-of-range), and it avoids
materializing an astronomically large integer. It does not affect
in-range or boundary conversions.

### Validation

Built z3 from this checkout (Python/`make` build) and re-ran the
benchmark:

- `z3 -T:20 inputs/issues/iss-3199/bug-1.smt2` now prints `sat`,
matching the recorded `bug-1.expected.out` oracle.

Additional manual sanity checks (all correct, no regression):

- `fp.to_ubv 8` of `5.0` -> `#x05`
- `fp.to_sbv 8` of `-5.0` -> `#xfb` (-5)
- `fp.to_sbv 8` of `128.0` -> unspecified (`sat`, out of range)
- `fp.to_sbv 8` of `-128.0` -> `#x80` (-128, in-range boundary
preserved)

Opened as a **draft** for human review.




> [!WARNING]
> <details>
> <summary>Firewall blocked 1 domain</summary>
>
> The following domain was blocked by the firewall during workflow
execution:
>
> - `pypi.org`
>> To allow these domains, add them to the `network.allowed` list in
your workflow frontmatter:
>
> ```yaml
> network:
>   allowed:
>     - defaults
>     - "pypi.org"
> ```
>
> See [Network
Configuration](https://github.github.com/gh-aw/reference/network/) for
more information.
>
> </details>


> Generated by [Fix a Z3 snapshot-regression
divergence](https://github.com/Z3Prover/bench/actions/runs/29804584327)
· 314.9 AIC · ⌖ 20.3 AIC · ⊞ 10.7K ·
[◷](https://github.com/search?q=repo%3AZ3Prover%2Fz3+%22gh-aw-workflow-id%3A+snapshot-regression-fixer%22&type=pullrequests)

<!-- gh-aw-agentic-workflow: Fix a Z3 snapshot-regression divergence,
engine: copilot, version: 1.0.65, model: claude-opus-4.8, id:
29804584327, workflow_id: snapshot-regression-fixer, run:
https://github.com/Z3Prover/bench/actions/runs/29804584327 -->

<!-- gh-aw-workflow-id: snapshot-regression-fixer -->
<!-- gh-aw-workflow-call-id: Z3Prover/bench/snapshot-regression-fixer
-->

Co-authored-by: z3prover-ci-bot[bot] <305651407+z3prover-ci-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-21 18:55:46 -07:00
z3prover-ci-bot[bot]
396147851e
[fixer-selftest] Fix typo in euf_ac_plugin.cpp comment: "betwen" → "between" (#10141)
## Automated workflow self-test

This PR is an **automated workflow self-test** of the `fixer-selftest` /
`snapshot-regression-fixer` pipeline running on the self-hosted
`rise-runner-1` pool. Its purpose is to verify that Copilot inference
runs and that the `create-pull-request` safe output can open a real
**draft** pull request on `Z3Prover/z3`.

### The fix

- **File:** `src/ast/euf/euf_ac_plugin.cpp` (line 995)
- **Before:** `// add difference betwen dst.l and src.l to both src.l,
src.r`
- **After:** `// add difference between dst.l and src.l to both src.l,
src.r`

A single spelling mistake (`betwen` → `between`) in a `//` code comment.

### Why no rebuild is needed

The change is **comment-only** — no code, string literals, identifiers,
or build files were touched — so it cannot affect z3's behaviour and
requires no compilation or testing.

### For maintainers

This is a genuine, correct fix, so feel free to **merge** it. Equally,
you may simply **close** it — the success of the self-test does not
depend on this PR being merged.




> Generated by [Self-test the agentic PR pipeline with a tiny z3 comment
fix](https://github.com/Z3Prover/bench/actions/runs/29523479037) · 54.5
AIC · ⌖ 18.9 AIC · ⊞ 8K ·
[◷](https://github.com/search?q=repo%3AZ3Prover%2Fz3+%22gh-aw-workflow-id%3A+fixer-selftest%22&type=pullrequests)

<!-- gh-aw-agentic-workflow: Self-test the agentic PR pipeline with a
tiny z3 comment fix, engine: copilot, version: 1.0.65, model:
claude-opus-4.8, id: 29523479037, workflow_id: fixer-selftest, run:
https://github.com/Z3Prover/bench/actions/runs/29523479037 -->

<!-- gh-aw-workflow-id: fixer-selftest -->
<!-- gh-aw-workflow-call-id: Z3Prover/bench/fixer-selftest -->

Co-authored-by: z3prover-ci-bot[bot] <305651407+z3prover-ci-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-16 11:33:30 -07:00
Nikolaj Bjorner
436237bfbb fixup unit tests
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-16 09:35:18 -07:00
Nikolaj Bjorner
694a72c785 push quantifiers
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-16 08:38:28 -07:00
Nikolaj Bjorner
9945e3dc9a add Margus' unfold-fold operation and consolidate range-predicate recognizer/constructor.
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-15 20:44:46 -07:00
Nikolaj Bjorner
2db625606d fold functionality into seq_range_collapse
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-15 20:04:48 -07:00
Nikolaj Bjorner
7c8c6a4df0 fixup pattern inference 2026-07-14 21:01:15 -07:00
Nikolaj Bjorner
e2b9e3a6dc distribute quantifiers over Booleans 2026-07-14 09:57:04 -07:00
Nikolaj Bjorner
8f2713bdb1 Add array eta-reduction rewrite: (lambda (x*) (select a x*)) -> a
Sound by array extensionality when a is independent of the bound
variables. Implemented as array_rewriter::mk_lambda_core and wired into
th_rewriter::reduce_quantifier alongside the ground-lambda case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 09:24:16 -07:00
Copilot
038b367d68
Fix non-termination in mod rewriter for symbolic modulus (#10105)
Combining `mod0`/`div0` quantifier axioms with a mod-idempotency
quantifier caused Z3 to loop forever. The core issue was that
`mk_mod_core` in `arith_rewriter.cpp` only handled rewrite rules for
*numeral* moduli, leaving two gaps for symbolic `y`:

1. `mod(a + k*y, y)` was not reduced to `mod(a, y)`, so `(not (= (mod (+
a b) b) (mod a b)))` stayed unreduced and caused the nlsat solver to
spin.
2. The E-matching pattern `(mod (mod x y) y)` fired on every new term it
produced, creating an unbounded chain of nested `mod` expressions.

```lisp
; Previously non-terminating, now returns unsat immediately
(assert (forall ((x Int)) (! (= (mod0 x 0) 0) :pattern ((mod0 x 0)))))
(assert (forall ((x Int)) (! (= (div0 x 0) 0) :pattern ((div0 x 0)))))
(assert (forall ((x Int) (y Int))
  (! (= (mod (mod x y) y) (mod x y)) :pattern ((mod (mod x y) y)))))
(assert (not (= (mod (+ a b) b) (mod a b))))
(check-sat)
```

## Changes

- **`src/ast/rewriter/arith_rewriter.cpp` — symbolic summand
elimination**: In `mk_mod_core`, when the modulus is a non-numeral
integer and the dividend is an `add`, strip any summand equal to the
modulus or an integer multiple of it. Soundness: `k*0 = 0` for all `k`,
so the rule holds even at `y = 0`. This immediately collapses the
reported formula to `false`.

- **`src/ast/rewriter/arith_rewriter.cpp` — symbolic idempotency via
ite**: Extend the existing `mod(mod(x,y), y) → mod(x,y)` rule
(previously numeral-only) to symbolic `y` by rewriting to `ite(y=0,
mod(mod(x,0),0), mod(x,y))`. The `y=0` branch uses a numeral divisor,
which is excluded by the `!v2.is_zero()` guard, halting the E-matching
chain.

- **`src/test/arith_rewriter.cpp`**: Regression tests for `mod(a+y, y) =
mod(a,y)`, `mod(a+2y, y) = mod(a,y)`, and `mod(mod(a,3),3) = mod(a,3)`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 09:20:03 -07:00
Copilot
98e1f5ca2d
Fix assertion violation in bv2int_translator with smt.bv.solver=2 (#10109)
`ASSERTION VIOLATION` at `bv2int_translator.h:72` when using
`smt.bv.solver=2` with formulas containing `abs` (or other arith
expressions that rewrite to ITE with arith predicates).

**Root cause**

`ensure_translated` skips adding sub-expressions of boolean non-BV nodes
to the `todo` list — correct, since the base theory owns them in plugin
mode. However, `translate_expr`'s early-return only covered
`basic_family_id` booleans, not non-basic ones (e.g.,
`arith_family_id`).

When `(abs f)` is rewritten by the arith rewriter to `(ite (>= f 0) f (-
f))`, the predicate `(>= f 0)` ends up in `todo` (as a child of the
ITE), but its own children (e.g., the integer literal `0`) are not
added. `translate_expr` then calls `translated(0)` on an unmapped
expression, firing `SASSERT(r)`.

Reproducer:
```smt2
(declare-const f Int)
(assert (= 0 (mod 0 (bv2nat ((_ int_to_bv 1) (abs f))))))
(check-sat)
; z3 test.smt2 smt.bv.solver=2  →  ASSERTION VIOLATION (before fix)
```

**Fix**

Extend the early-return in `translate_expr` to match
`ensure_translated`'s skip condition — all boolean non-BV expressions in
plugin mode map to themselves:

```cpp
// before
if (m_is_plugin && ap->get_family_id() == basic_family_id && m.is_bool(ap)) {

// after
if (m_is_plugin && m.is_bool(ap) && ap->get_family_id() != bv.get_family_id()) {
```

BV boolean predicates (`bvule`, etc.) are unaffected — they still route
through `translate_bv`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 21:56:12 -07:00
Copilot
634b2886ba
fix: declare type variables before use in solver display output (#10103)
`decl_collector::visit_sort` did not collect sorts with `poly_family_id`
(type variables created via `mk_type_var` / `declare-type-var`), so
`solver::display` and `ast_pp_util::display_decls` never emitted type
variable declarations before referencing them — producing invalid
SMT-LIB2 output.

## Changes

- **`src/ast/decl_collector.h`**: added a dedicated `lim_svector<sort*>
m_type_vars` field (separate from `m_sorts`) with a `get_type_vars()`
getter; `reset()` clears it; `push()`/`pop()` maintain its scope.

- **`src/ast/decl_collector.cpp` — `visit_sort`**: sorts with
`poly_family_id` are now pushed to `m_type_vars` instead of `m_sorts`,
keeping type variables distinct from uninterpreted sorts:

```cpp
if (m.is_uninterp(n))
    m_sorts.push_back(n);
else if (fid == poly_family_id)
    m_type_vars.push_back(n);
```

- **`src/ast/ast_pp_util.h`**: added a `stacked_value<unsigned>
m_type_vars` cursor to track which type variables have already been
printed.

- **`src/ast/ast_pp_util.cpp` — `display_decls`**: emits
`(declare-type-var <name>)` for each collected type variable before
other sort declarations; `reset()`/`push()`/`pop()` maintain the new
cursor.

**Example** — given `(declare-type-var A)(declare-fun f (A) A)`, the
dump now correctly produces:

```smt2
(declare-type-var A)
(declare-fun f (A) A)
(assert ...)
```

The output round-trips cleanly through the Z3 parser.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 20:56:00 -07:00