3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-19 05:25:48 +00:00

bugfixes to front-end and matcher

This commit is contained in:
Nikolaj Bjorner 2026-07-06 15:29:02 -07:00
parent 6c8a5cd853
commit 470e966791
8 changed files with 275 additions and 67 deletions

View file

@ -43,6 +43,7 @@ Author:
--*/
#include "ast/euf/ho_matcher.h"
#include "ast/well_sorted.h"
@ -373,6 +374,9 @@ namespace euf {
pats.push_back(to_app(p1));
p1 = to_app(p1)->get_arg(0);
}
// innermost select is a meta variable,
// order patterns from inner-most application to outer-most.
pats.reverse();
auto v = to_var(p1);
if (wi.is_init())
wi.set_project();
@ -430,32 +434,44 @@ namespace euf {
// Without this the constructed lambda has the argument arities
// in the wrong nesting order and its sort disagrees with the
// flex head variable (producing an ill-typed binding).
pats.reverse();
ptr_vector<sort> domain, pat_domain;
ptr_vector<expr> pat_args;
svector<unsigned> pat_pos; // forward binder position (in domain) of each distinct index
expr_ref_vector args(m), pat_vars(m), bound_args(m);
vector<symbol> names;
pat_args.push_back(nullptr);
pat_vars.push_back(nullptr);
pat_pos.push_back(0); // placeholder for the flex-head slot 0
unsigned num_bound = 0;
expr_mark seen;
for (auto pat : pats) {
for (auto pi : array_select_indices(pat)) {
if (!seen.is_marked(pi)) {
pat_domain.push_back(pi->get_sort());
pat_args.push_back(pi);
pat_pos.push_back(num_bound);
seen.mark(pi);
}
++num_bound;
domain.push_back(pi->get_sort());
names.push_back(symbol(num_bound));
if (seen.is_marked(pi))
continue;
pat_domain.push_back(pi->get_sort());
pat_args.push_back(pi);
seen.mark(pi);
}
}
for (unsigned i = pat_args.size(); i-- > 1; ) {
auto pi = pat_args.get(i);
pat_vars.push_back(m.mk_var(pat_args.size() - i - 1, pi->get_sort()));
}
// pat_vars[k] references the lambda binder for the k-th distinct
// index and must carry that index' sort, so the reconstructed
// select stays aligned with the flex head's array domain
// (pat_domain). The binder at forward position p has de Bruijn
// index num_bound-1-p (the outermost binder has the highest
// index). Emitting in forward slot order keeps the select
// arguments in the same order as pat_domain even when the
// indices have heterogeneous sorts (otherwise the array plugin
// rejects the ill-ordered select).
for (unsigned k = 1; k < pat_args.size(); ++k) {
unsigned db = num_bound - 1 - pat_pos[k];
pat_vars.push_back(m.mk_var(db, pat_args.get(k)->get_sort()));
}
for (auto ti : *ta) {
sort* v_sort = m_array.mk_array_sort(pat_domain.size(), pat_domain.data(), ti->get_sort());
@ -463,7 +479,8 @@ namespace euf {
auto w = m.mk_var(m_subst.size() + wi.pat_offset() + num_bound, v_sort); // shifted by number of bound
m_subst.resize(m_subst.size() + 1);
pat_args[0] = v;
auto sel = m_array.mk_select(pat_args.size(), pat_args.data());
expr_ref sel(m);
sel = m_array.mk_select(pat_args.size(), pat_args.data());
m_goals.push(wi.level + 1, wi.term_offset(), sel, ti);
pat_vars[0] = w;
sel = m_array.mk_select(pat_vars.size(), pat_vars.data());
@ -479,6 +496,7 @@ namespace euf {
num_bound -= sz;
lam = m.mk_lambda(sz, domain.data() + num_bound, names.data() + num_bound, lam);
}
add_binding(v, wi.pat_offset(), lam);
wi.set_done();
return true;
@ -591,6 +609,7 @@ namespace euf {
}
lam = m.mk_lambda(names.size(), sorts.data(), names.data(), lam);
}
SASSERT(is_well_sorted(m, lam));
return lam;
}
@ -632,6 +651,8 @@ namespace euf {
SASSERT(var_sort);
body = m.mk_var(num_binders - i - 1, var_sort);
bind_lambdas(num_lambdas, s, body);
SASSERT(body->get_sort() == s);
SASSERT(is_well_sorted(m, body));
return body;
}
@ -646,6 +667,8 @@ namespace euf {
decl_names.push_back(symbol(i));
}
body = m.mk_lambda(sz, decl_sorts.data(), decl_names.data(), body);
SASSERT(s == body->get_sort());
SASSERT(is_well_sorted(m, body));
}
void ho_matcher::add_binding(var* v, unsigned offset, expr* t) {
@ -665,7 +688,6 @@ namespace euf {
}
auto is_ho = any_of(subterms::all(expr_ref(p, m)), [&](expr* t) {
return m_unitary.is_flex(0, t) ||
// m.is_lambda_def(t) ||
is_lambda(t);
});
if (!is_ho)
@ -684,8 +706,7 @@ namespace euf {
todo.pop_back();
continue;
}
if ((m_unitary.is_flex(0, t) && lvl > 1) || // m.is_lambda_def(t) ||
is_lambda(t)) {
if ((m_unitary.is_flex(0, t) && lvl > 1) || is_lambda(t)) {
if (!contains_pat2abs)
m_pat2abs.insert_if_not_there(p, svector<std::pair<unsigned, expr*>>()).push_back({ nb, t });
auto v = m.mk_var(nb++, t->get_sort());
@ -817,6 +838,7 @@ namespace euf {
auto& abs = m_pat2abs[fo_pat];
verbose_stream() << " m_pat2abs size: " << abs.size() << "\n";
for (auto [v, pat] : abs) verbose_stream() << " v=" << v << " pat=" << mk_pp(pat, m) << "\n";);
unsigned base_scope = m_trail.get_num_scopes();
m_trail.push_scope();
m_subst.resize(0);
m_subst.resize(s.size());
@ -835,12 +857,18 @@ namespace euf {
unsigned num_bound = 0, level = 0;
for (auto [v, pat] : m_pat2abs[fo_pat]) {
// Defensive: if the abstraction-variable indices in the stored
// pattern do not line up (sort-wise) with the current substitution,
// building the refined term would be ill-typed and abort the solve.
// Skip the whole refinement; a missed heuristic instance is sound.
// If a binding's sort disagrees with the pattern variable it would
// fill, substituting it would build an ill-sorted term. This can
// arise for deeply nested multi-select patterns whose de Bruijn
// remapping does not line up, or when E-matching delivers a
// candidate binding whose sort is incompatible with the abstracted
// higher-order pattern. Discard this refinement candidate (produce
// no instance) instead of aborting the whole solve.
if (!subst_sorts_match(m, pat, s, true)) {
m_trail.pop_scope(1);
IF_VERBOSE(0, verbose_stream() << "refine_ho_match: sorts do not match for " << mk_pp(pat, m) << " and "
<< s << "\n";);
UNREACHABLE();
return;
}
}
@ -850,8 +878,8 @@ namespace euf {
TRACE(ho_matching, tout << mk_pp(pat, m) << " -> " << pat_refined << "\n");
m_goals.push(level, num_bound, pat_refined, m_subst.get(v));
}
search();
m_trail.pop_scope(1);
}
@ -908,6 +936,7 @@ namespace euf {
};
void match_goals::push(unsigned level, unsigned offset, expr_ref const& pat, expr_ref const& t) {
SASSERT(pat->get_sort() == t->get_sort());
match_goal* wi = new (ho.trail().get_region()) match_goal(level, offset, pat, t);
ho.trail().push(retire_match_goal(*wi)); // reset on undo
wi->init(wi);

View file

@ -21,6 +21,7 @@ Notes:
#include "ast/ast_util.h"
#include "ast/ast_pp.h"
#include "ast/ast_ll_pp.h"
#include "ast/well_sorted.h"
#include "ast/rewriter/var_subst.h"
#include "params/array_rewriter_params.hpp"
#include "util/util.h"
@ -818,6 +819,7 @@ expr_ref array_rewriter::expand_store(expr* s) {
result = m().mk_ite(mk_and(eqs), tmp, result);
}
result = m().mk_lambda(sorts.size(), sorts.data(), names.data(), result);
SASSERT(is_well_sorted(m(), result));
return result;
}

View file

@ -21,6 +21,7 @@ Notes:
#include "ast/rewriter/rewriter.h"
#include "ast/ast_smt2_pp.h"
#include "ast/ast_ll_pp.h"
#include "ast/well_sorted.h"
#include "ast/ast_pp.h"
template<typename Config>
@ -552,6 +553,7 @@ void rewriter_tpl<Config>::process_quantifier(quantifier * q, frame & fr) {
SASSERT(fr.m_spos + num_children == result_stack().size());
expr * const * it = result_stack().data() + fr.m_spos;
expr * new_body = *it;
SASSERT(is_well_sorted(m_manager, q));
unsigned num_pats = q->get_num_patterns();
unsigned num_no_pats = q->get_num_no_patterns();
expr_ref_vector new_pats(m_manager, num_pats, q->get_patterns());
@ -604,6 +606,7 @@ void rewriter_tpl<Config>::process_quantifier(quantifier * q, frame & fr) {
if (!m_cfg.reduce_quantifier(q, new_body, new_pats.data(), new_no_pats.data(), m_r, m_pr)) {
if (fr.m_new_child) {
m_r = m().update_quantifier(q, num_pats, new_pats.data(), num_no_pats, new_no_pats.data(), new_body);
SASSERT(is_well_sorted(m(), m_r));
}
else {
TRACE(rewriter_reuse, tout << "reusing:\n" << mk_ismt2_pp(q, m()) << "\n";);

View file

@ -25,31 +25,57 @@ Revision History:
#include "util/warning.h"
#include "ast/ast_smt2_pp.h"
namespace {
struct well_sorted_proc {
ast_manager & m_manager;
bool m_error;
ast_manager & m;
bool m_error;
ptr_vector<sort> m_binding;
well_sorted_proc(ast_manager & m):m_manager(m), m_error(false) {}
void operator()(var * v) {}
well_sorted_proc(ast_manager & m):m(m), m_error(false) {}
void operator()(quantifier * n) {
expr const * e = n->get_expr();
if (!is_lambda(n) && !m_manager.is_bool(e)) {
warning_msg("quantifier's body must be a boolean.");
m_error = true;
UNREACHABLE();
void check(expr* e) {
for (auto term : subterms::ground(expr_ref(e, m))) {
if (is_app(term))
check_app(to_app(term));
else if (is_var(term))
check_var(to_var(term));
else if (is_quantifier(term))
check_quantifier(to_quantifier(term));
}
}
void operator()(app * n) {
void check_quantifier(quantifier * n) {
if (!is_lambda(n) && !m.is_bool(n->get_expr())) {
warning_msg("quantifier's body must be a boolean.");
m_error = true;
// UNREACHABLE();
}
unsigned sz = m_binding.size();
m_binding.append(n->get_num_decls(), n->get_decl_sorts());
check(n->get_expr());
m_binding.shrink(sz);
}
void check_var(var* v) {
if (v->get_idx() >= m_binding.size()) {
return;
}
sort *s = m_binding[m_binding.size() - v->get_idx() - 1];
if (s != v->get_sort()) {
warning_msg("variable sort does not match binding sort.");
m_error = true;
// UNREACHABLE();
}
}
void check_app(app * n) {
unsigned num_args = n->get_num_args();
func_decl * decl = n->get_decl();
if (num_args != decl->get_arity() && !decl->is_associative() &&
!decl->is_right_associative() && !decl->is_left_associative()) {
TRACE(ws, tout << "unexpected number of arguments.\n" << mk_ismt2_pp(n, m_manager););
TRACE(ws, tout << "unexpected number of arguments.\n" << mk_ismt2_pp(n, m););
warning_msg("unexpected number of arguments.");
m_error = true;
return;
@ -59,19 +85,20 @@ struct well_sorted_proc {
sort * actual_sort = n->get_arg(i)->get_sort();
sort * expected_sort = decl->is_associative() ? decl->get_domain(0) : decl->get_domain(i);
if (expected_sort != actual_sort) {
TRACE(tc, tout << "sort mismatch on argument #" << i << ".\n" << mk_ismt2_pp(n, m_manager);
tout << "Sort mismatch for argument " << i+1 << " of " << mk_ismt2_pp(n, m_manager, false) << "\n";
tout << "Expected sort: " << mk_pp(expected_sort, m_manager) << "\n";
tout << "Actual sort: " << mk_pp(actual_sort, m_manager) << "\n";
tout << "Function sort: " << mk_pp(decl, m_manager) << ".";
TRACE(tc, tout << "sort mismatch on argument #" << i << ".\n" << mk_ismt2_pp(n, m);
tout << "Sort mismatch for argument " << i+1 << " of " << mk_ismt2_pp(n, m, false) << "\n";
tout << "Expected sort: " << mk_pp(expected_sort, m) << "\n";
tout << "Actual sort: " << mk_pp(actual_sort, m) << "\n";
tout << "Function sort: " << mk_pp(decl, m) << ".";
);
std::ostringstream strm;
strm << "Sort mismatch for argument " << i+1 << " of " << mk_ll_pp(n, m_manager, false) << "\n";
strm << "Expected sort: " << mk_pp(expected_sort, m_manager) << '\n';
strm << "Actual sort: " << mk_pp(actual_sort, m_manager) << '\n';
strm << "Function sort: " << mk_pp(decl, m_manager) << '.';
strm << "Sort mismatch for argument " << i+1 << " of " << mk_ll_pp(n, m, false) << "\n";
strm << "Expected sort: " << mk_pp(expected_sort, m) << '\n';
strm << "Actual sort: " << mk_pp(actual_sort, m) << '\n';
strm << "Function sort: " << mk_pp(decl, m) << '.';
warning_msg("%s", std::move(strm).str().c_str());
m_error = true;
// UNREACHABLE();
return;
}
}
@ -82,7 +109,11 @@ struct well_sorted_proc {
bool is_well_sorted(ast_manager const & m, expr * n) {
well_sorted_proc p(const_cast<ast_manager&>(m));
for_each_expr(p, n);
p.check(n);
if (p.m_error) {
IF_VERBOSE(0, verbose_stream() << "expression is not well sorted.\n" << mk_pp(n, const_cast<ast_manager&>(m)) << "\n";);
IF_VERBOSE(0, verbose_stream() << mk_ll_pp(n, const_cast<ast_manager &>(m)) << "\n";);
}
return !p.m_error;
}

View file

@ -18,6 +18,7 @@
#include "ast/expr_abstract.h"
#include "ast/ast_util.h"
#include "ast/polymorphism_util.h"
#include "ast/well_sorted.h"
#include "ast/rewriter/expr_safe_replace.h"
#include "solver/solver.h"
#include "cmd_context/cmd_context.h"
@ -2461,6 +2462,7 @@ class tptp_parser {
m_has_conjecture = true;
f = m.mk_not(f);
}
SASSERT(is_well_sorted(m, f));
m_cmd.assert_expr(f);
} catch (z3_exception const& ex) {
// Sort mismatch or other semantic error in this formula — skip it.

View file

@ -42,10 +42,6 @@ Revision History:
namespace smt {
// Instrumentation: counts terms produced by ho_var term-enumeration
// (mf::ho_var::populate_inst_sets). One increment per enumerated term
// inserted into an instantiation set. Reset per model_finder instance.
static unsigned g_ho_var_term_enum = 0;
namespace mf {
@ -1173,6 +1169,7 @@ namespace smt {
virtual char const* get_kind() const = 0;
virtual bool is_equal(qinfo const* qi) const = 0;
virtual void display(std::ostream& out) const { out << "[" << get_kind() << "]"; }
virtual void collect_statistics(::statistics &st) const {}
// AUF fragment solver
virtual void process_auf(quantifier* q, auf_solver& s, context* ctx) = 0;
@ -1385,10 +1382,15 @@ namespace smt {
class ho_var : public qinfo {
unsigned m_var_i;
unsigned m_ho_var_term_enum = 0;
public:
ho_var(ast_manager& m, unsigned i) : qinfo(m), m_var_i(i) {
}
void collect_statistics(::statistics &st) const override {
st.update("mbqi.ho-var-term-enum", m_ho_var_term_enum);
}
char const *get_kind() const override {
return "ho_var";
}
@ -1453,7 +1455,7 @@ namespace smt {
unsigned generation = 0; // todo - inherited from sub-term of t?
TRACE(model_finder, tout << "ho_var: adding term " << mk_ismt2_pp(t, m)
<< " to instantiation set of S" << std::endl;);
++g_ho_var_term_enum;
++m_ho_var_term_enum;
S->insert(t, generation);
}
}
@ -1779,6 +1781,11 @@ namespace smt {
public:
typedef ptr_vector<cond_macro>::const_iterator macro_iterator;
void collect_statistics(::statistics &st) const {
for (auto *qi : m_qinfo_vect)
qi->collect_statistics(st);
}
static quantifier_ref mk_flat(ast_manager& m, quantifier* q) {
if (has_quantifiers(q->get_expr())) {
proof_ref pr(m);
@ -2415,7 +2422,6 @@ namespace smt {
m_auf_solver(alloc(auf_solver, m)),
m_dependencies(m),
m_new_constraints(m) {
g_ho_var_term_enum = 0;
}
model_finder::~model_finder() {
@ -2423,7 +2429,10 @@ namespace smt {
}
void model_finder::collect_statistics(::statistics & st) const {
st.update("ho-var term-enum", g_ho_var_term_enum);
// Retrieve the ho-var term-enumeration counters from the embedded
// qinfo objects (mf::ho_var) held by each registered quantifier_info.
for (auto const &[k, v] : m_q2info)
v->collect_statistics(st);
}
void model_finder::checkpoint() {

View file

@ -657,7 +657,7 @@ namespace smt {
if (m_fparams->m_ho_matching) {
m_ho_matcher = alloc(euf::ho_matcher, m, m_context->get_trail_stack());
std::function<void(euf::ho_subst&)> on_match = [&](euf::ho_subst& s) {
std::function<void(euf::ho_subst&)> on_match = [this](euf::ho_subst& s) {
on_ho_match(s);
};
m_ho_matcher->set_on_match(on_match);
@ -667,21 +667,6 @@ namespace smt {
quantifier_manager_plugin * mk_fresh() override { return alloc(default_qm_plugin); }
void on_ho_match(euf::ho_subst& s) {
ast_manager& m = m_context->get_manager();
try {
on_ho_match_core(s);
}
catch (z3_exception &) {
// A higher-order binding produced an ill-typed or otherwise
// unusable instantiation term. Adding a heuristic HO instance is
// optional, so we skip this match rather than aborting the solve.
// Re-raise only if the failure was due to cancellation/resource limits.
if (!m.inc())
throw;
}
}
void on_ho_match_core(euf::ho_subst& s) {
ast_manager& m = m_context->get_manager();
auto& st = m_ho_state;
auto* hoq = st.m_q;
@ -709,8 +694,9 @@ namespace smt {
change = false;
for (unsigned i = 1; i < binding.size(); ++i) {
if (!binding.get(i)) continue;
// Skip ill-typed substitutions: a misaligned higher-order
// binding would build an ill-sorted term and abort the solve.
// A misaligned higher-order binding would build an
// ill-sorted term. Abandon this refinement (no instance)
// rather than aborting the whole solve.
if (!euf::ho_matcher::subst_sorts_match(m, binding.get(i), binding, false))
return;
auto r = sub(binding.get(i), binding);
@ -770,6 +756,7 @@ namespace smt {
for (unsigned i = 0; i < num_bindings; ++i)
s.push_back(bindings[i]->get_expr());
unsigned num_instances = m_stat_ho_instances;
m_ho_state.m_q = qa;
m_ho_state.m_pat = pat;
m_ho_state.m_num_bindings = num_bindings;

View file

@ -170,6 +170,147 @@ namespace euf {
m_matcher.add_pattern(pat.get());
m_matcher(pat, t, 3, 1);
}
// Structural regression test derived from TPTP ANA067^1 (which fails
// under smt.ho_matching=true inside the full solver).
// pattern: (select (select v0 v3) v2) with v0 a doubly-nested array (flex head)
// term: (select K ...) matched term is itself array-sorted.
// Exercises imitation/projection of a flex head against an array-sorted
// term. The matcher must build only well-sorted bindings; the debug
// asserts in match_goals::push and mk_project catch any regression that
// commits an ill-sorted (extra array level) lambda/select.
void test7() {
sort_ref r(m_arith.mk_real(), m);
sort_ref arr_rb(m_array.mk_array_sort(r, m.mk_bool_sort()), m); // (Array Real Bool)
sort_ref arr_r_rb(m_array.mk_array_sort(r, arr_rb), m); // (Array Real (Array Real Bool))
sort_ref arr_r_r_rb(m_array.mk_array_sort(r, arr_r_rb), m); // v0 sort
expr_ref v0(m.mk_var(0, arr_r_r_rb), m);
expr_ref v2(m.mk_var(2, r), m);
expr_ref v3(m.mk_var(3, r), m);
expr_ref pat(m_array.mk_select(v0, v3), m);
pat = m_array.mk_select(pat, v2);
expr_ref K(m.mk_const(symbol("K"), arr_r_rb), m);
expr_ref b(m.mk_const(symbol("b"), r), m);
expr_ref t(m_array.mk_select(K, b), m);
IF_VERBOSE(0, verbose_stream() << "test7: " << pat << " =?= " << t << "\n";);
m_matcher.add_pattern(pat.get());
m_matcher(pat, t, 5);
// Faithful variant: the term index argument is itself
// (select fun (lambda (t) c)) as in ANA067^1, i.e. a term whose
// subterm is a function(array)-valued lambda. This forces the
// matcher to decompose/imitate against a lambda-bearing term.
sort_ref arr_rr(m_array.mk_array_sort(r, r), m); // (Array Real Real)
sort_ref fun_sort(m_array.mk_array_sort(arr_rr, r), m); // (Array (Array Real Real) Real)
symbol tt("t");
sort* r_s = r.get();
expr_ref c0(m.mk_const(symbol("c0"), r), m);
expr_ref lam(m.mk_lambda(1, &r_s, &tt, c0), m); // (lambda (t Real) c0)
expr_ref fun(m.mk_const(symbol("fun"), fun_sort), m);
expr_ref idx(m_array.mk_select(fun, lam), m); // : Real
expr_ref t2(m_array.mk_select(K, idx), m);
IF_VERBOSE(0, verbose_stream() << "test7b: " << pat << " =?= " << t2 << "\n";);
m_matcher.add_pattern(pat.get());
m_matcher(pat, t2, 5);
}
// Structural regression test derived from TPTP PHI008^4 (which fails
// under smt.ho_matching=true inside the full solver).
// pattern: (select v3 v4) v3 flex head, v4 a flex arg of an array sort
// term: (select P ...) P a concrete array constant.
// Exercises projecting/imitating a flex head over a function(array)-sorted
// argument (incl. a lambda-valued term arg). The matcher must build only
// well-sorted bindings; debug asserts guard against regressions.
void test8() {
sort_ref i(m.mk_uninterpreted_sort(symbol("qML_i")), m);
sort_ref mu(m.mk_uninterpreted_sort(symbol("qML_mu")), m);
sort_ref arr_ib(m_array.mk_array_sort(i, m.mk_bool_sort()), m); // (Array qML_i Bool)
sort_ref arr_mu_ib(m_array.mk_array_sort(mu, arr_ib), m); // (Array qML_mu (Array qML_i Bool))
sort_ref p_sort(m_array.mk_array_sort(arr_mu_ib, arr_ib), m); // P sort
expr_ref v3(m.mk_var(3, p_sort), m);
expr_ref v4(m.mk_var(4, arr_mu_ib), m);
expr_ref pat(m_array.mk_select(v3, v4), m);
expr_ref P(m.mk_const(symbol("P"), p_sort), m);
expr_ref ell(m.mk_const(symbol("ell"), arr_mu_ib), m);
expr_ref t(m_array.mk_select(P, ell), m);
IF_VERBOSE(0, verbose_stream() << "test8: " << pat << " =?= " << t << "\n";);
m_matcher.add_pattern(pat.get());
m_matcher(pat, t, 9);
// Variant with a lambda-valued term argument, mirroring PHI008's
// (select scott_P (lambda (Y) (lambda (Z) ...))) goal that forces
// the matcher to decompose a flex head against a lambda term.
symbol yv("Y");
sort* mu_s = mu.get();
expr_ref cbody(m.mk_const(symbol("C"), arr_ib), m);
expr_ref lam(m.mk_lambda(1, &mu_s, &yv, cbody), m); // (lambda (Y qML_mu) C) : arr_mu_ib
expr_ref t2(m_array.mk_select(P, lam), m);
IF_VERBOSE(0, verbose_stream() << "test8b: " << pat << " =?= " << t2 << "\n";);
m_matcher.add_pattern(pat.get());
m_matcher(pat, t2, 9);
}
// Structural regression test for the ITP127-style shape (fails under
// smt.ho_matching=true inside the full solver). The flex head H has an
// applied result sort that is *itself* an array (monomo = (Array d Bool)).
// Matching (select (select H x1) x2) =?= f where f is array-sorted is a
// case where imitation could build a lambda with an extra array level
// (an ill-sorted select). The matcher must build only well-sorted
// bindings; debug asserts guard against regressions.
void test9() {
sort_ref c(m.mk_uninterpreted_sort(symbol("c")), m);
sort_ref d(m.mk_uninterpreted_sort(symbol("d")), m);
sort_ref monomo(m_array.mk_array_sort(d, m.mk_bool_sort()), m); // (Array d Bool)
sort_ref h1(m_array.mk_array_sort(c, monomo), m); // (Array c monomo)
sort_ref h2(m_array.mk_array_sort(c, h1), m); // (Array c (Array c monomo))
expr_ref H(m.mk_var(0, h2), m);
expr_ref x1(m.mk_var(1, c), m);
expr_ref x2(m.mk_var(2, c), m);
expr_ref pat(m_array.mk_select(H, x1), m);
pat = m_array.mk_select(pat, x2); // (select (select H x1) x2) : monomo
expr_ref f(m.mk_const(symbol("f"), monomo), m); // f : (Array d Bool)
IF_VERBOSE(0, verbose_stream() << "test9: " << pat << " =?= " << f << "\n";);
m_matcher.add_pattern(pat.get());
m_matcher(pat, f, 3);
}
// Faithful isolation test for the refine-time sort guard used by
// ho_matcher::refine_ho_match (throws "sort mismatch ..." on failure).
// Mirrors the SEV510^1 family where a bound-variable binding's sort
// disagrees with the pattern variable it would fill. subst_sorts_match
// must detect the mismatch (return false) and accept the matching case.
void test10() {
sort_ref int2int(m_array.mk_array_sort(m_int, m_int), m);
// pattern (select v0 v1): v0 is the array (idx 0), v1 the index (idx 1)
expr_ref v0(m.mk_var(0, int2int), m);
expr_ref v1(m.mk_var(1, m_int), m);
expr_ref pat(m_array.mk_select(v0, v1), m);
// std_order=true: var idx maps to s[size-idx-1], so idx0->s[1], idx1->s[0].
expr_ref arr(m.mk_const(symbol("arr"), int2int), m);
expr_ref i0(m_arith.mk_int(0), m);
// Well-sorted substitution: idx0 -> arr (array), idx1 -> 0 (int).
expr_ref_vector s_ok(m);
s_ok.push_back(i0); // s[0] -> var idx 1 (Int) OK
s_ok.push_back(arr); // s[1] -> var idx 0 (Array) OK
VERIFY(ho_matcher::subst_sorts_match(m, pat, s_ok, true));
// Ill-sorted substitution: idx0 (array var) bound to an Int -> mismatch.
expr_ref_vector s_bad(m);
s_bad.push_back(i0); // s[0] -> var idx 1 (Int) OK
s_bad.push_back(i0); // s[1] -> var idx 0 expects Array, got Int -> mismatch
VERIFY(!ho_matcher::subst_sorts_match(m, pat, s_bad, true));
IF_VERBOSE(0, verbose_stream() << "test10: subst_sorts_match detects sort mismatch\n";);
}
};
}
@ -183,6 +324,10 @@ void tst_ho_matcher() {
tm.test4();
tm.test5();
tm.test6();
tm.test7();
tm.test8();
tm.test9();
tm.test10();
}
catch (std::exception const& ex) {
std::cout << ex.what() << "\n";