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}