Skip to main content

formally_smt/
term.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
25use crate::*;
26use formally::support::*;
27
28use derive_more::From;
29use transitive::Transitive;
30
31pub use rug::{Integer, Rational};
32use std::fmt::Formatter;
33use std::{
34    fmt::Display,
35    hash::{Hash, Hasher},
36    sync::{Arc, Mutex},
37};
38
39/// A constant term.
40///
41/// Currently, only integer and rational constants are supported. The numbers are represented with
42/// arbitrary precision using the [rug] crate, whose types [Integer] and [Rational] are re-exported
43/// here for convenience.
44#[derive(Debug, Clone, Hash, PartialEq, Eq, Located, Locatable)]
45#[non_exhaustive]
46pub enum Constant {
47    /// An integer constant.
48    Integer {
49        value: Arc<Integer>,
50        span: Option<Span>,
51    },
52    /// A rational constant.
53    Rational {
54        value: Arc<Rational>,
55        span: Option<Span>,
56    },
57}
58
59impl From<Integer> for Constant {
60    fn from(value: Integer) -> Self {
61        Constant::Integer {
62            value: Arc::new(value),
63            span: None,
64        }
65    }
66}
67
68impl From<Rational> for Constant {
69    fn from(value: Rational) -> Self {
70        Constant::Rational {
71            value: Arc::new(value),
72            span: None,
73        }
74    }
75}
76
77/// A reference to a [Function] occurring at a given [Span].
78///
79/// This type is used as *head* of *bound atoms*, i.e. fully name-resolved atoms.
80#[derive(Debug, Clone, Hash, PartialEq, Eq, Located, Locatable)]
81pub struct BoundRef {
82    /// the function that is being applied.
83    pub function: Function,
84    /// the atom's source span.
85    pub span: Option<Span>,
86}
87
88impl From<Function> for BoundRef {
89    fn from(function: Function) -> Self {
90        BoundRef {
91            function,
92            span: None,
93        }
94    }
95}
96
97impl Display for BoundRef {
98    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99        write!(f, "{}", self.function.name())
100    }
101}
102
103/// A bound or unbound reference to a function.
104#[derive(Debug, Clone, Hash, PartialEq, Eq, From, Located, Locatable, Transitive)]
105#[allow(clippy::duplicated_attributes)]
106#[transitive(from(Function, BoundRef))]
107#[transitive(from(Variable, Function))]
108#[transitive(from(Primitive, Function))]
109#[transitive(from(UserFunction, Function))]
110#[transitive(from(Declared, UserFunction))]
111#[transitive(from(Defined, UserFunction))]
112pub enum FunctionRef {
113    /// A bound reference to a [Function]
114    Bound(BoundRef),
115    /// A reference to an unbound identifier
116    Unbound(Identifier<'static>),
117}
118
119impl Display for FunctionRef {
120    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121        match self {
122            FunctionRef::Bound(bound) => bound.fmt(f),
123            FunctionRef::Unbound(unbound) => unbound.fmt(f),
124        }
125    }
126}
127
128impl FunctionRef {
129    pub fn name(&self) -> &Identifier<'static> {
130        match self {
131            FunctionRef::Bound(bound) => bound.function.name(),
132            FunctionRef::Unbound(unbound) => unbound,
133        }
134    }
135}
136
137/// An atom term.
138///
139/// Atoms are applications of arguments to functions. These are the most common type of
140/// [terms][Term], which include also references to *constants* (functions without arguments) and
141/// *variables*.
142///
143/// Atoms can be *bound* or *unbound*:
144/// 1. bound atoms, with [FunctionRef::Bound] as `head`, refer to a specific [Function] object and
145///    therefore can be type checked directly.
146/// 2. unbound atoms, with [FunctionRef::Unbound] as `head`, contain only an [Identifier] in place
147///    of the applied function, so name resolution has to be performed on an unbound term before it
148///    can be type checked.
149///
150/// Most methods in [Solver] are responsible of performing name resolution (and type checking) on
151/// the terms they receive.
152#[derive(Debug, Clone, Hash, PartialEq, Eq, Located, Locatable, Transitive)]
153#[allow(clippy::duplicated_attributes)]
154#[transitive(from(Identifier<'static>, FunctionRef))]
155#[transitive(from(BoundRef, FunctionRef))]
156#[transitive(from(Function, BoundRef))]
157#[transitive(from(Variable, Function))]
158#[transitive(from(Primitive, Function))]
159#[transitive(from(UserFunction, Function))]
160#[transitive(from(Declared, UserFunction))]
161#[transitive(from(Defined, UserFunction))]
162pub struct Atom {
163    /// The head of the atom, i.e., the [Function] being applied.
164    pub head: FunctionRef,
165    /// The arguments of the atom.
166    pub arguments: Arc<[Term]>,
167    /// The source span of this atom.
168    pub span: Option<Span>,
169}
170
171impl From<FunctionRef> for Atom {
172    fn from(head: FunctionRef) -> Self {
173        Atom {
174            head,
175            arguments: Arc::default(),
176            span: None,
177        }
178    }
179}
180
181/// Either the `exists` or `forall` quantifier.
182#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
183pub enum Quantifier {
184    Forall,
185    Exists,
186}
187
188/// A quantified formula.
189///
190/// [Quantified] represents existentially or universally quantified formulas. The body must be of
191/// sort [Core::Bool()](theories::Core::Bool()) for the term to be considered well-typed.
192///
193/// The [variables](Variable) can be used in the body freely.
194#[derive(Debug, Clone, Hash, PartialEq, Eq, Located, Locatable)]
195pub struct Quantified {
196    /// Which quantifier is used.
197    pub quantifier: Quantifier,
198    /// The quantified variables.
199    pub variables: Arc<[Variable]>,
200    /// The body of the quantified formula.
201    pub body: Term,
202    /// The source span of the formula.
203    pub span: Option<Span>,
204}
205
206/// A binding of a variable to a term in a `let` expression.
207///
208/// The type is parameterized by a type `V: ToSort` representing the sorts of the variables and
209/// a type `T: ToTerm` representing the defining term of the binding.
210///
211/// [Binding] is usually constructed using [Binding::new()] and given to [Solver::lookup_binding()]
212/// to apply name resolution and type checking to its constituent parts.
213#[derive(Debug, Clone, Hash, PartialEq, Eq, Located, Locatable)]
214pub struct Binding<V: ToSort = Sort, T: ToTerm = Term> {
215    /// The variable
216    pub variable: Variable<V>,
217    pub def: T,
218    pub span: Option<Span>,
219}
220
221impl<T: TypeCheck + ToTerm> Binding<Infer, T> {
222    /// Construct a new [Binding] with a to-be-inferred sort.
223    ///
224    /// The result can be given to [Solver::lookup_binding()] to apply name resolution and type
225    /// checking, to obtain a binding actually usable in a [Let] term.
226    pub fn new(name: Identifier<'_>, def: T) -> Binding<Infer, T> {
227        let namespan = name.span();
228        Binding {
229            variable: Variable::new(name, Infer).over(namespan),
230            def,
231            span: None,
232        }
233    }
234}
235
236/// A `let` expression.
237#[derive(Debug, Clone, Hash, PartialEq, Eq, Located, Locatable)]
238pub struct Let {
239    /// The variable bindings of the `let` expression
240    pub bindings: Arc<[Binding]>,
241    /// The body of the `let` expression
242    pub body: Term,
243    /// The source span of the term
244    pub span: Option<Span>,
245}
246
247/// The payload of [Term] objects.
248///
249/// The [TermKind] enum lists the possible kinds of terms supported by the framework. A term's kind
250/// is also available through the [Term::kind()] method. Terms can be constructed from [TermKind]
251/// using a [TermPool], such as the one provided by [Solver::pool()].
252///
253/// As mentioned in the [overview](formally::smt), we differ from most SMT APIs in that we do not
254/// offer multiple functions and/or types, one for each possible term node (addition, subtraction,
255/// conjunction, etc.), but rather we have a single notion of [Atom] which is the application of a
256/// [Function] to a list of argument terms. This allows maximum flexibility, while still keeping the
257/// construction of terms easy thanks to the [term!] macro. This is why this enum has relatively few
258/// variants.
259#[derive(Debug, Clone, Hash, PartialEq, Eq, From, Located, Locatable)]
260#[allow(clippy::duplicated_attributes)]
261#[non_exhaustive]
262pub enum TermKind {
263    /// A constant.
264    Constant(Constant),
265    /// An atom.
266    #[from(skip)]
267    Atom(Atom),
268    /// A quantified formula
269    Quantified(Quantified),
270    /// A `let` expression
271    Let(Let),
272}
273
274impl<T: Into<Atom>> From<T> for TermKind {
275    fn from(atom: T) -> Self {
276        TermKind::Atom(atom.into())
277    }
278}
279
280/// An SMT term.
281///
282/// [Term] objects represent SMT terms as used throughout the framework. The objects themselves are
283/// shared references to [TermKind] objects which contain the actual data.
284///
285/// Terms are uniqued and shared through the use of a [TermPool], usually indirectly by means of
286/// a [Solver]. Constructing a [Term] directly is often not needed, because most methods that
287/// would accept one accept instead generic instances of [ToTerm], which include the result of the
288/// [term!] macro, which is the recommended way of constructing terms.
289///
290/// See the [ToTerm] trait for more informations about how to construct [Term] objects from [ToTerm]
291/// instances.
292#[derive(Debug, Clone, Hash, PartialEq, Eq)]
293pub struct Term(pub(crate) Nominal<Arc<TermInner>>);
294
295#[derive(Debug)]
296pub(crate) struct TermInner {
297    pub(crate) kind: TermKind,
298    pub(crate) sort: Mutex<Option<Sort>>,
299    pub(crate) resolved: bool,
300}
301
302impl TermInner {
303    pub(crate) fn new(kind: TermKind) -> TermInner {
304        let resolved = match &kind {
305            TermKind::Constant(_) => true,
306            TermKind::Atom(atom) => {
307                matches!(&atom.head, FunctionRef::Bound(_))
308                    && atom.arguments.iter().all(Term::is_resolved)
309            }
310            TermKind::Quantified(quant) => quant.body.is_resolved(),
311            TermKind::Let(let_) => let_.body.is_resolved(),
312        };
313
314        TermInner {
315            kind,
316            sort: Mutex::default(),
317            resolved,
318        }
319    }
320}
321
322impl Term {
323    pub fn is_resolved(&self) -> bool {
324        self.0.resolved
325    }
326}
327
328impl Hash for TermInner {
329    fn hash<H: Hasher>(&self, state: &mut H) {
330        self.kind.hash(state)
331    }
332}
333
334impl PartialEq for TermInner {
335    fn eq(&self, other: &Self) -> bool {
336        self.kind == other.kind
337    }
338}
339
340impl Eq for TermInner {}
341
342impl Located for Term {
343    fn span(&self) -> Option<Span> {
344        self.0.kind.span()
345    }
346}
347
348impl From<bool> for TermKind {
349    fn from(value: bool) -> Self {
350        if value {
351            TermKind::from(theories::Core::True())
352        } else {
353            TermKind::from(theories::Core::False())
354        }
355    }
356}
357
358impl Term {
359    /// Get this term's [TermKind].
360    pub fn kind(&self) -> &TermKind {
361        &self.0.kind
362    }
363}
364
365impl PartialEq<bool> for Term {
366    fn eq(&self, other: &bool) -> bool {
367        if let TermKind::Atom(atom) = self.kind()
368            && let Atom { head, .. } = atom
369            && let FunctionRef::Bound(bound) = head
370            && let BoundRef { function, .. } = bound
371            && let Function::Primitive(prim) = function
372        {
373            (*other && *prim == theories::Core::True())
374                || (!*other && *prim == theories::Core::False())
375        } else {
376            false
377        }
378    }
379}
380
381impl PartialEq<Integer> for Term {
382    fn eq(&self, other: &Integer) -> bool {
383        if let TermKind::Constant(cnst) = self.kind()
384            && let Constant::Integer { value, .. } = cnst
385        {
386            **value == *other
387        } else {
388            false
389        }
390    }
391}
392
393impl PartialEq<Rational> for Term {
394    fn eq(&self, other: &Rational) -> bool {
395        if let TermKind::Constant(cnst) = self.kind()
396            && let Constant::Rational { value, .. } = cnst
397        {
398            **value == *other
399        } else {
400            false
401        }
402    }
403}