Skip to main content

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}