Skip to main content

formally_smt/
sort.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::{Display, From};
29use transitive::Transitive;
30
31use std::{
32    collections::HashMap,
33    fmt::{Debug, Display, Formatter},
34    iter::zip,
35    sync::Arc,
36};
37
38/// Trait for types that support name resolution, type checking, and conversion into sorts.
39///
40/// Most methods in the framework that would accept a sort accept instead a generic instance of
41/// [ToSort]. The purpose of this generality is mainly that of allowing one to accept a [Term]
42/// representing a sort instead of an actual [Sort] object.
43///
44/// Since [Sort] is a purely semantic object, it does not track location information. This means
45/// that errors in name resolution and type checking would produce error messages without precise
46/// location information. Front-ends (such as the SMT-LIBv2 frontend we provide) may therefore want
47/// instead to parse the sorts as [Term] objects and then pass those terms to whatever method
48/// expects a [ToSort]. Type checking and name resolution would then be performed on the [Term]
49/// directly before conversion into [Sort], obtaining informative error messages.
50///
51/// This trait is automatically implemented for every type implementing [Resolve], [TypeCheck] and
52/// `TryInto<Sort, Error: Emit>`.
53pub trait ToSort: Resolve + TypeCheck + TryInto<Sort, Error: Emit> {}
54
55impl<T: Resolve + TypeCheck + TryInto<Sort, Error: Emit>> ToSort for T {}
56
57/// Marker type to ask for type inference.
58///
59/// This type is currently used in the return type of [Binding::new()] to signal that the sort
60/// of the binding must be inferred from the body.
61#[derive(Clone, Copy)]
62pub struct Infer;
63
64impl Resolve for Infer {
65    fn resolve(&self, _env: &Env, _pool: &dyn TermPool, _role: Role) -> Result<Self> {
66        Ok(*self)
67    }
68}
69
70impl TypeCheck for Infer {
71    fn type_check(&self) -> Result<Sort> {
72        internal!(None, "type checking of an inference placeholder");
73        Err(DiagnosticEmitted)
74    }
75}
76
77impl TryFrom<Infer> for Sort {
78    type Error = Diagnostic;
79
80    fn try_from(_value: Infer) -> Result<Self, Diagnostic> {
81        Err(Diagnostic::new(
82            None,
83            "type checking of an inference place holder".to_string(),
84        ))
85    }
86}
87
88/// An argument in a parametric sort such as `Int` and `Real` in `(Array Int Real)`.
89#[derive(Clone, Debug, Hash, PartialEq, Eq, Transitive)]
90#[allow(clippy::duplicated_attributes)]
91pub enum SortArgument {
92    /// An integer argument (e.g., `32` in `(_ BitVec 32)`).
93    Value(Arc<Integer>),
94    /// A sort argument (e.g., `Int` and `Real` in `(Array Int Real)`).
95    Sort(Sort),
96}
97
98impl SortArgument {
99    #[allow(clippy::mutable_key_type)]
100    pub(crate) fn matches_with(
101        &self,
102        instance: &SortArgument,
103        matches: &mut HashMap<Variable, Sort>,
104    ) -> bool {
105        match (self, instance) {
106            (SortArgument::Value(this), SortArgument::Value(inst)) => this == inst,
107            (SortArgument::Sort(this), SortArgument::Sort(inst)) => {
108                this.matches_with(inst, matches)
109            }
110            _ => false,
111        }
112    }
113}
114
115impl From<Integer> for SortArgument {
116    fn from(value: Integer) -> Self {
117        SortArgument::Value(Arc::new(value))
118    }
119}
120
121impl<T: Into<Sort>> From<T> for SortArgument {
122    fn from(value: T) -> Self {
123        SortArgument::Sort(value.into())
124    }
125}
126
127/// The head of a [Sort], i.e. the sort constructor being applied.
128///
129/// Similar to [atoms](Atom), a sort can be *bound* or *unbound*. The latter only refer to their
130/// sort constructor by name and need name resolution to be usable.
131///
132/// See also the [ToSort] trait.
133#[derive(Debug, Clone, Hash, PartialEq, Eq, From, Transitive)]
134#[allow(clippy::duplicated_attributes)]
135#[transitive(from(Variable, Function))]
136#[transitive(from(Primitive, Function))]
137#[transitive(from(UserFunction, Function))]
138#[transitive(from(Declared, UserFunction))]
139#[transitive(from(Defined, UserFunction))]
140pub enum SortHead {
141    Bound(Function),
142    Unbound(Identifier<'static>),
143}
144
145impl SortHead {
146    pub fn name(&self) -> &Identifier<'static> {
147        match self {
148            SortHead::Bound(bound) => bound.name(),
149            SortHead::Unbound(name) => name,
150        }
151    }
152}
153
154impl From<FunctionRef> for SortHead {
155    fn from(funcref: FunctionRef) -> Self {
156        match funcref {
157            FunctionRef::Bound(bound) => SortHead::Bound(bound.function),
158            FunctionRef::Unbound(unbound) => SortHead::Unbound(unbound),
159        }
160    }
161}
162
163impl From<SortHead> for FunctionRef {
164    fn from(head: SortHead) -> Self {
165        match head {
166            SortHead::Bound(function) => FunctionRef::Bound(BoundRef {
167                function,
168                span: None,
169            }),
170            SortHead::Unbound(name) => FunctionRef::Unbound(name),
171        }
172    }
173}
174
175impl Display for SortHead {
176    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
177        match self {
178            SortHead::Bound(func) => write!(f, "{}", func.name()),
179            SortHead::Unbound(name) => write!(f, "{name}"),
180        }
181    }
182}
183
184/// The sort of a term.
185///
186/// [Sort] represent the semantic notion of sort of a term, i.e. its type. Sorts are represented by
187/// the application of some arguments to a function, called *sort constructor*, whose range must be
188/// [Sort::sort()], the sort of sorts.
189///
190/// In contrast to [Term], sorts can be constructed directly and are not uniqued.
191///
192/// Most methods that would accept a [Sort] accept instead a generic instance of the [ToSort] trait.
193/// See its documentation for details.
194#[allow(clippy::duplicated_attributes)]
195#[derive(Clone, Hash, PartialEq, Eq)]
196pub struct Sort {
197    /// The sort constructor that is being applied.
198    pub head: SortHead,
199    /// The sort's arguments.
200    pub arguments: SArc<[SortArgument]>,
201}
202
203impl<T: Into<Function>> From<T> for Sort {
204    fn from(value: T) -> Self {
205        Sort {
206            head: SortHead::Bound(value.into()),
207            arguments: SArc::default(),
208        }
209    }
210}
211
212impl From<Identifier<'_>> for Sort {
213    fn from(value: Identifier<'_>) -> Self {
214        Sort {
215            head: SortHead::Unbound(value.into_owned()),
216            arguments: SArc::default(),
217        }
218    }
219}
220
221impl Debug for Sort {
222    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
223        if *self == Sort::sort() {
224            write!(f, "Sort::sort()")
225        } else {
226            f.debug_struct("Sort").field("head", &self.head).finish()
227        }
228    }
229}
230
231/// Error type for `TryFrom<Term> for Sort`
232#[derive(Debug, Clone, Located, Display)]
233#[display("sort term must be an atom")]
234pub struct InvalidSortTerm {
235    span: Option<Span>,
236}
237
238impl Diagnosable for InvalidSortTerm {}
239
240impl TryFrom<Term> for Sort {
241    type Error = InvalidSortTerm;
242
243    fn try_from(term: Term) -> Result<Sort, InvalidSortTerm> {
244        let TermKind::Atom(atom) = term.kind() else {
245            return Err(InvalidSortTerm { span: term.span() });
246        };
247
248        let mut arguments = Vec::with_capacity(atom.arguments.len());
249        for arg in &*atom.arguments {
250            match arg.kind() {
251                TermKind::Constant(c) => match c {
252                    Constant::Integer { value, .. } => {
253                        arguments.push(SortArgument::Value((*value).clone()))
254                    }
255                    Constant::Rational { .. } => return Err(InvalidSortTerm { span: term.span() }),
256                },
257                _ => arguments.push(SortArgument::Sort(Sort::try_from(arg.clone())?)),
258            }
259        }
260
261        let head = SortHead::from(atom.head.clone());
262
263        Ok(Sort {
264            head,
265            arguments: SArc::from(arguments.into_boxed_slice()),
266        })
267    }
268}
269
270impl Sort {
271    /// Alias for `term.type_check(ctx)` which provide a better notation.
272    pub fn of(term: &Term) -> Result<Sort> {
273        term.type_check()
274    }
275
276    #[allow(clippy::mutable_key_type)]
277    pub(crate) fn matches_with(
278        &self,
279        argument: &Sort,
280        matches: &mut HashMap<Variable, Sort>,
281    ) -> bool {
282        let SortHead::Bound(func) = &self.head else {
283            return false;
284        };
285
286        let SortHead::Bound(argfunc) = &argument.head else {
287            return false;
288        };
289
290        match (&func, &argfunc) {
291            (Function::Variable(this), _) => {
292                if let Some(this) = matches.get(this).cloned() {
293                    this.head == argument.head
294                        && this.arguments.len() == argument.arguments.len()
295                        && zip(&*this.arguments, &*argument.arguments)
296                            .all(|(this, arg)| this.matches_with(arg, matches))
297                } else {
298                    matches.insert(this.clone(), argument.clone());
299                    true
300                }
301            }
302            (Function::Primitive(this), Function::Primitive(inst)) => {
303                this == inst
304                    && self.arguments.len() == argument.arguments.len()
305                    && zip(self.arguments.iter(), argument.arguments.iter())
306                        .all(|(this, inst)| this.matches_with(inst, matches))
307            }
308            (Function::User(this), Function::User(inst)) => match (this, inst) {
309                (UserFunction::Declared(this), UserFunction::Declared(inst)) => {
310                    this == inst
311                        && self.arguments.len() == argument.arguments.len()
312                        && zip(self.arguments.iter(), argument.arguments.iter())
313                            .all(|(this, inst)| this.matches_with(inst, matches))
314                }
315                (UserFunction::Defined(_), UserFunction::Defined(_)) => {
316                    todo!() // we need to expand the definitions
317                }
318                _ => false,
319            },
320            _ => false,
321        }
322    }
323
324    #[allow(clippy::mutable_key_type)]
325    pub(crate) fn instantiate(&self, matches: &HashMap<Variable, Sort>) -> Result<Sort> {
326        match &self.head {
327            SortHead::Bound(func) if let Function::Variable(var) = &func => {
328                return matches.get(var).cloned().ok_or_else(|| {
329                    internal!(
330                        None,
331                        "usage of unconstrained sort parameter: {}",
332                        var.name()
333                    );
334                    DiagnosticEmitted
335                });
336            }
337            _ => {}
338        }
339
340        let mut arguments = Vec::with_capacity(self.arguments.len());
341        for arg in &*self.arguments {
342            match arg {
343                SortArgument::Value(term) => arguments.push(SortArgument::Value(term.clone())),
344                SortArgument::Sort(sort) => {
345                    arguments.push(SortArgument::Sort(sort.instantiate(matches)?))
346                }
347            }
348        }
349
350        Ok(Sort {
351            head: self.head.clone(),
352            arguments: SArc::from(arguments.into_boxed_slice()),
353        })
354    }
355}