Skip to main content

dada_ir_sym/check/subtype/
terms.rs

1//! Implement object-level subtyping.
2
3use dada_ir_ast::{diagnostic::Errors, span::Span};
4use dada_util::boxed_async_fn;
5
6use crate::{
7    check::{
8        env::Env,
9        inference::{Direction, InferVarKind},
10        live_places::LivePlaces,
11        red::RedTy,
12        report::{Because, OrElse},
13        subtype::perms::require_sub_perms,
14        to_red::ToRedTy,
15    },
16    ir::{
17        classes::SymAggregateStyle,
18        indices::{FromInfer, InferVarIndex},
19        types::{SymGenericKind, SymGenericTerm, SymPerm, SymTy, SymTyKind, SymTyName},
20    },
21};
22
23pub async fn require_assignable_type<'db>(
24    env: &mut Env<'db>,
25    live_after: LivePlaces,
26    value_ty: SymTy<'db>,
27    place_ty: SymTy<'db>,
28    or_else: &dyn OrElse<'db>,
29) -> Errors<()> {
30    let db = env.db();
31
32    match (value_ty.kind(db), place_ty.kind(db)) {
33        (SymTyKind::Never, _) => Ok(()),
34        _ => require_sub_terms(env, live_after, value_ty.into(), place_ty.into(), or_else).await,
35    }
36}
37
38pub async fn require_sub_terms<'db>(
39    env: &mut Env<'db>,
40    live_after: LivePlaces,
41    lower: SymGenericTerm<'db>,
42    upper: SymGenericTerm<'db>,
43    or_else: &dyn OrElse<'db>,
44) -> Errors<()> {
45    env.log("require_sub_terms", &[&lower, &upper]);
46    let red_term_lower = lower.to_red_ty(env);
47    let red_term_upper = upper.to_red_ty(env);
48    require_sub_red_terms(env, live_after, red_term_lower, red_term_upper, or_else).await
49}
50
51#[boxed_async_fn]
52pub async fn require_sub_red_terms<'db>(
53    env: &mut Env<'db>,
54    live_after: LivePlaces,
55    (lower_red_ty, lower_perm): (RedTy<'db>, SymPerm<'db>),
56    (upper_red_ty, upper_perm): (RedTy<'db>, SymPerm<'db>),
57    or_else: &dyn OrElse<'db>,
58) -> Errors<()> {
59    env.log(
60        "require_sub_red_terms",
61        &[&lower_perm, &lower_red_ty, &upper_perm, &upper_red_ty],
62    );
63    match (&lower_red_ty, &upper_red_ty) {
64        (&RedTy::Error(reported), _) | (_, &RedTy::Error(reported)) => Err(reported),
65
66        (&RedTy::Infer(lower_infer), &RedTy::Infer(upper_infer)) => {
67            require_infer_sub_infer(
68                env,
69                live_after,
70                lower_perm,
71                lower_infer,
72                upper_perm,
73                upper_infer,
74                or_else,
75            )
76            .await
77        }
78
79        (&RedTy::Infer(lower_infer), _) => {
80            require_infer_sub_ty(
81                env,
82                live_after,
83                lower_perm,
84                lower_infer,
85                upper_perm,
86                upper_red_ty,
87                or_else,
88            )
89            .await
90        }
91
92        (_, &RedTy::Infer(upper_infer)) => {
93            require_ty_sub_infer(
94                env,
95                live_after,
96                lower_perm,
97                lower_red_ty,
98                upper_perm,
99                upper_infer,
100                or_else,
101            )
102            .await
103        }
104
105        (
106            &RedTy::Named(name_lower, ref lower_generics),
107            &RedTy::Named(name_upper, ref upper_generics),
108        ) => {
109            if name_lower == name_upper {
110                let variances = env.variances(name_lower);
111                assert_eq!(lower_generics.len(), upper_generics.len());
112                env.require_for_all(
113                    variances
114                        .iter()
115                        .zip(lower_generics.iter().zip(upper_generics)),
116                    async |env, (&variance, (&lower_generic, &upper_generic))| {
117                        let db = env.db();
118                        let mut lower_generic = lower_generic;
119                        let mut upper_generic = upper_generic;
120
121                        if !variance.relative {
122                            lower_generic = lower_perm.apply_to(db, lower_generic);
123                            upper_generic = upper_perm.apply_to(db, upper_generic);
124                        }
125
126                        env.require_both(
127                            async |env| {
128                                if variance.at_least_covariant {
129                                    require_sub_terms(
130                                        env,
131                                        live_after,
132                                        lower_generic,
133                                        upper_generic,
134                                        or_else,
135                                    )
136                                    .await
137                                } else {
138                                    Ok(())
139                                }
140                            },
141                            async |env| {
142                                if variance.at_least_contravariant {
143                                    require_sub_terms(
144                                        env,
145                                        live_after,
146                                        upper_generic,
147                                        lower_generic,
148                                        or_else,
149                                    )
150                                    .await
151                                } else {
152                                    Ok(())
153                                }
154                            },
155                        )
156                        .await
157                    },
158                )
159                .await?;
160
161                match name_lower.style(env.db()) {
162                    SymAggregateStyle::Struct => {}
163                    SymAggregateStyle::Class => {
164                        require_sub_perms(env, live_after, lower_perm, upper_perm, or_else).await?;
165                    }
166                }
167
168                Ok(())
169            } else {
170                Err(or_else.report(env, Because::NameMismatch(name_lower, name_upper)))
171            }
172        }
173        (&RedTy::Named(..), _) | (_, &RedTy::Named(..)) => {
174            Err(or_else.report(env, Because::JustSo))
175        }
176
177        (&RedTy::Never, &RedTy::Never) => {
178            require_sub_perms(env, live_after, lower_perm, upper_perm, or_else).await
179        }
180        (&RedTy::Never, _) | (_, &RedTy::Never) => Err(or_else.report(env, Because::JustSo)),
181
182        (&RedTy::Var(var_lower), &RedTy::Var(var_upper)) => {
183            if var_lower == var_upper {
184                require_sub_perms(env, live_after, lower_perm, upper_perm, or_else).await
185            } else {
186                Err(or_else.report(env, Because::UniversalMismatch(var_lower, var_upper)))
187            }
188        }
189        (&RedTy::Var(_), _) | (_, &RedTy::Var(_)) => Err(or_else.report(env, Because::JustSo)),
190
191        (&RedTy::Perm, &RedTy::Perm) => {
192            require_sub_perms(env, live_after, lower_perm, upper_perm, or_else).await
193        }
194    }
195}
196
197/// Require that `lower <: upper`, where both are type inference variables.
198/// This will insert record `upper` as an upper bound of `lower`.
199/// If `upper` is a new upper bound, it will begin looping,
200/// taking each lower bound of `lower` and propagating it to `upper`
201/// (in this case it will not return).
202async fn require_infer_sub_infer<'db>(
203    env: &mut Env<'db>,
204    live_after: LivePlaces,
205    lower_perm: SymPerm<'db>,
206    lower_infer: InferVarIndex,
207    upper_perm: SymPerm<'db>,
208    upper_infer: InferVarIndex,
209    or_else: &dyn OrElse<'db>,
210) -> Errors<()> {
211    debug_assert_eq!(env.infer_var_kind(lower_infer), InferVarKind::Type);
212    debug_assert_eq!(env.infer_var_kind(upper_infer), InferVarKind::Type);
213
214    if lower_infer == upper_infer {
215        return Ok(());
216    }
217
218    // FIXME: needs to take live-places into account
219    if env.insert_sub_infer_var_pair(lower_infer, upper_infer) {
220        env.require_both(
221            async |env| {
222                env.for_each_bound(
223                    Direction::FromBelow,
224                    lower_infer,
225                    async |env, lower_bound, _or_else| {
226                        require_sub_red_terms(
227                            env,
228                            live_after,
229                            (lower_bound.clone(), lower_perm),
230                            (RedTy::Infer(upper_infer), upper_perm),
231                            or_else,
232                        )
233                        .await
234                    },
235                )
236                .await
237            },
238            async |env| {
239                env.for_each_bound(
240                    Direction::FromAbove,
241                    upper_infer,
242                    async |env, upper_bound, _or_else| {
243                        require_sub_red_terms(
244                            env,
245                            live_after,
246                            (RedTy::Infer(lower_infer), lower_perm),
247                            (upper_bound.clone(), upper_perm),
248                            or_else,
249                        )
250                        .await
251                    },
252                )
253                .await
254            },
255        )
256        .await
257    } else {
258        Ok(())
259    }
260}
261
262/// Relate `lower_term` (not)
263async fn require_ty_sub_infer<'db>(
264    env: &mut Env<'db>,
265    live_after: LivePlaces,
266    lower_perm: SymPerm<'db>,
267    lower_ty: RedTy<'db>,
268    upper_perm: SymPerm<'db>,
269    upper_infer: InferVarIndex,
270    or_else: &dyn OrElse<'db>,
271) -> Errors<()> {
272    debug_assert!(
273        !matches!(lower_ty, RedTy::Infer(_)),
274        "unexpected inference variable"
275    );
276
277    // Get the lower bounding red-ty from `upper_infer`;
278    // if it doesn't have one yet, generalize `lower_ty` to create one.
279    let generalized_ty =
280        require_infer_has_bound(env, Direction::FromBelow, &lower_ty, upper_infer, or_else).await?;
281
282    // Relate the lower term to the upper term
283    require_sub_red_terms(
284        env,
285        live_after,
286        (lower_ty, lower_perm),
287        (generalized_ty, upper_perm),
288        or_else,
289    )
290    .await
291}
292
293/// Return the red-ty lower bound from `infer`, creating one if needed by generalizing `bound`.
294/// Does not relate the return value and `bound` in any other way.
295async fn require_infer_sub_ty<'db>(
296    env: &mut Env<'db>,
297    live_after: LivePlaces,
298    lower_perm: SymPerm<'db>,
299    lower_infer: InferVarIndex,
300    upper_perm: SymPerm<'db>,
301    upper_ty: RedTy<'db>,
302    or_else: &dyn OrElse<'db>,
303) -> Errors<()> {
304    debug_assert!(
305        !matches!(upper_ty, RedTy::Infer(_)),
306        "unexpected inference variable"
307    );
308
309    // Get the upper bounding red-ty from `upper_infer`;
310    // if it doesn't have one yet, generalize `upper_term.ty` to create one.
311    let generalized_ty =
312        require_infer_has_bound(env, Direction::FromAbove, &upper_ty, lower_infer, or_else).await?;
313
314    require_sub_red_terms(
315        env,
316        live_after,
317        (generalized_ty, lower_perm),
318        (upper_ty, upper_perm),
319        or_else,
320    )
321    .await
322}
323
324/// Return the upper or lower (depending on direction) red-ty-bound from `infer`.
325/// If `infer` does not yet have a suitable bound, create one by generalizing `bound`.
326async fn require_infer_has_bound<'db>(
327    env: &mut Env<'db>,
328    direction: Direction,
329    bound: &RedTy<'db>,
330    infer: InferVarIndex,
331    or_else: &dyn OrElse<'db>,
332) -> Errors<RedTy<'db>> {
333    match env.red_bound(infer, direction).peek_ty() {
334        None => {
335            // Inference variable does not currently have a red-ty bound.
336            // Create a generalized version of `bound` and use that.
337            let span = env.infer_var_span(infer);
338            let generalized = generalize(env, bound, span)?;
339            env.red_bound(infer, direction)
340                .set_ty(generalized.clone(), or_else);
341            Ok(generalized)
342        }
343
344        Some((generalized, _generalized_or_else)) => {
345            // There is already a red-ty bound on the inference variable.
346            //
347            // FIXME: We may need to adjust this bound once we introduce enum.
348            Ok(generalized)
349        }
350    }
351}
352
353/// *Generalize* returns a new red-ty created by replacing any generic arguments in `red_ty`
354/// with fresh inference variables.
355fn generalize<'db>(env: &mut Env<'db>, red_ty: &RedTy<'db>, span: Span<'db>) -> Errors<RedTy<'db>> {
356    let db = env.db();
357    let red_ty_generalized = match red_ty {
358        RedTy::Error(reported) => return Err(*reported),
359        RedTy::Never => RedTy::Never,
360        RedTy::Infer(_) => unreachable!("infer should not get here"),
361        RedTy::Var(sym_variable) => RedTy::Var(*sym_variable),
362        RedTy::Perm => RedTy::Perm,
363        RedTy::Named(sym_ty_name, generics) => {
364            let generics_generalized = generics
365                .iter()
366                .map(|generic| match *generic {
367                    SymGenericTerm::Type(_) => {
368                        let v = env.fresh_inference_var(SymGenericKind::Type, span);
369                        SymTy::infer(db, v).into()
370                    }
371                    SymGenericTerm::Perm(_) => {
372                        let v = env.fresh_inference_var(SymGenericKind::Perm, span);
373                        SymPerm::infer(db, v).into()
374                    }
375                    SymGenericTerm::Place(p) => SymGenericTerm::Place(p),
376                    SymGenericTerm::Error(reported) => SymGenericTerm::Error(reported),
377                })
378                .collect();
379            RedTy::Named(*sym_ty_name, generics_generalized)
380        }
381    };
382    Ok(red_ty_generalized)
383}
384
385/// A task that runs for each type inference variable. It awaits any upper/lower bounds
386/// and propagates a corresponding bound.
387pub async fn reconcile_ty_bounds<'db>(env: &mut Env<'db>, infer: InferVarIndex) -> Errors<()> {
388    assert_eq!(env.infer_var_kind(infer), InferVarKind::Type);
389
390    env.require_all()
391        .require(async |env| propagate_inverse_bound(env, infer, Direction::FromAbove).await)
392        .require(async |env| propagate_inverse_bound(env, infer, Direction::FromBelow).await)
393        .finish()
394        .await
395}
396
397/// What each upper or lower  (depending on `direction`) bound `B` that is added to `infer`
398/// and add a lower or upper (respectively) bound `B1` that is implied by `B`.
399///
400/// # Example
401///
402/// Given `Direction::FromAbove`, if we learn that a new upper bound `i32`,
403/// i.e., that `infer <: i32`, then this also implies `i32 <: infer`, so we add the
404/// lower bound `i32`.
405///
406/// Given `Direction::FromAbove`, if we learn that a new upper bound `Vec[T1]`,
407/// i.e., that `infer <: Vec[T1]`, then this also implies `Vec[_] <: infer`, so we add the
408/// lower bound `Vec[?X]` for a fresh `?X`. This will in turn require that `?X <: T1`.
409///
410/// NB. In some cases (e.g., `Vec[i32]` for sure...) we could avoid creating the inference
411/// variable but right now we just *always* create one since I didn't want to think about it.
412async fn propagate_inverse_bound<'db>(
413    env: &mut Env<'db>,
414    infer: InferVarIndex,
415    direction: Direction,
416) -> Errors<()> {
417    let db = env.db();
418
419    let span = env.infer_var_span(infer);
420
421    // Every type inference variable has both an associated
422    // permission variable `perm_infer` and upper/lower red-ty
423    // bounds (see `InferenceVarBounds` in the `inference` module
424    // for more details).
425    //
426    // The bound `B` and opposite bound `B1` that we are creating
427    // correspond to the red-ty bound part of `?X`.
428    // To relate these bounds fully to `?X` will will need to combine
429    // them with the permissions from `perm_infer`.
430    // This is because you can't directly subtype e.g. a struct
431    // without knowing the permission.
432    let perm_infer = SymPerm::infer(db, env.perm_infer(infer));
433
434    // For each new bound `B` where `?X <: B`...
435    //
436    // NB: Comments are written assuming `Direction::FromAbove`.
437    env.for_each_bound(direction, infer, async |env, red_ty, or_else| {
438        // ...see if that implies an opposite bound `B1 <: ?X`...
439        let opposite_bound = match red_ty {
440            RedTy::Error(_) => None,
441
442            RedTy::Named(sym_ty_name, _) => match sym_ty_name {
443                SymTyName::Primitive(_) | SymTyName::Future | SymTyName::Tuple { .. } => {
444                    Some(generalize(env, red_ty, span)?)
445                }
446                SymTyName::Aggregate(_sym_aggregate) => {
447                    // FIXME(#241): check if `sym_aggregate` is an enum
448                    // in which case we need to adjust based on `direction`
449                    Some(generalize(env, red_ty, span)?)
450                }
451            },
452
453            RedTy::Never | RedTy::Var(..) => Some(red_ty.clone()),
454
455            RedTy::Infer(..) | RedTy::Perm => {
456                unreachable!("unexpected kind for red-ty bound: {red_ty:?}")
457            }
458        };
459
460        // If so, add the new opposite bound `B1`.
461        if let Some(opposite_bound) = opposite_bound {
462            match direction {
463                Direction::FromAbove => {
464                    // ... `?X <: B` so `B1 <: ?X`
465                    require_ty_sub_infer(
466                        env,
467                        LivePlaces::fixme(),
468                        // Combine `B1` with the permission variable from `?X`
469                        perm_infer,
470                        opposite_bound,
471                        // Pass `?X` along with its permission variable as the upper term
472                        perm_infer,
473                        infer,
474                        &or_else,
475                    )
476                    .await?;
477                }
478                Direction::FromBelow => {
479                    // ... `B <: ?X` so `?X <: B1`
480                    require_infer_sub_ty(
481                        env,
482                        LivePlaces::fixme(),
483                        // Pass `?X` along with its permission variable as the lower term
484                        perm_infer,
485                        infer,
486                        // Combine `B1` with the permission variable from `?X`
487                        perm_infer,
488                        opposite_bound,
489                        &or_else,
490                    )
491                    .await?
492                }
493            }
494        }
495
496        Ok(())
497    })
498    .await
499}