z3-src 501.0.0

Source distribution of the Z3 SMT solver, for use as a build dependency
Documentation
/*++
Copyright (c) 2026 Microsoft Corporation

Module Name:

    seq_subset.cpp

Abstract:

    Heuristic regular-expression subset checks used by seq_rewriter.

Author:

    Nikolaj Bjorner (nbjorner) 2026-6-8

--*/

#include "ast/rewriter/seq_subset.h"

bool seq_subset::is_subset_rec(expr* a, expr* b, unsigned depth) const {
    while (true) {

        if (a == b)
            return true;
        if (m_re.is_empty(a))
            return true;
        if (m_re.is_full_seq(b))
            return true;
        if (m_re.is_epsilon(a) && m_re.get_info(b).nullable == l_true)
            return true;

        if (depth >= m_max_depth)
            return false;

        expr* a1 = nullptr, * a2 = nullptr, * b1 = nullptr, * b2 = nullptr;
        unsigned la, ua, lb, ub;

        // a ⊆ .+ iff a is non-nullable
        if (m_re.is_dot_plus(b) && m_re.get_info(a).nullable == l_false)
            return true;

        // e ⊆ a*
        if (m_re.is_epsilon(a) && m_re.is_star(b, b1))
            return true;

        // a ⊆ a*: if b = b1* and a ⊆ b1, then a ⊆ b1*
        if (m_re.is_star(b, b1) && is_subset_rec(a, b1, depth))
            return true;

        // R1* ⊆ R2* if R1 ⊆ R2
        if (m_re.is_star(a, a1) && m_re.is_star(b, b1) && is_subset_rec(a1, b1, depth + 1))
            return true;

        // R1+ ⊆ R2+ if R1 ⊆ R2
        if (m_re.is_plus(a, a1) && m_re.is_plus(b, b1) && is_subset_rec(a1, b1, depth))
            return true;

        // R ⊆ R+
        if (m_re.is_plus(b, b1) && is_subset_rec(a, b1, depth))
            return true;

        // R+ ⊆ R*
        if (m_re.is_plus(a, a1) && m_re.is_star(b, b1) && is_subset_rec(a1, b1, depth + 1))
            return true;

        // range containment
        if (m_re.is_range(a, la, ua) && m_re.is_range(b, lb, ub) && lb <= la && ua <= ub)
            return true;

        // to_re(s) ⊆ range
        if (m_re.is_to_re(a, a1) && m_re.is_range(b, lb, ub) && is_app(a1)) {
            func_decl* f = to_app(a1)->get_decl();
            if (f->get_decl_kind() == OP_STRING_CONST && f->get_num_parameters() == 1) {
                zstring const& s = f->get_parameter(0).get_zstring();
                if (s.length() == 1 && lb <= s[0] && s[0] <= ub)
                    return true;
            }
        }

        // a ⊆ b1 ∪ b2 if a ⊆ b1 or a ⊆ b2
        if (m_re.is_union(b, b1, b2) && (is_subset_rec(a, b1, depth + 1) || is_subset_rec(a, b2, depth + 1)))
            return true;

        // a1 ∪ a2 ⊆ b if a1 ⊆ b and a2 ⊆ b
        if (m_re.is_union(a, a1, a2) && is_subset_rec(a1, b, depth + 1) && is_subset_rec(a2, b, depth + 1))
            return true;

        // a1 ∩ a2 ⊆ b if a1 ⊆ b or a2 ⊆ b
        if (m_re.is_intersection(a, a1, a2) && (is_subset_rec(a1, b, depth + 1) || is_subset_rec(a2, b, depth + 1)))
            return true;

        // a ⊆ b1 ∩ b2 if a ⊆ b1 and a ⊆ b2
        if (m_re.is_intersection(b, b1, b2) && is_subset_rec(a, b1, depth + 1) && is_subset_rec(a, b2, depth + 1))
            return true;

        // R{la,ua} ⊆  R'{lb,ub} if  R ⊆ R', lb<=la, ua<=ub
        if (m_re.is_loop(a, a1, la, ua) &&
            m_re.is_loop(b, b1, lb, ub) &&
            lb <= la && ua <= ub && is_subset_rec(a1, b1, depth + 1)) {
            return true;
        }

        // a1 \ a2 ⊆ b if a1 ⊆ b
        if (m_re.is_diff(a, a1, a2) && is_subset_rec(a1, b, depth + 1))
            return true;

        // R ⊆ Σ*·R' if R ⊆ R'
        if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b1) && is_subset_rec(a, b2, depth))
            return true;

        // prefix absorption: P·R' ⊆ Σ*·R' for any prefix P (since P ⊆ Σ*).
        // Detect that a has R' (= b2) as a concatenation suffix, where b = Σ*·R'.
        // Covers contains-patterns, e.g. Σ*·a·Σ*·b·Σ* ⊆ Σ*·b·Σ*.
        if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b1) && ends_with(a, b2))
            return true;

        // R ⊆ R'·Σ* if R ⊆ R'
        if (m_re.is_concat(b, b1, b2) && m_re.is_full_seq(b2) && is_subset_rec(a, b1, depth))
            return true;

        // star absorption: R·R* ⊆ R*, R*·R ⊆ R*
        bool const is_concat_star = m_re.is_concat(a, a1, a2) && m_re.is_star(b, b1);
        if (is_concat_star &&
            is_subset_rec(a1, b1, depth + 1) && is_subset_rec(a2, b, depth + 1))
            return true;
        if (is_concat_star &&
            is_subset_rec(a2, b1, depth + 1) && is_subset_rec(a1, b, depth + 1))
            return true;

        // concat monotonicity:
        // tail-recursive on second arguments (without increasing depth bound).
        if (m_re.is_concat(a, a1, a2) && m_re.is_concat(b, b1, b2) && is_subset_rec(a1, b1, depth + 1)) {
            a = a2;
            b = b2;
            continue;
        }

        // complement: ~a ⊆ ~b if b ⊆ a
        if (m_re.is_complement(a, a1) && m_re.is_complement(b, b1))
            return is_subset_rec(b1, a1, depth + 1);

        return false;
    }
}

bool seq_subset::is_subset(expr* a, expr* b) const {
    return is_subset_rec(a, b, 0);
}

bool seq_subset::ends_with(expr* a, expr* suf) const {
    if (a == suf)
        return true;
    // Flatten both regexes into their sequence of concatenation factors
    // (independent of left/right associativity) and test list-suffix equality.
    ptr_vector<expr> af, sf;
    flatten_concat(a, af);
    flatten_concat(suf, sf);
    if (sf.size() > af.size())
        return false;
    unsigned off = af.size() - sf.size();
    for (unsigned i = 0; i < sf.size(); ++i)
        if (af[off + i] != sf[i])
            return false;
    return true;
}

void seq_subset::flatten_concat(expr* a, ptr_vector<expr>& out) const {
    expr* a1 = nullptr, * a2 = nullptr;
    if (m_re.is_concat(a, a1, a2)) {
        flatten_concat(a1, out);
        flatten_concat(a2, out);
    }
    else
        out.push_back(a);
}