Skip to content

Conversation

@puyral
Copy link

@puyral puyral commented Jan 6, 2026

This pull request introduces a subterm theory for datatypes, developed as part of the 199.029 SMT Solving Internals course at TU Wien.

The implementation seems functional to my testing, though there are known limitations and areas for improvement:

  • Mutually recursive datatypes are not supported. I did not want to introduce the complexity of polymorphism for my first time modifying z3.
  • Conflict propagation currently generates trivial equalities (e.g., equalities already decided in the current model or easily discardable by inspecting constructors). Attempts to optimize this led to unexpected issues, so the changes were reverted. The overhead is expected to be minimal, as these literal would quickly get back to the theory solver, where effectively the same code will be executed.
  • Model generation is not yet implemented.

puyral and others added 10 commits January 6, 2026 23:42
I can't really figure out how to link the smt parser to the rest of the
machinenery, so I will stop here and try from the other side. I'll start
implmenting the logic and see if it brings me back to the parser.
Very primitive, but I don't like have that much work uncommitted.
Skip adding obvious equalities or disequality
Add m_subterm to the trail
@puyral
Copy link
Author

puyral commented Jan 6, 2026

@microsoft-github-policy-service agree


TRACE(datatype, tout << "propagate_not_is_subterm: " << enode_pp(n, ctx) << "\n";);

datatype_util util(get_manager());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_util

TRACE(datatype, tout << "adding recursive case: " << mk_pp(arg1->get_expr(), m) << ""
<< mk_pp(s->get_expr(), m) << "\n";);
app_ref sub_app(m.mk_app(sub_decl, arg1->get_expr(), s->get_expr()), m);
ctx.internalize(sub_app, false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is probably mk_literal(sub_app) from base class in smt_theory.h instead of internalize + get_bool_var.

@NikolajBjorner NikolajBjorner marked this pull request as ready for review January 7, 2026 02:47
@NikolajBjorner
Copy link
Contributor

I am not sure what you mean by that model generation isn't implemented. Subterm relationship is an interpreted function.
Maybe the simplifier can be completed by always returning true/false for ground data-type terms. But, assuming the datatype theory solver reaches a saturated state, then each uninterpreted datatype constant is given an interpretation without requiring to access the subterm predicate.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a subterm theory for datatypes as part of the SMT Solving Internals course at TU Wien. The implementation adds support for subterm predicates (⊑) that can express subterm relationships within datatype terms, with reflexivity axioms automatically generated and propagation logic integrated with the existing datatype theory solver.

Key Changes

  • Added subterm predicates as a new datatype operator (OP_DT_SUBTERM) with syntax support through :subterm keyword in SMT-LIB2 parser
  • Implemented propagation logic for subterm constraints including handling of true/false assignments and interaction with constructor information
  • Created a custom iterator pattern (subterm_iterator) for traversing subterms of datatype expressions

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/smt/theory_datatype.h Adds m_subterms tracking to var_data, declares subterm propagation methods, and introduces subterm_iterator class
src/smt/theory_datatype.cpp Implements reflexivity axioms, propagation logic for subterm predicates, and subterm iterator functionality
src/parsers/smt2/smt2parser.cpp Adds parsing support for :subterm keyword in datatype declarations
src/cmd_context/pdecl.h Adds psubterm_decl class and optional m_subterm member to pdatatype_decl
src/cmd_context/pdecl.cpp Implements display method and instantiation logic for subterm declarations
src/cmd_context/cmd_context.cpp Registers subterm predicate function declarations in the command context
src/ast/datatype_decl_plugin.h Declares subterm class, OP_DT_SUBTERM operator, and utility methods for subterm predicates
src/ast/datatype_decl_plugin.cpp Implements subterm instantiation, mk_subterm factory method, and get_datatype_subterm lookup
src/ast/rewriter/datatype_rewriter.cpp Adds case for OP_DT_SUBTERM (currently returns BR_FAILED, no rewriting)

add_subterm_predicate(a, e);
add_subterm_predicate(b, e);

// propagating potentially adds a lot of litteral, avoid it if we can
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "litteral" should be "literals".

Suggested change
// propagating potentially adds a lot of litteral, avoid it if we can
// propagating potentially adds a lot of literals, avoid it if we can

Copilot uses AI. Check for mistakes.
}

