Skip to main content

formally_smt/
resolve.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 std::panic::AssertUnwindSafe;
29use std::{collections::HashMap, iter::zip, sync::Arc};
30
31/// Trait for types that can perform name resolution on themselves.
32///
33/// The [Resolve::resolve()] method is called by [Solver] in many situations (e.g. on the term of
34/// the body of a definition). See also [ToSort].
35pub trait Resolve: Sized {
36    /// Perform name resolution on `self`.
37    ///
38    /// Name resolution means different thing on different types. For [Term], for example, it means
39    /// to replace all the [unbound name references](FunctionRef::Unbound) with actual
40    /// [functions](Function).
41    ///
42    /// Name resolution is usually a prerequisite for [type checking](TypeCheck).
43    ///
44    /// Name resolution is performed using the scopes in the given [environment](Env). SMT-LIBv2 has
45    /// two different namespaces for name lookup for sorts and for functions, therefore when
46    /// performing name resolution the [Role] argument specifies if the names in `self` have to be
47    /// looked up as sorts or as functions. Note that this is a hint which is interpreted
48    /// differently by different implementors of the trait.
49    ///
50    /// The [TermPool] argument is used for uniquing terms used to construct the resolved instance
51    /// if needed.
52    fn resolve(&self, env: &Env, pool: &dyn TermPool, role: Role) -> Result<Self>;
53}
54
55impl Resolve for Term {
56    fn resolve(&self, env: &Env, pool: &dyn TermPool, role: Role) -> Result<Term> {
57        if self.is_resolved() {
58            return Ok(self.clone());
59        }
60
61        Ok(match self.kind() {
62            TermKind::Constant(_) => self.clone(),
63            TermKind::Atom(atom) => Self::resolve_atom(atom, env, role, pool)?.into_term_in(pool),
64            TermKind::Quantified(quant) => {
65                TermKind::Quantified(Self::resolve_quant(quant, env, role, pool)?)
66                    .into_term_in(pool)
67            }
68            TermKind::Let(let_) => {
69                TermKind::Let(Self::resolve_let(let_, env, role, pool)?).into_term_in(pool)
70            }
71        })
72    }
73}
74
75impl Term {
76    fn resolve_atom(atom: &Atom, env: &Env, role: Role, pool: &dyn TermPool) -> Result<Atom> {
77        match &atom.head {
78            FunctionRef::Bound(bound) => Self::resolve_bound(bound, env, &atom.arguments, pool),
79            FunctionRef::Unbound(unbound) => {
80                Self::resolve_unbound(unbound, env, &atom.arguments, role, pool)
81            }
82        }
83    }
84
85    fn resolve_bound(
86        head: &BoundRef,
87        env: &Env,
88        arguments: &[Term],
89        pool: &dyn TermPool,
90    ) -> Result<Atom> {
91        let domain = head.domain(arguments.len());
92
93        if domain.len() != arguments.len() {
94            error!(
95                head.span(),
96                "applied {} arguments to a function of {} parameters",
97                arguments.len(),
98                domain.len(),
99            );
100            return Err(DiagnosticEmitted);
101        }
102
103        let mut resolved = Vec::new();
104        for (sort, arg) in zip(domain, arguments) {
105            if sort == Sort::sort() {
106                resolved.push(arg.resolve(env, pool, Role::Sort)?);
107            } else {
108                resolved.push(arg.resolve(env, pool, Role::Function)?);
109            }
110        }
111
112        Ok(Atom {
113            head: FunctionRef::Bound(head.clone()),
114            arguments: Arc::from(resolved.into_boxed_slice()),
115            span: head.span.clone(),
116        })
117    }
118
119    fn resolve_unbound(
120        head: &Identifier<'static>,
121        env: &Env,
122        arguments: &[Term],
123        role: Role,
124        pool: &dyn TermPool,
125    ) -> Result<Atom> {
126        let mut resolved = Vec::with_capacity(arguments.len());
127        let mut argsorts = Vec::with_capacity(arguments.len());
128        for arg in arguments {
129            let t = arg.resolve(env, pool, role)?;
130            argsorts.push(Sort::of(&t)?);
131            resolved.push(t);
132        }
133
134        env.lookup(head.clone(), role)
135            .filter_map(move |f| Self::candidate(f, env, head, &argsorts, &resolved, pool))
136            .one()
137    }
138
139    fn candidate(
140        f: &Function,
141        env: &Env,
142        head: &Identifier<'_>,
143        argsorts: &[Sort],
144        arguments: &[Term],
145        pool: &dyn TermPool,
146    ) -> Option<Atom> {
147        let bound = BoundRef {
148            function: f.clone(),
149            span: head.span(),
150        };
151        let atom = Diagnostic::with(
152            NullEmitter,
153            AssertUnwindSafe(|| Self::resolve_bound(&bound, env, arguments, pool)),
154        )
155        .ok()?;
156
157        #[allow(clippy::mutable_key_type)]
158        let mut matches = HashMap::new();
159        let domain = bound.domain(arguments.len());
160        for (sort, arg) in zip(domain, argsorts) {
161            if !sort.matches_with(arg, &mut matches) {
162                return None;
163            }
164        }
165
166        Some(atom)
167    }
168
169    fn resolve_quant(
170        quant: &Quantified,
171        env: &Env,
172        role: Role,
173        pool: &dyn TermPool,
174    ) -> Result<Quantified> {
175        let mut nested = Env::new().with_parent(env.clone());
176
177        let mut variables = Vec::with_capacity(quant.variables.len());
178        for var in &*quant.variables {
179            let var = var.resolve(env, pool, role)?;
180            variables.push(var.clone());
181            nested
182                .functions
183                .add(var.name().name(), Function::from(var.clone()));
184        }
185
186        let body = quant.body.resolve(&nested, pool, role)?;
187
188        Ok(Quantified {
189            quantifier: quant.quantifier,
190            variables: Arc::from(variables.into_boxed_slice()),
191            body,
192            span: quant.span.clone(),
193        })
194    }
195
196    fn resolve_let(let_: &Let, env: &Env, role: Role, pool: &dyn TermPool) -> Result<Let> {
197        let mut nested = Env::new().with_parent(env.clone());
198
199        for bind in &*let_.bindings {
200            nested.functions.add(
201                bind.variable.name().name(),
202                Function::from(bind.variable.clone()),
203            );
204        }
205
206        let body = let_.body.resolve(&nested, pool, role)?;
207
208        Ok(Let {
209            bindings: let_.bindings.clone(),
210            body,
211            span: let_.span.clone(),
212        })
213    }
214}
215
216impl Resolve for Variable {
217    fn resolve(&self, env: &Env, pool: &dyn TermPool, _role: Role) -> Result<Self> {
218        Ok(Variable::new(
219            self.name().clone(),
220            self.sort().resolve(env, pool, Role::Sort)?,
221        )
222        .over(self.span()))
223    }
224}
225
226impl Resolve for Sort {
227    fn resolve(&self, env: &Env, pool: &dyn TermPool, role: Role) -> Result<Self> {
228        if role == Role::Function {
229            internal!(None, "cannot resolve a sort with Role::Function");
230            return Err(DiagnosticEmitted);
231        }
232
233        let term = self.to_term_in(pool);
234        let resolved = term.resolve(env, pool, Role::Sort)?;
235        let sort = Sort::try_from(resolved).ok().unwrap();
236
237        Ok(sort)
238    }
239}
240
241impl Resolve for Declared {
242    fn resolve(&self, _env: &Env, _pool: &dyn TermPool, _role: Role) -> Result<Self> {
243        Ok(self.clone())
244    }
245}
246
247impl Resolve for Defined {
248    fn resolve(&self, _env: &Env, _pool: &dyn TermPool, _role: Role) -> Result<Self> {
249        Ok(self.clone())
250    }
251}