formally_smt/lib.rs
1//
2// ::formally - the open-source formal methods toolchain
3//
4// Copyright (c) 2025 Nicola Gigante
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23//
24
25#![allow(clippy::type_complexity)]
26
27//! Abstraction around Satisfiability Modulo Theories solvers.
28#![doc = ""]
29#![cfg_attr(
30 not(feature = "__subcratedoc"),
31 doc = "**WARNING: This crate is not supposed to be used directly.**"
32)]
33#![doc = ""]
34#![cfg_attr(
35 not(feature = "__subcratedoc"),
36 doc = "**Use instead the `formally::smt` module of the main `formally` crate by enabling the `\"smt\"` feature.**"
37)]
38#![doc = ""]
39//!
40//! The [smt][self] crate is at the core of `::formally`. The goal is to provide access to different
41//! SMT solvers in a uniform way while at the same time granting access to as many solver-specific
42//! features as possible, without limiting the user to the lowest common denominator between the
43//! supported backends.
44//!
45//! ### Example
46//! ```
47//! # mod formally {
48//! # pub extern crate formally_support as support;
49//! # pub extern crate formally_smt as smt;
50//! # }
51//! use formally::smt::*;
52//!
53//! # use formally::support::*;
54//! # fn main() -> Result<()> {
55//! let config = Config::default().logic("LIA");
56//! let mut solver = Solver::new(&config)?;
57//!
58//! solver.declare(Declaration::integer("x"))?;
59//! solver.declare(Declaration::integer("y"))?;
60//!
61//! solver.require(term!(> (- x y) 0))?;
62//! solver.require(term!(< x y))?;
63//!
64//! let answer = solver.check()?;
65//!
66//! assert_eq!(answer, Answer::No);
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! # Overview
72//!
73//! At the core of the [smt][self] crate we have the [Solver] type, which provides easy access to a
74//! selected backend (e.g. Z3 or cvc5), and the [Term] type, which describes an SMT term that can be
75//! asserted to the [Solver] or used to represent values in a model.
76//!
77//! Terms are built on top of [Function] objects which can be either:
78//! 1. [Primitive] entities such as the ones defined by a theory, e.g. [theories::Ints::plus()].
79//! 2. [Variables](Variable) used to represent function parameters, quantified variables, etc.
80//! 3. [Declared] entities, which are the unknowns of the SMT problem, which the solver has to find
81//! values of.
82//! 2. [Defined] entities, which have a known definition and are mostly just shortcuts to repeat
83//! common expressions.
84//!
85//! ## Declarations and definitions
86//!
87//! Functions (including constants, which are functions without arguments) are declared by
88//! constructing a [Declaration] object and passing it to [Solver::declare()], as in the example
89//! above. [Declaration] objects act like a specification of what we want to declare. Then, the
90//! [declare()](Solver::declare()) method returns a [Declared] object which represents the specific
91//! declared entity. [Declared] hides a shared reference to the unique underlying [Declaration]
92//! object registered inside the solver, which is accessible via [Deref](std::ops::Deref) but, at
93//! this point, immutable. One can freely clone [Declared] objects to obtain different handles to
94//! the same underlying declaration.
95//!
96//! Similarly, definitions are obtained by calling [Solver::define()] with a [Definition] object.
97//! The method returns a [Defined] object which uniquely represents the definition.
98//!
99//! [Declared] and [Defined] objects can then be used to build more complex [Term] objects, either
100//! directly or with the [term!] macro, which can then be asserted using [Solver::require()].
101//!
102//! Each declared and defined entity has a *sort*, a type in the logical lingo, which is represented
103//! by the [Sort] type. Sorts can be created directly or with the [sort!] macro, as in the example
104//! above.
105//!
106//! In the above example we used the [Declaration::integer()] function which is a shortcut to call
107//! [Declaration::constant()] with sort `sort!(Int)`. In turn, [Declaration::constant()]
108//! is a shortcut to call [Declaration::function()] with an empty domain. [Declaration::predicate()]
109//! also exists to declare functions that return `sort!(Bool)`. Similar shortcuts exist
110//! for [Definition].
111//!
112//! ## Building terms and sorts
113//!
114//! SMT terms are represented by the [Term] type, which is a shared reference to a [TermKind] enum.
115//! The latter is a simple enum which is either a constant (such as `42` or `3.14`), an atom that
116//! applies a [Function] to some arguments, a quantified formula, a `let` expression, and so on...
117//! Usually, interfaces to SMT solvers provide many types and functions to create all the different
118//! kind of terms supported by the solvers (boolean connectives, arithmetic functions, and so on).
119//! Instead, [formally::smt] takes a different approach. Terms are just applications of functions to
120//! arguments, and the functions are objects provided by the different theories to represent what
121//! the theory supports.
122//!
123//! The most straightforward way of building a term is the [term!] macro, which
124//! directly accepts a convenient subset of the SMT-LIBv2 syntax for terms. The macro constructs a
125//! temporary object which implements the [ToTerm] trait, so the [term!] macro can be used as
126//! argument to any method that accepts a [ToTerm] instance, such as the [Solver::require()] method
127//! used in the above example. The macro supports specifying both arbitrary names
128//! which will be looked-up by the [Solver] later, or expanding previously declared [ToTerm]
129//! objects. The [term!] macro only describes the term with an allocation-free local object, which
130//! needs a [Solver] or another instance of [TermPool] to be reified into a [Term].
131//!
132//! The [sort!] macro is similar, accepting a subset of the SMT-LIBv2 syntax for sorts. It also
133//! returns a temporary object implementing [ToTerm], because sorts can be seen as terms applying
134//! some arguments to sort constructors. Using the macro, sorts can be just mentioned by name and
135//! they will be looked up in the theory currently selected for the solver.
136//!
137//! Example:
138//! ```
139//! # mod formally {
140//! # pub extern crate formally_support as support;
141//! # pub extern crate formally_smt as smt;
142//! # }
143//! # use formally::{smt::*, support::*};
144//! # fn main() -> Result<()> {
145//! let config = Config::default().logic("LIA");
146//! let mut solver = Solver::new(&config)?;
147//!
148//! solver.declare(Declaration::constant("x", sort!(Int)))?;
149//! solver.declare(Declaration::constant("y", sort!(Int)))?;
150//!
151//! // here, `x` and `y` are looked up by `require()` in the solver's scope
152//! solver.require(term!(= (+ x y) 0));
153//!
154//! let z = solver.declare(Declaration::integer("z"))?;
155//! // here, `#z` refers to the Rust variable `z` declared above, of type [Declared], which
156//! // is expanded in place
157//! solver.require(term!(> #z 0))?;
158//!
159//! // any instance of ToTerm can be expanded
160//! let t1 = term!(> x y);
161//! let t2 = term!(> y z);
162//! let t3 = term!(> x z);
163//! solver.require(term!(=> (and #t1 #t2) #t3))?;
164//!
165//! # Ok(())
166//! # }
167//! ```
168//!
169//! ## Asserting terms and extracting models
170//!
171//! [Solvers](Solver) are instantiated with a reference to a [Config] object selecting, among other
172//! things, the required SMT logic. A specific SMT backend can be selected by instantiating
173//! the solver with the [Solver::with_backend()] constructor.
174//!
175//! Terms can be asserted with the [require()](Solver::require()) method of [Solver]. Then, the
176//! solver can be asked to check the current assertions for satisfiability by calling
177//! [check()](Solver::check()), which returns a [Result](formally::support::Result) holding a
178//! [Answer], a simple tri-valued enum telling [Yes](Answer::Yes), [No](Answer::No), or
179//! [Unknown](Answer::Unknown). Note that returning `Result<Answer>` means we distinguish
180//! the case of an error occurring during processing from the case where the solver itself was not
181//! able to find a solution without any erroneous condition.
182//!
183//! [Solver] implements [Stack](formally::support::Stack), so assertions can be stacked with
184//! [push()](formally::support::Stack::push()) and [pop()](formally::support::Stack::pop()) as is
185//! customary in most SAT/SMT solvers. Currently, these methods stack declarations and definitions
186//! as well (the SMT-LIBv2 standard admits an option to select this behavior, but it is not yet
187//! supported).
188//!
189//! After a call to [check()](Solver::check()) returned `Ok(Answer::Yes)`, and only if the
190//! `produce_models` option has been set to `true` in the [Solver]'s configuration, a model can be
191//! extracted with the [model()](Solver::model()) method. This method returns
192//! `Result<Option<Model>>`, so again we can distinguish the case where there is no model (because
193//! the last call to [check()](Solver::check()) was unsuccessful) from the case where an error
194//! occurred while extracting the model itself. The [Model] object can be used to extract the value
195//! of [ToTerm] objects using the [value()](ModelProvider::value()) method, which returns
196//! `Option<Term>`.
197//!
198//! Example:
199//! ```
200//! # mod formally {
201//! # pub extern crate formally_support as support;
202//! # pub extern crate formally_smt as smt;
203//! # }
204//! # use formally::{smt::*, support::*};
205//! # fn main() -> Result<()> {
206//! let config = Config::default().produce_models(true);
207//! let mut solver = Solver::new(&config)?;
208//!
209//! let p = solver.declare(Declaration::boolean("p"))?;
210//!
211//! solver.require(p.clone())?;
212//!
213//! assert_eq!(solver.check()?, Answer::Yes);
214//!
215//! assert_eq!(solver.model()?.unwrap().value(p)?.unwrap(), true);
216//! # Ok(())
217//! # }
218//! ```
219//!
220//! ## Parsing and executing SMT-LIBv2 scripts
221//!
222//! This crate supports for parsing and executing scripts in the SMT-LIBv2 language as defined by
223//! the [official specification document](https://smt-lib.org/language.shtml), currently updated to
224//! version 2.7. Everything that regards the SMT-LIBv2 language is contained in the [smtlib] module
225//! so we refer to that module's documentation for details.
226//!
227//! ## SMT backends
228//!
229//! Backends are types implementing the [Backend](backends::Backend) trait. Currently, we only
230//! provide two backends:
231//! - [backends::z3::Z3], implemented on top of the [z3_sys] crate.
232//! - [backends::cvc5::Cvc5], implemented on top of the [cvc5_sys] crate.
233//!
234//! See the documentation of the [Backend](backends::Solver) trait for information about how to
235//! implement new backends.
236//!
237//! ## Error handling
238//!
239//! [formally::smt] integrates with the error handling schema of `formally` by emitting diagnostics
240//! using the current global [Emitter](formally::support::Emitter). Almost all methods of [Solver]
241//! return a [Result](formally::support::Result) to account for possible failures which are detailed
242//! in the emitted diagnostics. See the documentation of [Result](formally::support::Result) and
243//! [Emitter](formally::support::Emitter) for details.
244//!
245//! ## Current limitations
246//!
247//! [formally::smt], like the rest of the framework, is under active development and many features
248//! are missing or incomplete. Here is a list of currently unimplemented or partially implemented
249//! features that are needed for a fully compliant support of SMT-LIBv2:
250//! 1. recursive function definitions
251//! 2. user-defined sort
252//! 3. enumerated sorts and algebraic data types
253//! 4. match expressions
254//! 5. proper representation of models and values coming from models
255//! 6. the complete taxonomy of standard SMT-LIBv2 theories and logics
256//! 7. many SMT-LIBv2 commands in [Interpreter](smtlib::interpreter::Interpreter)
257//! 8. many other little things...
258//!
259//! On top of that, more backends will be needed.
260//!
261//! The project as a whole also currently lacks a proper test suite for correctness and compliance
262//! with the language specification, so those features that are already implemented will have bugs.
263//!
264//! # Details
265//!
266//! ## Theories and logics
267//!
268//! [formally::smt] strictly follows the categories of theories and logics defined in the SMT-LIBv2
269//! standard. In this respect, theories (instances of the [Theory](theories::Theory) trait) describe
270//! the symbols (constants, functions and sorts) provided by the theory, while logics (instances of
271//! the [Logic](logics::Logic) trait) combine a few theories with a set of syntactic constraints.
272//! For example, [LIA](logics::LIA) combines the theories [Core](theories::Core) and
273//! [Ints](theories::Ints) and enforces the linearity of the terms, while [QF_ALIA](logics::QF_ALIA)
274//! combines [Core](theories::Core), [Ints](theories::Ints) and [Arrays](theories::Arrays) and
275//! enforces both linearity and the absense of quantifiers.
276//!
277//! Most SMT interfaces provide some number of functions to create all the different kind of
278//! supported primitives (e.g., an integer sum, or an array `select`). This means that an interface
279//! cannot support a theory which was not foreseen when the interface was designed. In
280//! [formally::smt], we adopt a different approach. In [TermKind], atoms are represented as just a
281//! [Function] applied to a number of [Term] arguments. The function, among other things, can be a
282//! [Primitive]. The latter are provided by theories, and theories can be declared by any backend
283//! solver independently of which other theories were foreseen in advance by the framework.
284//!
285//! Theories are declare using the `theories!{}` macro, which allows one to declare its sorts,
286//! constants and functions. Logics are declared similarly with the `logic!{}` macro. Standard
287//! theories and logics declared in this way are unit structs (i.e. an instance of
288//! [LIA](logics::LIA) is just `LIA`, not `LIA {}`), which provide one associated function for each
289//! symbol provided by the theory. For example, the `Ints` theory in SMT-LIBv2 provides the "+"
290//! binary function, which is accessible here as [Ints::plus()](theories::Ints::plus()). Note that
291//! naming these methods directly is seldom necessary because the symbols can be looked up by name
292//! when using the [term!] macro.
293//!
294//! For example, a term `term!(+ x y)` will contain an unbound name `"+"` which will be resolved by
295//! the solver when the term is used (e.g. in an assertion or a definition), and found to correspond
296//! to [Ints::plus()](theories::Ints::plus()) or any other symbol matching its arguments in the
297//! theories combined by the logic selected in the current backend (e.g.
298//! [Reals::plus()](theories::Reals::plus()) if `x` and `y` resolve to reals).
299//!
300//! The crate provides a set (currently incomplete) of standard theories and logics extracted from
301//! the SMT-LIBv2 standard, but new ones can be declared with the help of the [theories!] and
302//! [logic!] macros, so we refer to their documentation for details.
303//!
304//! ## Declaring sorts
305//!
306//! Sorts can be declared or defined in the same way as functions. Indeed, sorts are seen as just
307//! constants of the special sort [Sort::sort()]. A shortcut [Declaration::sort()] exists in place
308//! of calling `Declaration::constant(name, Sort::sort())`.
309//!
310//! Example:
311//! ```
312//! # mod formally {
313//! # pub extern crate formally_support as support;
314//! # pub extern crate formally_smt as smt;
315//! # }
316//! # use formally::{smt::*, support::*};
317//! # fn main() -> Result<()> {
318//! let config = Config::default().logic("UFLIA");
319//! let mut solver = Solver::new(&config)?;
320//!
321//! let people = solver.declare(Declaration::sort("People"))?;
322//! let food = solver.declare(Declaration::sort("Food"))?;
323//! let likes = solver.declare(Declaration::predicate("likes", [people.clone(), food.clone()]))?;
324//!
325//! solver.declare(Declaration::constant("mike", people))?;
326//! solver.declare(Declaration::constant("cake", food))?;
327//!
328//! solver.require(term!(not (likes mike cake)))?;
329//!
330//! # Ok(())
331//! # }
332//! ```
333//!
334//! Enumerated sorts and algebraic data types are not yet supported, but will be soon.
335//!
336//! ## Subterm sharing, [TermPool], and [TermManager]
337//!
338//! As in most other SMT APIs, [formally::smt] implements automatic subterm sharing. For this
339//! reason, to obtain an actual [Term] from a [ToTerm] object (e.g. from the result of the [term!]
340//! macro) one needs an instance of a type implementing [TermPool]. This instance can usually be
341//! obtained by [Solver::pool()].
342//!
343//! Example:
344//! ```
345//! # mod formally {
346//! # pub extern crate formally_support as support;
347//! # pub extern crate formally_smt as smt;
348//! # }
349//! # use formally::{smt::*, support::*};
350//! # fn main() -> Result<()> {
351//! let config = Config::default();
352//! let mut solver = Solver::new(&config)?;
353//!
354//! let x = solver.declare(Declaration::integer("x"))?;
355//! let y = solver.declare(Declaration::integer("y"))?;
356//!
357//! let term = term!(> x y).into_term_in(solver.pool());
358//!
359//! solver.require(term)?;
360//!
361//! # Ok(())
362//! # }
363//! ```
364//!
365//! Inside each [Solver], an instance of [TermManager] is responsible for providing a [TermPool] and
366//! for the conversion of [terms][Term] to the corresponding internal representation of the given
367//! backend (e.g. to [Z3_ast](z3_sys::Z3_ast) in the case of Z3). The [Solver::with_manager()]
368//! constructor can be used to instantiate a [Solver] with a specific instance of [TermManager],
369//! which can be shared between multiple solvers. Terms created over the same [TermManager] are
370//! usable interchangeably with every [Solver] instantiated over it, and will be converted to the
371//! underlying backend representation only once.
372//!
373//! [TermManager], in turn, relies on a specific data structure implementing the [TermPool] trait.
374//! By default, it uses an instance of [HashPool], which is based on standard hash tables.
375//!
376//! ## Multi-threading
377//!
378//! The [Term] type is [Send]+[Sync], since it is based on [Arc](std::sync::Arc), so terms can be
379//! safelty shared and accessed between multiple threads.
380//!
381//! However, since solvers and term managers of most backend SMT APIs are not thread safe, [Solver]
382//! and [TermManager] are *not* [Send] nor [Sync], and can therefore be used by a single thread at
383//! the time. Nevertheless, the [TermManager::with_pool] constructor can be used to instantiate a
384//! [TermManager] over a thread-safe term pool such as [DashPool], which is implemented on top of
385//! the concurrent hash table [DashMap](dashmap::DashMap). In this way, subterm sharing at the
386//! front-end level can be made to work concurrently, while different threads will instantiate their
387//! specific [TermManagers](TermManager) and [Solvers](Solver).
388
389mod formally {
390 pub use formally_io as io;
391 pub use formally_support as support;
392 pub extern crate self as smt;
393}
394
395#[doc(hidden)]
396pub mod exports {
397 pub use linkme::distributed_slice;
398 pub use paste::paste;
399}
400
401mod decl;
402mod pool;
403mod pretty;
404mod resolve;
405mod solver;
406mod sort;
407mod term;
408mod type_check;
409
410#[doc(hidden)]
411pub mod macros;
412pub use macros::backend;
413
414pub mod backends;
415pub mod logics;
416pub mod smtlib;
417pub mod theories;
418
419pub use decl::*;
420pub use pool::*;
421pub use resolve::*;
422pub use solver::*;
423pub use sort::*;
424pub use term::*;
425pub use type_check::*;