dada_ir_sym/check/subtype/perms.rs
1use dada_ir_ast::diagnostic::Errors;
2
3use crate::{
4 check::{
5 env::Env,
6 inference::Direction,
7 live_places::LivePlaces,
8 red::{
9 RedPerm,
10 lattice::{glb_perms, lub_perms},
11 sub::chain_sub_chain,
12 },
13 report::{Because, OrElse},
14 stream::Consumer,
15 to_red::ToRedPerm,
16 },
17 ir::{
18 indices::InferVarIndex,
19 types::{SymPerm, SymPermKind},
20 },
21};
22
23// Rules (ignoring inference and layout rules)
24//
25// * `my <= C`
26// * `our <= C1 if C1 is copy`
27// * `(our C0) <= (our C1) if C0 <= C1`
28// * `(mutable[place0] C0) <= (mutable[place1] C1) if place1 <= place0 && C0 <= C1`
29// * `(shared[place0] C0) <= (shared[place1] C1) if place1 <= place0 && C0 <= C1`
30// * `(shared[place0] C0) <= (our C1) if (mutable[place0] C0) <= C1`
31// * `X C0 <= X C1 if C0 <= C1`
32// * `X <= our if X is copy+owned`
33// * `X <= my if X is move+owned`
34
35pub async fn require_sub_perms<'db>(
36 env: &mut Env<'db>,
37 live_after: LivePlaces,
38 lower_perm: SymPerm<'db>,
39 upper_perm: SymPerm<'db>,
40 or_else: &dyn OrElse<'db>,
41) -> Errors<()> {
42 let db = env.db();
43
44 // This is all a bit subtle. Here is the problem.
45 // We have permissions coming in that we can convert to red-permissions.
46 // Once we've done that, comparing two red-permissions for subtyping is relatively easy
47 // (see `require_red_perm_sub_red_perm`).
48 //
49 // The challenge comes in when there are inference variables involved.
50 // There can be a lot of ambiguity in this case. Consider something like
51 // `P <: ?A ?B`. What do we attribute to `?A` vs `?B`?
52 //
53 // Right now we are doing the simplest thing that could possibly work.
54 // There are definitely more sophisticated things we could do, it's not clear
55 // yet whether it will matter.
56 //
57 // The strategy at present is to do three things at once:
58 //
59 // No matter what, we convert the lower/upper perms into red-perms and relate those.
60 // If there are inference variables involved, this process will continue until
61 // inference has completed, propagating any new bounds that appear on those variables.
62 // We make no effort at present to avoid doing this multiple times in distinct tasks
63 // for the same pairs of inference variables. That's for a future day.
64 //
65 // Concurrently, we check for cases where an inference variable appears on its
66 // own in the lower/upper bound, e.g., `P <: ?A` or `?A <: Q`. These cases do not
67 // have any ambiguities to worry about. We can just expand `P` (resp. `Q`)
68 // to a set of red-permissions and add them to the lower (resp. upper) bounds of `?A`.
69 // Note that in the case of `?A <: ?B` both of those cases apply simultaneously, and
70 // so this serves to forward lower bounds from `?A` to `?B` and upper bounds from `?B` to `?A`.
71 //
72 // The main case that this does NOT handle is something like `our ?A <: our mut[x]`.
73 // We could do in fact deduce that `?A` has an upper bound of `mut[x]` in this case,
74 // but we are not smart enough. Instead, we'll just wait for any lower bounds on `?A` to show
75 // up and compare them against `mut[x]`.
76
77 env.log("require_sub_perms", &[&lower_perm, &upper_perm]);
78
79 env.require_all()
80 .require(async |env| {
81 let SymPermKind::Infer(lower_infer) = lower_perm.kind(db) else {
82 return Ok(());
83 };
84 require_infer_bounded_by_perm(
85 env,
86 live_after,
87 *lower_infer,
88 Direction::FromAbove,
89 upper_perm,
90 or_else,
91 )
92 .await
93 })
94 .require(async |env| {
95 let SymPermKind::Infer(upper_infer) = upper_perm.kind(db) else {
96 return Ok(());
97 };
98 require_infer_bounded_by_perm(
99 env,
100 live_after,
101 *upper_infer,
102 Direction::FromBelow,
103 lower_perm,
104 or_else,
105 )
106 .await
107 })
108 .require(async |env| {
109 require_perm_sub_perm(env, live_after, lower_perm, upper_perm, or_else).await
110 })
111 .finish()
112 .await
113}
114
115async fn require_infer_bounded_by_perm<'db>(
116 env: &mut Env<'db>,
117 live_after: LivePlaces,
118 infer: InferVarIndex,
119 direction: Direction,
120 new_sym_bound: SymPerm<'db>,
121 or_else: &dyn OrElse<'db>,
122) -> Errors<()> {
123 env.log(
124 "require_infer_bounded_by_perm",
125 &[&infer, &direction, &new_sym_bound],
126 );
127 new_sym_bound
128 .to_red_perm(
129 env,
130 live_after,
131 direction,
132 Consumer::new(async |env, new_red_bound: RedPerm<'db>| {
133 match env.red_bound(infer, direction).peek_perm() {
134 Some((old_red_bound, old_or_else)) => {
135 let perm_combined = match direction {
136 Direction::FromAbove => {
137 Some(lub_perms(env, old_red_bound, new_red_bound))
138 }
139 Direction::FromBelow => glb_perms(env, old_red_bound, new_red_bound),
140 };
141 match perm_combined {
142 Some(red_perm_glb) => {
143 env.red_bound(infer, direction)
144 .set_perm(red_perm_glb, or_else);
145 }
146 None => {
147 or_else.report(
148 env,
149 Because::InferredPermBound(
150 direction,
151 old_red_bound,
152 old_or_else,
153 ),
154 );
155 }
156 }
157 }
158
159 None => {
160 env.red_bound(infer, direction)
161 .set_perm(new_red_bound, or_else);
162 }
163 }
164
165 Ok(())
166 }),
167 )
168 .await
169}
170
171pub(crate) async fn require_perm_sub_perm<'db>(
172 env: &mut Env<'db>,
173 live_after: LivePlaces,
174 lower_perm: SymPerm<'db>,
175 upper_perm: SymPerm<'db>,
176 or_else: &dyn OrElse<'db>,
177) -> Errors<()> {
178 lower_perm
179 .to_red_perm(
180 env,
181 live_after,
182 Direction::FromBelow,
183 Consumer::new(async |env, lower_red_perm: RedPerm<'db>| {
184 upper_perm
185 .to_red_perm(
186 env,
187 live_after,
188 Direction::FromAbove,
189 Consumer::new(async |env, upper_red_perm: RedPerm<'db>| {
190 require_red_perm_sub_red_perm(
191 env,
192 lower_red_perm,
193 upper_red_perm,
194 or_else,
195 )
196 }),
197 )
198 .await
199 }),
200 )
201 .await
202}
203
204fn require_red_perm_sub_red_perm<'db>(
205 env: &mut Env<'db>,
206 lower_perm: RedPerm<'db>,
207 upper_perm: RedPerm<'db>,
208 or_else: &dyn OrElse<'db>,
209) -> Errors<()> {
210 let db = env.db();
211
212 // Require that forall lower there exists an upper where lower <= upper
213 'lower: for &lower_chain in lower_perm.chains(db) {
214 for &upper_chain in upper_perm.chains(db) {
215 if chain_sub_chain(env, lower_chain, upper_chain)? {
216 continue 'lower;
217 }
218 }
219
220 // No suitable upper chain for `lower_chain`
221 return Err(or_else.report(env, Because::JustSo));
222 }
223
224 Ok(())
225}