]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! Type inference for patterns. |
2 | ||
3 | use std::iter::repeat_with; | |
4 | ||
5 | use chalk_ir::Mutability; | |
6 | use hir_def::{ | |
353b0b11 | 7 | body::Body, |
fe692bf9 | 8 | hir::{Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId}, |
064997fb | 9 | path::Path, |
064997fb FG |
10 | }; |
11 | use hir_expand::name::Name; | |
12 | ||
13 | use crate::{ | |
353b0b11 | 14 | consteval::{try_const_usize, usize_const}, |
064997fb FG |
15 | infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, |
16 | lower::lower_to_chalk_mutability, | |
f2b60f7d | 17 | primitive::UintTy, |
781aab86 FG |
18 | static_lifetime, InferenceDiagnostic, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, |
19 | TyKind, | |
064997fb FG |
20 | }; |
21 | ||
353b0b11 FG |
22 | /// Used to generalize patterns and assignee expressions. |
23 | pub(super) trait PatLike: Into<ExprOrPatId> + Copy { | |
24 | type BindingMode: Copy; | |
25 | ||
26 | fn infer( | |
27 | this: &mut InferenceContext<'_>, | |
28 | id: Self, | |
29 | expected_ty: &Ty, | |
30 | default_bm: Self::BindingMode, | |
31 | ) -> Ty; | |
32 | } | |
33 | ||
34 | impl PatLike for ExprId { | |
35 | type BindingMode = (); | |
36 | ||
37 | fn infer( | |
38 | this: &mut InferenceContext<'_>, | |
39 | id: Self, | |
40 | expected_ty: &Ty, | |
41 | (): Self::BindingMode, | |
42 | ) -> Ty { | |
43 | this.infer_assignee_expr(id, expected_ty) | |
44 | } | |
45 | } | |
46 | ||
47 | impl PatLike for PatId { | |
48 | type BindingMode = BindingMode; | |
49 | ||
50 | fn infer( | |
51 | this: &mut InferenceContext<'_>, | |
52 | id: Self, | |
53 | expected_ty: &Ty, | |
54 | default_bm: Self::BindingMode, | |
55 | ) -> Ty { | |
56 | this.infer_pat(id, expected_ty, default_bm) | |
57 | } | |
58 | } | |
064997fb | 59 | |
add651ee | 60 | impl InferenceContext<'_> { |
064997fb FG |
61 | /// Infers type for tuple struct pattern or its corresponding assignee expression. |
62 | /// | |
63 | /// Ellipses found in the original pattern or expression must be filtered out. | |
64 | pub(super) fn infer_tuple_struct_pat_like<T: PatLike>( | |
65 | &mut self, | |
66 | path: Option<&Path>, | |
67 | expected: &Ty, | |
68 | default_bm: T::BindingMode, | |
69 | id: T, | |
70 | ellipsis: Option<usize>, | |
71 | subs: &[T], | |
72 | ) -> Ty { | |
73 | let (ty, def) = self.resolve_variant(path, true); | |
74 | let var_data = def.map(|it| it.variant_data(self.db.upcast())); | |
75 | if let Some(variant) = def { | |
76 | self.write_variant_resolution(id.into(), variant); | |
77 | } | |
781aab86 FG |
78 | if let Some(var) = &var_data { |
79 | let cmp = if ellipsis.is_some() { usize::gt } else { usize::ne }; | |
80 | ||
81 | if cmp(&subs.len(), &var.fields().len()) { | |
82 | self.push_diagnostic(InferenceDiagnostic::MismatchedTupleStructPatArgCount { | |
83 | pat: id.into(), | |
84 | expected: var.fields().len(), | |
85 | found: subs.len(), | |
86 | }); | |
87 | } | |
88 | } | |
89 | ||
064997fb FG |
90 | self.unify(&ty, expected); |
91 | ||
92 | let substs = | |
93 | ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); | |
94 | ||
781aab86 FG |
95 | match def { |
96 | _ if subs.len() == 0 => {} | |
97 | Some(def) => { | |
98 | let field_types = self.db.field_types(def); | |
99 | let variant_data = def.variant_data(self.db.upcast()); | |
100 | let visibilities = self.db.field_visibilities(def); | |
101 | ||
102 | let (pre, post) = match ellipsis { | |
103 | Some(idx) => subs.split_at(idx), | |
104 | None => (subs, &[][..]), | |
105 | }; | |
106 | let post_idx_offset = field_types.iter().count().saturating_sub(post.len()); | |
107 | ||
108 | let pre_iter = pre.iter().enumerate(); | |
109 | let post_iter = (post_idx_offset..).zip(post.iter()); | |
110 | ||
111 | for (i, &subpat) in pre_iter.chain(post_iter) { | |
112 | let field_def = { | |
113 | match variant_data.field(&Name::new_tuple_field(i)) { | |
114 | Some(local_id) => { | |
115 | if !visibilities[local_id] | |
116 | .is_visible_from(self.db.upcast(), self.resolver.module()) | |
117 | { | |
118 | // FIXME(DIAGNOSE): private tuple field | |
119 | } | |
120 | Some(local_id) | |
121 | } | |
122 | None => None, | |
123 | } | |
124 | }; | |
125 | ||
126 | let expected_ty = field_def.map_or(self.err_ty(), |f| { | |
127 | field_types[f].clone().substitute(Interner, &substs) | |
128 | }); | |
129 | let expected_ty = self.normalize_associated_types_in(expected_ty); | |
130 | ||
131 | T::infer(self, subpat, &expected_ty, default_bm); | |
132 | } | |
133 | } | |
134 | None => { | |
135 | let err_ty = self.err_ty(); | |
136 | for &inner in subs { | |
137 | T::infer(self, inner, &err_ty, default_bm); | |
138 | } | |
139 | } | |
064997fb FG |
140 | } |
141 | ||
142 | ty | |
143 | } | |
144 | ||
145 | /// Infers type for record pattern or its corresponding assignee expression. | |
146 | pub(super) fn infer_record_pat_like<T: PatLike>( | |
147 | &mut self, | |
148 | path: Option<&Path>, | |
149 | expected: &Ty, | |
150 | default_bm: T::BindingMode, | |
151 | id: T, | |
781aab86 | 152 | subs: impl Iterator<Item = (Name, T)> + ExactSizeIterator, |
064997fb FG |
153 | ) -> Ty { |
154 | let (ty, def) = self.resolve_variant(path, false); | |
155 | if let Some(variant) = def { | |
156 | self.write_variant_resolution(id.into(), variant); | |
157 | } | |
158 | ||
159 | self.unify(&ty, expected); | |
160 | ||
161 | let substs = | |
162 | ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); | |
163 | ||
781aab86 FG |
164 | match def { |
165 | _ if subs.len() == 0 => {} | |
166 | Some(def) => { | |
167 | let field_types = self.db.field_types(def); | |
168 | let variant_data = def.variant_data(self.db.upcast()); | |
169 | let visibilities = self.db.field_visibilities(def); | |
170 | ||
171 | for (name, inner) in subs { | |
172 | let field_def = { | |
173 | match variant_data.field(&name) { | |
174 | Some(local_id) => { | |
175 | if !visibilities[local_id] | |
176 | .is_visible_from(self.db.upcast(), self.resolver.module()) | |
177 | { | |
178 | self.push_diagnostic(InferenceDiagnostic::NoSuchField { | |
179 | field: inner.into(), | |
180 | private: true, | |
181 | }); | |
182 | } | |
183 | Some(local_id) | |
184 | } | |
185 | None => { | |
186 | self.push_diagnostic(InferenceDiagnostic::NoSuchField { | |
187 | field: inner.into(), | |
188 | private: false, | |
189 | }); | |
190 | None | |
191 | } | |
192 | } | |
193 | }; | |
194 | ||
195 | let expected_ty = field_def.map_or(self.err_ty(), |f| { | |
196 | field_types[f].clone().substitute(Interner, &substs) | |
197 | }); | |
198 | let expected_ty = self.normalize_associated_types_in(expected_ty); | |
199 | ||
200 | T::infer(self, inner, &expected_ty, default_bm); | |
201 | } | |
202 | } | |
203 | None => { | |
204 | let err_ty = self.err_ty(); | |
205 | for (_, inner) in subs { | |
206 | T::infer(self, inner, &err_ty, default_bm); | |
207 | } | |
208 | } | |
064997fb FG |
209 | } |
210 | ||
211 | ty | |
212 | } | |
213 | ||
214 | /// Infers type for tuple pattern or its corresponding assignee expression. | |
215 | /// | |
216 | /// Ellipses found in the original pattern or expression must be filtered out. | |
217 | pub(super) fn infer_tuple_pat_like<T: PatLike>( | |
218 | &mut self, | |
219 | expected: &Ty, | |
220 | default_bm: T::BindingMode, | |
221 | ellipsis: Option<usize>, | |
222 | subs: &[T], | |
223 | ) -> Ty { | |
353b0b11 | 224 | let expected = self.resolve_ty_shallow(expected); |
064997fb FG |
225 | let expectations = match expected.as_tuple() { |
226 | Some(parameters) => &*parameters.as_slice(Interner), | |
227 | _ => &[], | |
228 | }; | |
229 | ||
230 | let ((pre, post), n_uncovered_patterns) = match ellipsis { | |
231 | Some(idx) => (subs.split_at(idx), expectations.len().saturating_sub(subs.len())), | |
232 | None => ((&subs[..], &[][..]), 0), | |
233 | }; | |
234 | let mut expectations_iter = expectations | |
235 | .iter() | |
236 | .cloned() | |
237 | .map(|a| a.assert_ty_ref(Interner).clone()) | |
238 | .chain(repeat_with(|| self.table.new_type_var())); | |
239 | ||
240 | let mut inner_tys = Vec::with_capacity(n_uncovered_patterns + subs.len()); | |
241 | ||
242 | inner_tys.extend(expectations_iter.by_ref().take(n_uncovered_patterns + subs.len())); | |
243 | ||
244 | // Process pre | |
245 | for (ty, pat) in inner_tys.iter_mut().zip(pre) { | |
246 | *ty = T::infer(self, *pat, ty, default_bm); | |
247 | } | |
248 | ||
249 | // Process post | |
250 | for (ty, pat) in inner_tys.iter_mut().skip(pre.len() + n_uncovered_patterns).zip(post) { | |
251 | *ty = T::infer(self, *pat, ty, default_bm); | |
252 | } | |
253 | ||
254 | TyKind::Tuple(inner_tys.len(), Substitution::from_iter(Interner, inner_tys)) | |
255 | .intern(Interner) | |
256 | } | |
257 | ||
353b0b11 FG |
258 | pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) { |
259 | self.infer_pat(pat, expected, BindingMode::default()); | |
260 | } | |
261 | ||
262 | fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty { | |
064997fb FG |
263 | let mut expected = self.resolve_ty_shallow(expected); |
264 | ||
4b012472 | 265 | if self.is_non_ref_pat(self.body, pat) { |
064997fb FG |
266 | let mut pat_adjustments = Vec::new(); |
267 | while let Some((inner, _lifetime, mutability)) = expected.as_reference() { | |
268 | pat_adjustments.push(expected.clone()); | |
269 | expected = self.resolve_ty_shallow(inner); | |
270 | default_bm = match default_bm { | |
271 | BindingMode::Move => BindingMode::Ref(mutability), | |
272 | BindingMode::Ref(Mutability::Not) => BindingMode::Ref(Mutability::Not), | |
273 | BindingMode::Ref(Mutability::Mut) => BindingMode::Ref(mutability), | |
274 | } | |
275 | } | |
276 | ||
277 | if !pat_adjustments.is_empty() { | |
278 | pat_adjustments.shrink_to_fit(); | |
279 | self.result.pat_adjustments.insert(pat, pat_adjustments); | |
280 | } | |
281 | } else if let Pat::Ref { .. } = &self.body[pat] { | |
282 | cov_mark::hit!(match_ergonomics_ref); | |
283 | // When you encounter a `&pat` pattern, reset to Move. | |
284 | // This is so that `w` is by value: `let (_, &w) = &(1, &2);` | |
285 | default_bm = BindingMode::Move; | |
286 | } | |
287 | ||
288 | // Lose mutability. | |
289 | let default_bm = default_bm; | |
290 | let expected = expected; | |
291 | ||
292 | let ty = match &self.body[pat] { | |
293 | Pat::Tuple { args, ellipsis } => { | |
294 | self.infer_tuple_pat_like(&expected, default_bm, *ellipsis, args) | |
295 | } | |
296 | Pat::Or(pats) => { | |
353b0b11 FG |
297 | for pat in pats.iter() { |
298 | self.infer_pat(*pat, &expected, default_bm); | |
064997fb | 299 | } |
353b0b11 | 300 | expected.clone() |
064997fb | 301 | } |
353b0b11 FG |
302 | &Pat::Ref { pat, mutability } => self.infer_ref_pat( |
303 | pat, | |
304 | lower_to_chalk_mutability(mutability), | |
305 | &expected, | |
306 | default_bm, | |
307 | ), | |
064997fb FG |
308 | Pat::TupleStruct { path: p, args: subpats, ellipsis } => self |
309 | .infer_tuple_struct_pat_like( | |
310 | p.as_deref(), | |
311 | &expected, | |
312 | default_bm, | |
313 | pat, | |
314 | *ellipsis, | |
315 | subpats, | |
316 | ), | |
317 | Pat::Record { path: p, args: fields, ellipsis: _ } => { | |
318 | let subs = fields.iter().map(|f| (f.name.clone(), f.pat)); | |
9c376795 | 319 | self.infer_record_pat_like(p.as_deref(), &expected, default_bm, pat, subs) |
064997fb FG |
320 | } |
321 | Pat::Path(path) => { | |
353b0b11 FG |
322 | // FIXME update resolver for the surrounding expression |
323 | self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty()) | |
064997fb | 324 | } |
353b0b11 FG |
325 | Pat::Bind { id, subpat } => { |
326 | return self.infer_bind_pat(pat, *id, default_bm, *subpat, &expected); | |
064997fb FG |
327 | } |
328 | Pat::Slice { prefix, slice, suffix } => { | |
353b0b11 | 329 | self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm) |
064997fb FG |
330 | } |
331 | Pat::Wild => expected.clone(), | |
fe692bf9 FG |
332 | Pat::Range { .. } => { |
333 | // FIXME: do some checks here. | |
334 | expected.clone() | |
064997fb | 335 | } |
f2b60f7d | 336 | &Pat::Lit(expr) => { |
353b0b11 FG |
337 | // Don't emit type mismatches again, the expression lowering already did that. |
338 | let ty = self.infer_lit_pat(expr, &expected); | |
339 | self.write_pat_ty(pat, ty.clone()); | |
fe692bf9 | 340 | return self.pat_ty_after_adjustment(pat); |
f2b60f7d | 341 | } |
064997fb FG |
342 | Pat::Box { inner } => match self.resolve_boxed_box() { |
343 | Some(box_adt) => { | |
344 | let (inner_ty, alloc_ty) = match expected.as_adt() { | |
345 | Some((adt, subst)) if adt == box_adt => ( | |
346 | subst.at(Interner, 0).assert_ty_ref(Interner).clone(), | |
347 | subst.as_slice(Interner).get(1).and_then(|a| a.ty(Interner).cloned()), | |
348 | ), | |
349 | _ => (self.result.standard_types.unknown.clone(), None), | |
350 | }; | |
351 | ||
352 | let inner_ty = self.infer_pat(*inner, &inner_ty, default_bm); | |
353 | let mut b = TyBuilder::adt(self.db, box_adt).push(inner_ty); | |
354 | ||
355 | if let Some(alloc_ty) = alloc_ty { | |
356 | b = b.push(alloc_ty); | |
357 | } | |
358 | b.fill_with_defaults(self.db, || self.table.new_type_var()).build() | |
359 | } | |
360 | None => self.err_ty(), | |
361 | }, | |
362 | Pat::ConstBlock(expr) => { | |
363 | self.infer_expr(*expr, &Expectation::has_type(expected.clone())) | |
364 | } | |
365 | Pat::Missing => self.err_ty(), | |
366 | }; | |
367 | // use a new type variable if we got error type here | |
368 | let ty = self.insert_type_vars_shallow(ty); | |
353b0b11 FG |
369 | // FIXME: This never check is odd, but required with out we do inference right now |
370 | if !expected.is_never() && !self.unify(&ty, &expected) { | |
064997fb FG |
371 | self.result |
372 | .type_mismatches | |
373 | .insert(pat.into(), TypeMismatch { expected, actual: ty.clone() }); | |
374 | } | |
fe692bf9 FG |
375 | self.write_pat_ty(pat, ty); |
376 | self.pat_ty_after_adjustment(pat) | |
377 | } | |
378 | ||
379 | fn pat_ty_after_adjustment(&self, pat: PatId) -> Ty { | |
380 | self.result | |
381 | .pat_adjustments | |
382 | .get(&pat) | |
add651ee | 383 | .and_then(|it| it.first()) |
fe692bf9 FG |
384 | .unwrap_or(&self.result.type_of_pat[pat]) |
385 | .clone() | |
064997fb | 386 | } |
353b0b11 FG |
387 | |
388 | fn infer_ref_pat( | |
389 | &mut self, | |
fe692bf9 | 390 | inner_pat: PatId, |
353b0b11 FG |
391 | mutability: Mutability, |
392 | expected: &Ty, | |
393 | default_bm: BindingMode, | |
394 | ) -> Ty { | |
395 | let expectation = match expected.as_reference() { | |
396 | Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(), | |
fe692bf9 FG |
397 | None => { |
398 | let inner_ty = self.table.new_type_var(); | |
399 | let ref_ty = | |
400 | TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner); | |
401 | // Unification failure will be reported by the caller. | |
402 | self.unify(&ref_ty, expected); | |
403 | inner_ty | |
404 | } | |
353b0b11 | 405 | }; |
fe692bf9 | 406 | let subty = self.infer_pat(inner_pat, &expectation, default_bm); |
353b0b11 FG |
407 | TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) |
408 | } | |
409 | ||
410 | fn infer_bind_pat( | |
411 | &mut self, | |
412 | pat: PatId, | |
413 | binding: BindingId, | |
414 | default_bm: BindingMode, | |
415 | subpat: Option<PatId>, | |
416 | expected: &Ty, | |
417 | ) -> Ty { | |
418 | let Binding { mode, .. } = self.body.bindings[binding]; | |
419 | let mode = if mode == BindingAnnotation::Unannotated { | |
420 | default_bm | |
421 | } else { | |
422 | BindingMode::convert(mode) | |
423 | }; | |
4b012472 | 424 | self.result.binding_modes.insert(pat, mode); |
353b0b11 FG |
425 | |
426 | let inner_ty = match subpat { | |
427 | Some(subpat) => self.infer_pat(subpat, &expected, default_bm), | |
428 | None => expected.clone(), | |
429 | }; | |
430 | let inner_ty = self.insert_type_vars_shallow(inner_ty); | |
431 | ||
432 | let bound_ty = match mode { | |
433 | BindingMode::Ref(mutability) => { | |
434 | TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner) | |
435 | } | |
436 | BindingMode::Move => inner_ty.clone(), | |
437 | }; | |
fe692bf9 | 438 | self.write_pat_ty(pat, inner_ty.clone()); |
353b0b11 FG |
439 | self.write_binding_ty(binding, bound_ty); |
440 | return inner_ty; | |
441 | } | |
442 | ||
443 | fn infer_slice_pat( | |
444 | &mut self, | |
445 | expected: &Ty, | |
446 | prefix: &[PatId], | |
447 | slice: &Option<PatId>, | |
448 | suffix: &[PatId], | |
449 | default_bm: BindingMode, | |
450 | ) -> Ty { | |
451 | let elem_ty = match expected.kind(Interner) { | |
452 | TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(), | |
453 | _ => self.err_ty(), | |
454 | }; | |
455 | ||
456 | for &pat_id in prefix.iter().chain(suffix.iter()) { | |
457 | self.infer_pat(pat_id, &elem_ty, default_bm); | |
458 | } | |
459 | ||
460 | if let &Some(slice_pat_id) = slice { | |
461 | let rest_pat_ty = match expected.kind(Interner) { | |
462 | TyKind::Array(_, length) => { | |
fe692bf9 | 463 | let len = try_const_usize(self.db, length); |
353b0b11 FG |
464 | let len = |
465 | len.and_then(|len| len.checked_sub((prefix.len() + suffix.len()) as u128)); | |
466 | TyKind::Array(elem_ty.clone(), usize_const(self.db, len, self.resolver.krate())) | |
467 | } | |
468 | _ => TyKind::Slice(elem_ty.clone()), | |
469 | } | |
470 | .intern(Interner); | |
471 | self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm); | |
472 | } | |
473 | ||
474 | match expected.kind(Interner) { | |
475 | TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()), | |
476 | _ => TyKind::Slice(elem_ty), | |
477 | } | |
478 | .intern(Interner) | |
479 | } | |
480 | ||
481 | fn infer_lit_pat(&mut self, expr: ExprId, expected: &Ty) -> Ty { | |
482 | // Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`. | |
483 | if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] { | |
484 | if let Some((inner, ..)) = expected.as_reference() { | |
485 | let inner = self.resolve_ty_shallow(inner); | |
486 | if matches!(inner.kind(Interner), TyKind::Slice(_)) { | |
487 | let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); | |
488 | let slice_ty = TyKind::Slice(elem_ty).intern(Interner); | |
489 | let ty = | |
490 | TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty).intern(Interner); | |
491 | self.write_expr_ty(expr, ty.clone()); | |
492 | return ty; | |
493 | } | |
494 | } | |
495 | } | |
496 | ||
497 | self.infer_expr(expr, &Expectation::has_type(expected.clone())) | |
498 | } | |
064997fb | 499 | |
4b012472 FG |
500 | fn is_non_ref_pat(&mut self, body: &hir_def::body::Body, pat: PatId) -> bool { |
501 | match &body[pat] { | |
502 | Pat::Tuple { .. } | |
503 | | Pat::TupleStruct { .. } | |
504 | | Pat::Record { .. } | |
505 | | Pat::Range { .. } | |
506 | | Pat::Slice { .. } => true, | |
507 | Pat::Or(pats) => pats.iter().all(|p| self.is_non_ref_pat(body, *p)), | |
508 | Pat::Path(p) => { | |
509 | let v = self.resolve_value_path_inner(p, pat.into()); | |
510 | v.is_some_and(|x| !matches!(x.0, hir_def::resolver::ValueNs::ConstId(_))) | |
511 | } | |
512 | Pat::ConstBlock(..) => false, | |
513 | Pat::Lit(expr) => !matches!( | |
514 | body[*expr], | |
515 | Expr::Literal(Literal::String(..) | Literal::CString(..) | Literal::ByteString(..)) | |
516 | ), | |
517 | Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => { | |
518 | false | |
519 | } | |
520 | } | |
064997fb FG |
521 | } |
522 | } | |
353b0b11 FG |
523 | |
524 | pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool { | |
525 | let mut res = false; | |
526 | body.walk_pats(pat_id, &mut |pat| { | |
fe692bf9 | 527 | res |= matches!(body[pat], Pat::Bind { id, .. } if body.bindings[id].mode == BindingAnnotation::Ref); |
353b0b11 FG |
528 | }); |
529 | res | |
530 | } |