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}