Skip to main content

dada_ir_sym/check/predicates/
var_infer.rs

1use dada_ir_ast::diagnostic::Errors;
2use dada_util::boxed_async_fn;
3
4use crate::{
5    check::{
6        env::Env,
7        inference::Direction,
8        predicates::{Predicate, term_is_provably},
9        report::{Because, OrElse, OrElseHelper},
10    },
11    ir::{
12        indices::InferVarIndex,
13        types::{SymGenericTerm, SymPerm},
14        variables::SymVariable,
15    },
16};
17
18use super::require_term_is;
19
20pub fn test_var_is_provably<'db>(
21    env: &mut Env<'db>,
22    var: SymVariable<'db>,
23    predicate: Predicate,
24) -> bool {
25    env.var_is_declared_to_be(var, predicate)
26}
27
28pub(super) fn require_var_is<'db>(
29    env: &mut Env<'db>,
30    var: SymVariable<'db>,
31    predicate: Predicate,
32    or_else: &dyn OrElse<'db>,
33) -> Errors<()> {
34    if env.var_is_declared_to_be(var, predicate) {
35        Ok(())
36    } else {
37        Err(or_else.report(env, Because::VarNotDeclaredToBe(var, predicate)))
38    }
39}
40
41/// Requires the inference variable (appearing under the given context)
42/// to meet the given predicate (possibly reporting an error
43/// if that is contradictory).
44///
45/// If this is a type inference variable, we are ignoring the affiliated permission.
46pub async fn require_infer_is<'db>(
47    env: &mut Env<'db>,
48    perm: SymPerm<'db>,
49    infer: InferVarIndex,
50    predicate: Predicate,
51    or_else: &dyn OrElse<'db>,
52) -> Errors<()> {
53    // The algorithms here are tied to the code in resolve.
54    //
55    // In particular to the fact that permissions fallback to `SymPerm::My`
56    // if no bounds are found.
57
58    let db = env.db();
59    env.indent(
60        "require_infer_is",
61        &[&infer, &env.infer_var_kind(infer), &predicate],
62        async |env| {
63            // We leverage two key Lemmas.
64            //
65            // If `P1 <: P2` and for predicate C in {unique, owned, shared, lent} then
66            //
67            // 1. P2 is C => P1 is C
68            // 2. not (P1 is C) => not (P2 is C)
69            //
70            // (Note that because of the possibility of `|` types,
71            // Lemma 2 is weaker than it seems like it should be.
72            // Intuitively I expected `P1 is shared => P2 is shared`,
73            // for example, but because `our <: (our | my)`, that does not hold.
74            // However, `not (P1 is unique) => not (P2 is unique)` does,
75            // as does `not (P1 is shared) => not (Pr is shared)` etc.)
76            //
77            // Let's talk through the algorithm using the example of `Predicate::Owned`.
78            // The Unique and Shared predicates are analogous.
79            //
80            // If we see a **lower bound** B1, we cannot not yet that the
81            // final value will be owned. A future upper bound B2 could
82            // be added that is not owned. However, Lemma 2 tells us that
83            // B1 must be owned, since otherwise any future upper bound
84            // could not be owned. So we can enforce that and then
85            // continue waiting for future bounds.
86            //
87            // If see an **upper bound** B2, we can enforce it is owned
88            // and then stop, since by Lemma 1, any future lower bound
89            // B1 compatible with B2 must also be owned.
90            //
91            // If we reach the end of inference and we have only seen
92            // lower bounds, then we can return successfully.
93            // This is because the final value of the permission will be
94            // equal to the final lower bound, and required it to be owned.
95            // Otherwise we check that the fallback permission meets
96            // the predicate.
97
98            let mut bounds = env.term_bounds(perm, infer);
99            let mut observed_lower_bound = false;
100            while let Some((direction, bound)) = bounds.next(env).await {
101                env.log("observed bound", &[&infer, &direction, &bound, &predicate]);
102
103                require_term_is(env, bound, predicate, or_else).await?;
104
105                match direction {
106                    Direction::FromBelow => observed_lower_bound = true,
107                    Direction::FromAbove => return Ok(()),
108                }
109            }
110
111            env.log("observed_lower_bound", &[&observed_lower_bound]);
112
113            if observed_lower_bound {
114                Ok(())
115            } else {
116                require_term_is(
117                    env,
118                    perm.apply_to(db, SymGenericTerm::fallback(db, env.infer_var_kind(infer))),
119                    predicate,
120                    &or_else.map_because(move |_| Because::UnconstrainedInfer(infer)),
121                )
122                .await
123            }
124        },
125    )
126    .await
127}
128
129/// Wait until we know whether the inference variable IS the given predicate
130/// or we know that we'll never be able to prove that it is.
131#[boxed_async_fn]
132pub async fn infer_is_provably<'db>(
133    env: &mut Env<'db>,
134    perm: SymPerm<'db>,
135    infer: InferVarIndex,
136    predicate: Predicate,
137) -> Errors<bool> {
138    match predicate {
139        Predicate::Lent => {
140            // If some lower bound is lent, then this must be lent.
141            exists_bounding_term(
142                env,
143                perm,
144                infer,
145                Some(Direction::FromBelow),
146                Predicate::Lent,
147            )
148            .await
149        }
150
151        Predicate::Owned => {
152            // If some upper bound must be owned, the result must be owned.
153            exists_bounding_term(
154                env,
155                perm,
156                infer,
157                Some(Direction::FromAbove),
158                Predicate::Owned,
159            )
160            .await
161        }
162
163        Predicate::Shared | Predicate::Unique => {
164            // If any bound is {shared, unique}, the result must be {shared, unique}.
165            exists_bounding_term(env, perm, infer, None, predicate).await
166        }
167    }
168}
169
170async fn exists_bounding_term<'db>(
171    env: &mut Env<'db>,
172    perm: SymPerm<'db>,
173    infer: InferVarIndex,
174    direction: Option<Direction>,
175    predicate: Predicate,
176) -> Errors<bool> {
177    env.indent("exists_bounding_term", &[&perm, &infer], async |env| {
178        let db = env.db();
179        let mut bounds = env.term_bounds(perm, infer);
180        while let Some((direction_bound, bound)) = bounds.next(env).await {
181            if let Some(direction) = direction
182                && direction_bound != direction
183            {
184                continue;
185            }
186
187            if term_is_provably(env, perm.apply_to(db, bound), predicate).await? {
188                return Ok(true);
189            }
190        }
191        Ok(false)
192    })
193    .await
194}