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}