void theory_datatype::propagate_subterm(enode *n, bool is_true) {
force_push(); // I am faily sure I need that here
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "faily" should be "fairly".

Suggested change
force_push(); // I am faily sure I need that here
force_push(); // I am fairly sure I need that here

Copilot uses AI. Check for mistakes.
return BR_DONE;
}
case OP_DT_SUBTERM:
// Re rewrite yet for subterms
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "Re rewrite yet" should be "No rewrite yet".

Suggested change
// Re rewrite yet for subterms
// No rewrite yet for subterms

Copilot uses AI. Check for mistakes.
*
* With `predicate:='a ⊑ b'` this should be called with `v:='a'` and `v:='b'`.
*
* This doesn't handle potential propagation. The responsability for it
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "responsability" should be "responsibility".

Suggested change
* This doesn't handle potential propagation. The responsability for it
* This doesn't handle potential propagation. The responsibility for it

Copilot uses AI. Check for mistakes.
Comment on lines +187 to +192
subterm_iterator(const subterm_iterator&) = delete;

subterm_iterator begin() {
return std::move(*this);
}
subterm_iterator end() {
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iterator pattern has a bug. When used in a range-based for loop (line 723), begin() moves from the iterator object, leaving it in a moved-from state. Then end() is called on the same moved-from object. While this might work in practice because end() only checks m_current, it's undefined behavior to use a moved-from object. The proper pattern would be to either: (1) have begin() and end() return separate iterator objects, not move from this; or (2) make this a proper range type with separate iterator and sentinel types.

Suggested change
subterm_iterator(const subterm_iterator&) = delete;
subterm_iterator begin() {
return std::move(*this);
}
subterm_iterator end() {
subterm_iterator(const subterm_iterator& other)
: m_todo(other.m_todo),
m_marked(other.m_marked),
m_manager(other.m_manager),
m_current(other.m_current) {
}
subterm_iterator begin() const {
return *this;
}
subterm_iterator end() const {

Copilot uses AI. Check for mistakes.
Comment on lines +29 to 30
#include "theory_datatype.h"

Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant include: theory_datatype.h is already included on line 25, so the duplicate include on line 29 should be removed.

Suggested change
#include "theory_datatype.h"

Copilot uses AI. Check for mistakes.
friend class pdecl_manager;
friend class pdatatype_decl;
symbol m_name;
ptype m_type;
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The psubterm_decl class declares a member variable m_type on line 237, but it is never initialized or used. This appears to be a copy-paste artifact from paccessor_decl. Either this member should be removed, or if it's intended for future use, it should be initialized in the constructor.

Suggested change
ptype m_type;

Copilot uses AI. Check for mistakes.
Comment on lines +265 to +278
void theory_datatype::assert_subterm_axioms(enode * n) {
sort * s = n->get_sort();
if (m_util.is_datatype(s)) {
func_decl * sub_decl = m_util.get_datatype_subterm(s);
if (sub_decl) {
TRACE(datatype, tout << "asserting reflexivity for #" << n->get_owner_id() << " " << mk_pp(n->get_expr(), m) << "\n";);
app_ref reflex(m.mk_app(sub_decl, n->get_expr(), n->get_expr()), m);
ctx.internalize(reflex, false);
literal l(ctx.get_bool_var(reflex));
ctx.mark_as_relevant(l);
ctx.mk_th_axiom(get_id(), 1, &l);
}
}
}
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subterm theory feature lacks test coverage. Given that the repository has comprehensive test coverage for datatype functionality (see src/test/parametric_datatype.cpp, src/test/simplifier.cpp with test_datatypes(), etc.), the new subterm predicates should have corresponding tests to verify correct behavior, including basic reflexivity, transitivity checks, propagation with constructors, and conflict detection.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants