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 let (lower_bound, owner_perm) = non_infer_lower_bound(self.env, owner_ty).await;
41
42 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 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 match member {
65 SearchResult::Field {
66 owner: _,
67 field,
68 field_ty,
69 } => {
70 let mut temporaries = vec![];
71
72 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 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 SymTyName::Primitive(_) => Ok(None),
151
152 SymTyName::Tuple { arity: _ } => Ok(None),
154
155 SymTyName::Aggregate(owner) => self.search_aggr_for_member(owner, generics, id),
157
158 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
221async 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 unreachable!()
244 }
245 }
246 } else {
247 (red_ty, perm)
248 }
249}