3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-15 03:25:43 +00:00
z3/src/tactic
Lev Nachmanson 22c779c77c
[snapshot-regression-fix] Fix elim_uncnstr disabled by manager-wide has_type_vars() flag (iss-6260/small-2) (#10063)
## Summary

Fixes a completeness regression where `elim_uncnstr` was silently
disabled for ordinary (non-polymorphic) goals, detected by the
`snapshot-regression` corpus.

- **Originating discussion:**
https://github.com/Z3Prover/bench/discussions/3054
- **Benchmark:** `iss-6260/small-2.smt2` (corpus `Z3Prover/bench`,
`inputs/issues/iss-6260/`)
- **Divergence:** recorded oracle `sat` → current z3 produces `unknown`

### Divergence diff

```diff
--- small-2.expected.out (expected)
+++ produced (current z3)
@@ -1,3 +1,3 @@
-sat
+unknown
 (error "line 17 column 0: unexpected character")
 (error "line 17 column 1: unexpected character")
```

(The `(error ...)` lines are expected: the benchmark contains a stray
```` ``` ```` fence on line 17. Only the `sat` → `unknown` change is the
regression.)

## Root cause

`git bisect` over the regression window pins the flip to commit
`208cc5686` ("fix build"), which added `|| m().has_type_vars()` to the
`elim_uncnstr_tactic` guard and an equivalent `if (m.has_type_vars())
return;` to the `elim_unconstrained` simplifier.

`ast_manager::has_type_vars()` is a **manager-wide, sticky** flag: it is
set to `true` as soon as *any* type variable is created, and is never
reset. In particular `finite_set_decl_plugin::init()` creates type
variables `A`/`B` to define its polymorphic signatures. Those type
variables never occur in the user's assertions, but once the finite_set
plugin is initialized — which happens while processing this benchmark —
the flag is globally `true` (confirmed by instrumenting `mk_type_var`:
the only type vars created for this benchmark are the finite_set
signature vars `A` and `B`).

As a result `elim_uncnstr` bails out for goals that contain **no**
polymorphic terms at all, i.e. it is effectively disabled. Unconstrained
subterms that used to be eliminated now reach the theory solvers.

For this benchmark the (single) assertion is `(not (xor (>= x 0) (>= x1
0) (>= x 0) x4 (str.contains ...)))`. The duplicated `(>= x 0)` cancels
(`a xor a = false`), and the free Boolean `x4` can fix the parity
regardless of the value of the `str.contains` term, so the goal is
trivially `sat`. That `str.contains`/`str.replace_re` subterm is
unconstrained and was previously removed by `elim_uncnstr`; without that
elimination it reaches `theory_seq`, which marks `str.replace_re` as
unhandled and gives up in `final_check` → `unknown` (`incomplete (theory
seq)`).

## Fix

Make the guard precise. Keep `has_type_vars()` as a cheap pre-filter
(matching existing usage in `ast_translation.cpp` and
`ast_manager::has_type_var`), but only bail out when the goal / asserted
formulas **actually** contain type-variable typed terms, using the
existing `polymorphism::util::has_type_vars(expr*)`.

This preserves the polymorphism crash-protection (goals with genuine
type-variable terms still skip `elim_uncnstr`) while restoring
`elim_uncnstr` for the vast majority of goals that merely triggered a
polymorphic-plugin initialization. Both twin guards (the `elim_uncnstr`
tactic and the `elim_unconstrained` simplifier) are fixed consistently.

## Validation

Built this checkout and re-ran the benchmark (step 5 of the fixer
workflow):

- `./configure && make -C build -j$(nproc)` — Z3 version 4.17.0.
- Unpatched master reproduced the divergence: `z3 -T:20
iss-6260/small-2.smt2` → `unknown`.
- After the fix, `z3 -T:20 inputs/issues/iss-6260/small-2.smt2` produces
**exactly** the recorded oracle:

```
sat
(error "line 17 column 0: unexpected character")
(error "line 17 column 1: unexpected character")
(error "line 17 column 2: unexpected character")
```

- Regression sanity checks: sibling `iss-6260/small.smt2` unchanged
(`sat`); plain arithmetic unconstrained goals unchanged; polymorphic
goals with genuine type-variable terms (including `declare-type-var` and
forcing `(check-sat-using (then elim-uncnstr smt))`) still solve without
crashing — the guard still fires for those.

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




> Generated by [Fix a Z3 snapshot-regression
divergence](https://github.com/Z3Prover/bench/actions/runs/28844840657)
· 861.2 AIC · ⌖ 45.8 AIC · ⊞ 8.9K ·
[◷](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.63, model: claude-opus-4.8, id:
28844840657, workflow_id: snapshot-regression-fixer, run:
https://github.com/Z3Prover/bench/actions/runs/28844840657 -->

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-07 13:02:37 -07:00
..
aig Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
arith Fix reference to recfun::util in lia2card_tactic.cpp 2026-06-16 14:02:24 -06:00
bv Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
core [snapshot-regression-fix] Fix elim_uncnstr disabled by manager-wide has_type_vars() flag (iss-6260/small-2) (#10063) 2026-07-07 13:02:37 -07:00
fd_solver Parallel tactic (#9824) (#9825) 2026-06-26 10:36:15 -06:00
fpa Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
portfolio Parallel tactic (#9824) (#9825) 2026-06-26 10:36:15 -06:00
sls Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
smtlogics Throttle lia2card in QF_LIA preamble (mk_preamble_tactic) (#9489) 2026-05-11 12:03:09 -04:00
ufbv Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
CMakeLists.txt move horn_subsume_model_converter to ast/converters 2022-11-03 20:26:02 -07:00
dependency_converter.cpp call it data instead of c_ptr for approaching C++11 std::vector convention. 2021-04-13 18:17:35 -07:00
dependency_converter.h move model and proof converters to self-contained module 2022-11-03 05:23:01 -07:00
dependent_expr_state_tactic.h add an option to register callback on quantifier instantiation 2025-08-06 21:11:55 -07:00
goal.cpp Add initializer_list overloads and update all call sites 2026-02-16 01:27:08 +00:00
goal.h Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
goal_num_occurs.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
goal_num_occurs.h booyah 2020-07-04 15:56:30 -07:00
goal_proof_converter.h Fix some typos. (#7115) 2024-02-07 23:06:43 -08:00
goal_shared_occs.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
goal_shared_occs.h booyah 2020-07-04 15:56:30 -07:00
goal_util.cpp port to inherit from std::exception 2024-11-04 13:25:14 -08:00
goal_util.h booyah 2020-07-04 15:56:30 -07:00
probe.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
probe.h remove default destructors & some default constructors 2024-09-04 22:30:23 +01:00
tactic.cpp Standardize for-loop increments to prefix form (++i) (#8199) 2026-01-14 19:55:31 -08:00
tactic.h fix #7404, relates to #7400. 2024-09-27 11:36:10 +01:00
tactic_exception.h add noexcept 2024-11-04 11:21:55 -08:00
tactical.cpp Remove unnecessary semicolons (Attempt 2) (#10020) 2026-07-02 12:47:29 -07:00
tactical.h booyah 2020-07-04 15:56:30 -07:00
user_propagator_base.h add an option to register callback on quantifier instantiation 2025-08-06 21:11:55 -07:00