Skip to main content

dada_ir_sym/check/
member_lookup.rs

1use crate::ir::{
2    binder::Binder,
3    classes::{SymAggregate, SymClassMember, SymField},
4    functions::SymFunction,
5    types::{SymGenericTerm, SymPerm, SymTy, SymTyName},
6};
7use dada_ir_ast::{
8    ast::{Identifier, SpannedIdentifier},
9    diagnostic::{Diagnostic, Err, Errors, Level, Reported},
10    span::Span,
11};
12use dada_util::{debug, debug_heading};
13
14use crate::{
15    check::env::Env,
16    check::exprs::{ExprResult, ExprResultKind},
17    ir::exprs::{SymPlaceExpr, SymPlaceExprKind},
18    prelude::CheckedFieldTy,
19};
20
21use super::{inference::Direction, red::RedTy, to_red::ToRedTy};
22
23pub(crate) struct MemberLookup<'member, 'db> {
24    env: &'member mut Env<'db>,
25}
26
27impl<'member, 'db> MemberLookup<'member, 'db> {
28    pub fn new(env: &'member mut Env<'db>) -> Self {
29        Self { env }
30    }
31
32    pub async fn lookup_member(
33        &mut self,
34        owner: ExprResult<'db>,
35        id: SpannedIdentifier<'db>,
36    ) -> ExprResult<'db> {
37        let owner_ty = owner.ty(self.env);
38
39        // Block until we find a lower bound on the owner's type.
40        let (lower_bound, owner_perm) = non_infer_lower_bound(self.env, owner_ty).await;
41
42        // The owner will be some supertype of `ty`.
43        match self.search_lower_bound_for_member(lower_bound, id.id) {
44            Ok(Some(member)) => self.confirm_member(owner, owner_perm, member, id),
45            Ok(None) => {
46                // If there is no member, then since the owner must be a supertype of `ty`,
47                // this expression is invalid.
48                self.no_such_member_result(id, owner.span, owner_ty)
49            }
50            Err(reported) => ExprResult::err(self.env.db(), reported),
51        }
52    }
53
54    fn confirm_member(
55        &mut self,
56        owner: ExprResult<'db>,
57        owner_perm: SymPerm<'db>,
58        member: SearchResult<'db>,
59        id: SpannedIdentifier<'db>,
60    ) -> ExprResult<'db> {
61        let db = self.env.db();
62
63        // Construct the result
64        match member {
65            SearchResult::Field {
66                owner: _,
67                field,
68                field_ty,
69            } => {
70                let mut temporaries = vec![];
71
72                // The type of the field will be the declared type `F` with...
73                // * `self` replaced with the place `owner`
74                // * the permission from the owner applied
75                let owner_place_expr = owner.into_place_expr(self.env, &mut temporaries);
76                let field_ty = field_ty.substitute(db, &[owner_place_expr.into_sym_place(db)]);
77                let field_ty_with_perm = owner_perm.apply_to(db, field_ty);
78
79                // construct the place expression
80                let place_expr = SymPlaceExpr::new(
81                    db,
82                    id.span,
83                    field_ty_with_perm,
84                    SymPlaceExprKind::Field(owner_place_expr, field),
85                );
86                ExprResult::from_place_expr(db, place_expr, temporaries)
87            }
88            SearchResult::Method { owner: _, method } => {
89                let mut temporaries = vec![];
90                let owner = owner.into_expr(self.env, &mut temporaries);
91                ExprResult {
92                    temporaries,
93                    span: owner.span(db).to(db, id.span),
94                    kind: ExprResultKind::Method {
95                        self_expr: owner,
96                        function: method,
97                        generics: None,
98                        id_span: id.span,
99                    },
100                }
101            }
102        }
103    }
104
105    fn no_such_member_result(
106        &mut self,
107        id: SpannedIdentifier<'db>,
108        owner_span: Span<'db>,
109        owner_ty: SymTy<'db>,
110    ) -> ExprResult<'db> {
111        ExprResult::err(self.env.db(), self.no_such_member(id, owner_span, owner_ty))
112    }
113
114    fn no_such_member(
115        &mut self,
116        id: SpannedIdentifier<'db>,
117        owner_span: Span<'db>,
118        owner_ty: SymTy<'db>,
119    ) -> Reported {
120        let db = self.env.db();
121        let SpannedIdentifier { span: id_span, id } = id;
122        Diagnostic::error(db, id_span, format!("unrecognized field or method `{id}`"))
123            .label(
124                db,
125                Level::Error,
126                id_span,
127                format!("I could not find a field or method named `{id}`"),
128            )
129            .label(
130                db,
131                Level::Info,
132                owner_span,
133                format!(
134                    "this has type `{ty}`, which doesn't appear to have a field or method `{id}`",
135                    ty = self.env.describe_ty(owner_ty)
136                ),
137            )
138            .report(db)
139    }
140
141    fn search_lower_bound_for_member(
142        &mut self,
143        lower_bound: RedTy<'db>,
144        id: Identifier<'db>,
145    ) -> Errors<Option<SearchResult<'db>>> {
146        debug_heading!("search_lower_bound_for_member", lower_bound, id);
147        match lower_bound {
148            RedTy::Named(name, ref generics) => match name {
149                // Primitive types don't have members.
150                SymTyName::Primitive(_) => Ok(None),
151
152                // Tuples have indexed members, not named ones.
153                SymTyName::Tuple { arity: _ } => Ok(None),
154
155                // Classes have members.
156                SymTyName::Aggregate(owner) => self.search_aggr_for_member(owner, generics, id),
157
158                // Future types have no members.
159                SymTyName::Future => Ok(None),
160            },
161            RedTy::Error(reported) => Err(reported),
162            RedTy::Never => Ok(None),
163            RedTy::Infer(_) => panic!("did not expect inference variable"),
164            RedTy::Var(_) => Ok(None),
165            RedTy::Perm => panic!("did not expect permission red-ty"),
166        }
167    }
168
169    fn search_aggr_for_member(
170        &mut self,
171        owner: SymAggregate<'db>,
172        generics: &[SymGenericTerm<'db>],
173        id: Identifier<'db>,
174    ) -> Errors<Option<SearchResult<'db>>> {
175        let db = self.env.db();
176        debug_heading!("search_class_for_member", id, owner);
177
178        for &member in owner.members(db) {
179            match member {
180                SymClassMember::SymField(field) => {
181                    if field.name(db) == id {
182                        debug!("found field", field);
183                        return Ok(Some(SearchResult::Field {
184                            owner,
185                            field,
186                            field_ty: field.checked_field_ty(db).substitute(db, generics),
187                        }));
188                    } else {
189                        debug!("found field with wrong name", field.name(db));
190                    }
191                }
192
193                SymClassMember::SymFunction(method) => {
194                    if method.name(db) == id {
195                        debug!("found method", method);
196                        return Ok(Some(SearchResult::Method { owner, method }));
197                    } else {
198                        debug!("found method with wrong name", method.name(db));
199                    }
200                }
201            }
202        }
203
204        Ok(None)
205    }
206}
207
208#[derive(Clone, PartialEq, Eq)]
209enum SearchResult<'db> {
210    Field {
211        owner: SymAggregate<'db>,
212        field: SymField<'db>,
213        field_ty: Binder<'db, SymTy<'db>>,
214    },
215    Method {
216        owner: SymAggregate<'db>,
217        method: SymFunction<'db>,
218    },
219}
220
221/// Convert `ty` to a [`RedTy`][]; if the result is an inference variable,
222/// then wait until that variable has a lower-bound.
223///
224/// # Returns
225///
226/// A [`RedTy`][] that is a lower bound for `ty` and which is not an inference variable.
227async fn non_infer_lower_bound<'db>(
228    env: &mut Env<'db>,
229    ty: SymTy<'db>,
230) -> (RedTy<'db>, SymPerm<'db>) {
231    let (red_ty, perm) = ty.to_red_ty(env);
232    if let RedTy::Infer(infer_var_index) = red_ty {
233        match env
234            .red_bound(infer_var_index, Direction::FromBelow)
235            .ty()
236            .await
237        {
238            Some((bound_red_ty, _)) => (bound_red_ty, perm),
239            None => {
240                // Subtle: Not possible to get here. The reason is that the above for-loop will
241                // never terminate until we can construct a complete expression for the body,
242                // and we can't do that until we resolve all member references.
243                unreachable!()
244            }
245        }
246    } else {
247        (red_ty, perm)
248    }
249}