Skip to main content

dada_ir_sym/check/red/
lattice.rs

1use dada_ir_ast::diagnostic::{Err, Errors};
2use dada_util::Set;
3use itertools::Itertools;
4
5use crate::{
6    check::{env::Env, predicates::Predicate},
7    ir::types::SymPlace,
8};
9
10use super::{Live, RedChain, RedLink, RedPerm, sub::chain_sub_chain};
11
12/// The **least upper bound** (LUB) of two permissions `(perm1, perm2)`.
13/// The LUB `perm3` is a permission such that `perm1 <: perm3` and `perm1 <: perm3`
14/// but also there is no other mutual upper bound `perm4` where `perm4 <: perm3`.
15/// In other words, if `perm1` and `perm2` represent lower bounds in inference
16/// (i.e., for some inference variable `?X`, `perm1 <: ?X` and `perm2 <: ?X`),
17/// then `perm3` combines  those two bounds on `?X` into a single bound `perm3 <: ?X`
18/// that must also hold (because there is no possible value for `?X` that wouldn't
19/// satisfy it).
20///
21/// Computing the LUB is fairly trivial for us because we can just union the chains.
22/// This is not necessarily the most compact representation of the LUB but it is correct.
23/// We do some minimal simplification.
24///
25/// # Examples
26///
27/// * `lub_perms([our], [ref[x]]) = [ref[x]]`.
28///   * The union would be `[our, ref[x]]` but just `[ref[x]]` is equivalent and more compact.
29///     * Equivalent because `(our | ref[x]) <: ref[x]` and `ref[x] <: (our | ref[x]))`
30/// * `lub_perms([ref[x.y]], [ref[x]]) = [ref[x]]`.
31///   * Another example where the union `[ref[x], ref[x.y]]` contains redundancies.
32/// * `lub_perms([ref[x.y]], [ref[x.z]]) = [ref[x.y], ref[x.z]]`.
33///   * Union suffices.
34/// * `lub_perms([ref[x.y]], [ref[x.z]]) = [ref[x.y], ref[x.z]]`.
35pub fn lub_perms<'db>(env: &Env<'db>, perm1: RedPerm<'db>, perm2: RedPerm<'db>) -> RedPerm<'db> {
36    let db = env.db();
37
38    let mut dedup = Set::default();
39    let mut candidates = perm1
40        .chains(db)
41        .iter()
42        .chain(perm2.chains(db))
43        .copied()
44        .filter(|&c| dedup.insert(c))
45        .collect::<Vec<_>>();
46
47    match simplify(env, &mut candidates) {
48        Ok(()) => (),
49        Err(reported) => {
50            return RedPerm::err(db, reported);
51        }
52    }
53
54    RedPerm::new(db, candidates)
55}
56
57/// The **greatest lower bound** (GLB) of two permissions `(perm1, perm2)`, if it exists.
58/// The GLB `perm3` is a permission such that `perm3 <: perm1` and `perm3 <: perm2`
59/// but also there is no other mutual lower bound `perm4` where `perm3 <: perm4`.
60/// In other words, if `perm1` and `perm2` represent upper bounds in inference
61/// (i.e., for some inference variable `?X`, `?X <: perm1` and `?X <: perm2`),
62/// then `perm3` combines  those two bounds on `?X` into a single bound `?X <: perm3`
63/// that must also hold (because there is no possible value for `?X` that wouldn't
64/// satisfy it).
65///
66/// # Examples
67///
68/// * `glb_perms([our], [ref[x]]) = [our]`
69/// * `glb_perms([ref[x] mut[y.z]], [ref[x.y] mut[y]]) = [ref[x.y] mut[y.z]]`
70pub fn glb_perms<'db>(
71    env: &Env<'db>,
72    perm1: RedPerm<'db>,
73    perm2: RedPerm<'db>,
74) -> Option<RedPerm<'db>> {
75    GreatestLowerBound::glb(env, perm1, perm2).ok()
76}
77
78struct NoGlb;
79type Glb<T> = Result<T, NoGlb>;
80
81trait GreatestLowerBound<'db>: Sized {
82    fn glb(env: &Env<'db>, l1: Self, l2: Self) -> Glb<Self>;
83}
84
85impl<'db> GreatestLowerBound<'db> for RedPerm<'db> {
86    fn glb(env: &Env<'db>, l1: RedPerm<'db>, l2: RedPerm<'db>) -> Glb<RedPerm<'db>> {
87        // The algorithm:
88        //
89        // * Compute a list of candidates L consisting of
90        //   * all `glb(c1, c2)` that exist from
91        //     * each pair of chains `(c1, c2)` in `(l1.chains x l2.chains)`
92        // * Simplify the candidates by removing any candidate c1 where
93        //   * there exists another candidate c2 and
94        //     * c1 < c2
95        //
96        // If L is empty, there is no GLB. Otherwise, result is the simplified list L.
97        //
98        // Examples:
99        //
100        // * [[ref[a], ref[b.x]] vs [ref[b]]
101        //   * Initial candidate list = [our, ref[b.x]] because
102        //     * glb(ref[a], ref[b]) = our
103        //     * glb(ref[b.x], ref[b]) = ref[b.x]
104        //   * Simplified candidate list = [ref[b.x]]
105        // * [[mut[a], mut[b.x]] vs [mut[b]]
106        //   * Initial candidate list = [mut[b.x]] because
107        //     * glb(mut[a], mut[b]) does not exist
108        //     * glb(mut[b.x], mut[b]) = mut[b.x]
109        //   * Simplified candidate list = [mut[b.x]]
110        // * [[ref[b.x] mut[c]] vs [ref[b.x] mut[c.d], ref[b]]
111        //   * Initial candidate list = [ref[b.x] mut[c.d]] because
112        //     * glb([ref[b.x] mut[c], [ref[b.x] mut[c.d]) = [ref[b.x], mut[c.d]]
113        //     * glb([ref[b.x] mut[c], [ref[b]) = error
114
115        let db = env.db();
116
117        let mut unique = Set::default();
118        let mut candidates: Vec<RedChain<'db>> = l1
119            .chains(db)
120            .iter()
121            .cartesian_product(l2.chains(db))
122            .filter_map(|(&l1, &l2)| RedChain::glb(env, l1, l2).ok())
123            .filter(|&c| unique.insert(c))
124            .collect();
125
126        // Remove any chain `c1` which is a subchain of some other chain `c2`.
127        //
128        // so e.g. if you have `[our, ref[x]]`, `our <: ref[x]`, so remove `our`.
129        match simplify(env, &mut candidates) {
130            Ok(()) => (),
131            Err(reported) => {
132                return Ok(RedPerm::err(db, reported));
133            }
134        }
135
136        if candidates.is_empty() {
137            Err(NoGlb)
138        } else {
139            Ok(RedPerm::new(db, candidates))
140        }
141    }
142}
143
144impl<'db> GreatestLowerBound<'db> for RedChain<'db> {
145    fn glb(env: &Env<'db>, chain1: RedChain<'db>, chain2: RedChain<'db>) -> Glb<RedChain<'db>> {
146        let db = env.db();
147        let mut links1 = &chain1.links(db)[..];
148        let mut links2 = &chain2.links(db)[..];
149
150        let mut links_glb = vec![];
151        loop {
152            match (links1, links2) {
153                ([], []) => break,
154
155                ([RedLink::Our], o) | (o, [RedLink::Our]) => {
156                    // The only way to get `RedLink::Our` as the only item in the list
157                    // is if it is the first thing in the list.
158                    assert!(links_glb.is_empty());
159                    return match RedLink::are_copy(env, o) {
160                        Ok(true) => Ok(RedChain::our(db)),
161                        Ok(false) => Err(NoGlb),
162                        Err(reported) => Ok(RedChain::err(db, reported)),
163                    };
164                }
165
166                ([RedLink::Our, RedLink::Mut(..), tail1 @ ..], [RedLink::Our, tail2 @ ..])
167                | ([RedLink::Our, tail2 @ ..], [RedLink::Our, RedLink::Mut(..), tail1 @ ..]) => {
168                    links_glb.push(RedLink::Our);
169                    links1 = tail1;
170                    links2 = tail2;
171                }
172
173                (
174                    [RedLink::Our, RedLink::Mut(live1, place1), tail1 @ ..],
175                    [RedLink::Ref(live2, place2), tail2 @ ..],
176                )
177                | (
178                    [RedLink::Ref(live2, place2), tail2 @ ..],
179                    [RedLink::Our, RedLink::Mut(live1, place1), tail1 @ ..],
180                ) => {
181                    links_glb.push(match SymPlace::glb(env, *place1, *place2) {
182                        Ok(place3) => RedLink::Ref(glb_live(*live1, *live2), place3),
183                        Err(NoGlb) => RedLink::Our,
184                    });
185
186                    links1 = tail1;
187                    links2 = tail2;
188                }
189
190                ([head1, tail1 @ ..], [head2, tail2 @ ..]) => {
191                    links_glb.push(RedLink::glb(env, *head1, *head2)?);
192                    links1 = tail1;
193                    links2 = tail2;
194                }
195
196                ([], [_, ..]) | ([_, ..], []) => {
197                    // Uneven lengths.
198                    return Err(NoGlb);
199                }
200            }
201        }
202
203        Ok(RedChain::new(db, links_glb))
204    }
205}
206
207impl<'db> GreatestLowerBound<'db> for RedLink<'db> {
208    fn glb(env: &Env<'db>, l1: RedLink<'db>, l2: RedLink<'db>) -> Glb<RedLink<'db>> {
209        match (l1, l2) {
210            (RedLink::Err(reported), _) | (_, RedLink::Err(reported)) => Ok(RedLink::Err(reported)),
211
212            (RedLink::Our, RedLink::Ref(..))
213            | (RedLink::Ref(..), RedLink::Our)
214            | (RedLink::Our, RedLink::Our) => Ok(RedLink::Our),
215
216            (RedLink::Our, RedLink::Var(v)) | (RedLink::Var(v), RedLink::Our) => {
217                if env.var_is_declared_to_be(v, Predicate::Shared) {
218                    Ok(RedLink::Our)
219                } else {
220                    Err(NoGlb)
221                }
222            }
223
224            (RedLink::Ref(live1, p1), RedLink::Ref(live2, p2)) => {
225                match SymPlace::glb(env, p1, p2) {
226                    Ok(p3) => Ok(RedLink::Ref(glb_live(live1, live2), p3)),
227                    Err(NoGlb) => Ok(RedLink::Our),
228                }
229            }
230
231            (RedLink::Mut(live1, p1), RedLink::Mut(live2, p2)) => {
232                let p3 = SymPlace::glb(env, p1, p2)?;
233                Ok(RedLink::Mut(glb_live(live1, live2), p3))
234            }
235
236            (RedLink::Var(v1), RedLink::Var(v2)) => {
237                if v1 == v2 {
238                    Ok(RedLink::Var(v1))
239                } else {
240                    Err(NoGlb)
241                }
242            }
243
244            (RedLink::Ref(..), RedLink::Var(v)) | (RedLink::Var(v), RedLink::Ref(..)) => {
245                // Subtle: we canonicalize vars to `Our` if we can
246                debug_assert!(
247                    !env.var_is_declared_to_be(v, Predicate::Shared)
248                        || !env.var_is_declared_to_be(v, Predicate::Owned)
249                );
250
251                Err(NoGlb)
252            }
253
254            // No type is a subtype of both our/mut at same time.
255            (RedLink::Our, RedLink::Mut(..)) | (RedLink::Mut(..), RedLink::Our) => Err(NoGlb),
256
257            // No type is a subtype of both ref/mut at same time.
258            (RedLink::Ref(..), RedLink::Mut(..)) | (RedLink::Mut(..), RedLink::Ref(..)) => {
259                Err(NoGlb)
260            }
261
262            // No type is a subtype of both var/mut at same time.
263            (RedLink::Mut(..), RedLink::Var(_)) | (RedLink::Var(_), RedLink::Mut(..)) => Err(NoGlb),
264        }
265    }
266}
267
268impl<'db> GreatestLowerBound<'db> for SymPlace<'db> {
269    fn glb(env: &Env<'db>, p1: SymPlace<'db>, p2: SymPlace<'db>) -> Glb<SymPlace<'db>> {
270        if p1.is_prefix_of(env.db(), p2) {
271            Ok(p2)
272        } else if p2.is_prefix_of(env.db(), p1) {
273            Ok(p1)
274        } else {
275            Err(NoGlb)
276        }
277    }
278}
279
280fn glb_live(Live(l1): Live, Live(l2): Live) -> Live {
281    Live(l1 || l2)
282}
283
284/// Remove each candidate `c1 \in candidates` where
285/// there exists another candidates `c2 \in candidates`
286/// and `c1 <: c2`
287fn simplify<'db>(env: &Env<'db>, candidates: &mut Vec<RedChain<'db>>) -> Errors<()> {
288    let mut redundant = Set::default();
289    for [c1, c2] in (0..candidates.len()).array_combinations() {
290        let c1 = candidates[c1];
291        let c2 = candidates[c2];
292
293        if chain_sub_chain(env, c1, c2)? {
294            redundant.insert(c1);
295        } else if chain_sub_chain(env, c2, c1)? {
296            redundant.insert(c2);
297        }
298    }
299
300    candidates.retain(|c| !redundant.contains(c));
301    Ok(())
302}