Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | crates/hir-ty/src/infer.rs | 8 | ||||
| -rw-r--r-- | crates/hir-ty/src/infer/closure.rs | 385 | ||||
| -rw-r--r-- | crates/hir-ty/src/mir/lower.rs | 11 | ||||
| -rw-r--r-- | crates/hir-ty/src/tests/closure_captures.rs | 231 |
4 files changed, 456 insertions, 179 deletions
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 8b4e98c550..062ea27815 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -59,6 +59,7 @@ use crate::{ generics::Generics, infer::{coerce::CoerceMany, unify::InferenceTable}, lower::ImplTraitLoweringMode, + mir::MirSpan, to_assoc_type_id, traits::FnTrait, utils::{InTypeConstIdMetadata, UnevaluatedConstEvaluatorFolder}, @@ -553,6 +554,12 @@ pub(crate) struct InferenceContext<'a> { // fields related to closure capture current_captures: Vec<CapturedItemWithoutTy>, + /// A stack that has an entry for each projection in the current capture. + /// + /// For example, in `a.b.c`, we capture the spans of `a`, `a.b`, and `a.b.c`. + /// We do that because sometimes we truncate projections (when a closure captures + /// both `a.b` and `a.b.c`), and we want to provide accurate spans in this case. + current_capture_span_stack: Vec<MirSpan>, current_closure: Option<ClosureId>, /// Stores the list of closure ids that need to be analyzed before this closure. See the /// comment on `InferenceContext::sort_closures` @@ -634,6 +641,7 @@ impl<'a> InferenceContext<'a> { breakables: Vec::new(), deferred_cast_checks: Vec::new(), current_captures: Vec::new(), + current_capture_span_stack: Vec::new(), current_closure: None, deferred_closures: FxHashMap::default(), closure_dependencies: FxHashMap::default(), diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 910e19dc58..68e1a6ee82 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -18,7 +18,7 @@ use hir_def::{ use hir_expand::name::Name; use intern::sym; use rustc_hash::FxHashMap; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use stdx::never; use crate::{ @@ -236,7 +236,13 @@ pub enum CaptureKind { pub struct CapturedItem { pub(crate) place: HirPlace, pub(crate) kind: CaptureKind, - pub(crate) span: MirSpan, + /// The inner vec is the stacks; the outer vec is for each capture reference. + /// + /// Even though we always report only the last span (i.e. the most inclusive span), + /// we need to keep them all, since when a closure occurs inside a closure, we + /// copy all captures of the inner closure to the outer closure, and then we may + /// truncate them, and we want the correct span to be reported. + span_stacks: SmallVec<[SmallVec<[MirSpan; 3]>; 3]>, pub(crate) ty: Binders<Ty>, } @@ -253,6 +259,10 @@ impl CapturedItem { self.kind } + pub fn spans(&self) -> SmallVec<[MirSpan; 3]> { + self.span_stacks.iter().map(|stack| *stack.last().expect("empty span stack")).collect() + } + pub fn display_place(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String { let body = db.body(owner); let krate = owner.krate(db.upcast()); @@ -314,7 +324,8 @@ impl CapturedItem { pub(crate) struct CapturedItemWithoutTy { pub(crate) place: HirPlace, pub(crate) kind: CaptureKind, - pub(crate) span: MirSpan, + /// The inner vec is the stacks; the outer vec is for each capture reference. + pub(crate) span_stacks: SmallVec<[SmallVec<[MirSpan; 3]>; 3]>, } impl CapturedItemWithoutTy { @@ -333,7 +344,7 @@ impl CapturedItemWithoutTy { return CapturedItem { place: self.place, kind: self.kind, - span: self.span, + span_stacks: self.span_stacks, ty: replace_placeholder_with_binder(ctx, ty), }; @@ -393,22 +404,26 @@ impl InferenceContext<'_> { let r = self.place_of_expr_without_adjust(tgt_expr)?; let default = vec![]; let adjustments = self.result.expr_adjustments.get(&tgt_expr).unwrap_or(&default); - apply_adjusts_to_place(r, adjustments) + apply_adjusts_to_place(&mut self.current_capture_span_stack, r, adjustments) } + /// Changes `current_capture_span_stack` to contain the stack of spans for this expr. fn place_of_expr_without_adjust(&mut self, tgt_expr: ExprId) -> Option<HirPlace> { + self.current_capture_span_stack.clear(); match &self.body[tgt_expr] { Expr::Path(p) => { let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); if let Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(b), _)) = resolver.resolve_path_in_value_ns(self.db.upcast(), p) { + self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); return Some(HirPlace { local: b, projections: vec![] }); } } Expr::Field { expr, name: _ } => { let mut place = self.place_of_expr(*expr)?; let field = self.result.field_resolution(tgt_expr)?; + self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); place.projections.push(ProjectionElem::Field(field)); return Some(place); } @@ -418,6 +433,7 @@ impl InferenceContext<'_> { TyKind::Ref(..) | TyKind::Raw(..) ) { let mut place = self.place_of_expr(*expr)?; + self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); place.projections.push(ProjectionElem::Deref); return Some(place); } @@ -427,29 +443,65 @@ impl InferenceContext<'_> { None } - fn push_capture(&mut self, capture: CapturedItemWithoutTy) { - self.current_captures.push(capture); + fn push_capture(&mut self, place: HirPlace, kind: CaptureKind) { + self.current_captures.push(CapturedItemWithoutTy { + place, + kind, + span_stacks: smallvec![self.current_capture_span_stack.iter().copied().collect()], + }); } - fn ref_expr(&mut self, expr: ExprId) { - if let Some(place) = self.place_of_expr(expr) { - self.add_capture(place, CaptureKind::ByRef(BorrowKind::Shared), expr.into()); + fn is_ref_span(&self, span: MirSpan) -> bool { + match span { + MirSpan::ExprId(expr) => matches!(self.body[expr], Expr::Ref { .. }), + MirSpan::BindingId(_) => true, + MirSpan::PatId(_) | MirSpan::SelfParam | MirSpan::Unknown => false, + } + } + + fn truncate_capture_spans(&self, capture: &mut CapturedItemWithoutTy, mut truncate_to: usize) { + // The first span is the identifier, and it must always remain. + truncate_to += 1; + for span_stack in &mut capture.span_stacks { + let mut remained = truncate_to; + let mut actual_truncate_to = 0; + for &span in &*span_stack { + actual_truncate_to += 1; + if !self.is_ref_span(span) { + remained -= 1; + if remained == 0 { + break; + } + } + } + if actual_truncate_to < span_stack.len() + && self.is_ref_span(span_stack[actual_truncate_to]) + { + // Include the ref operator if there is one, we will fix it later (in `strip_captures_ref_span()`) if it's incorrect. + actual_truncate_to += 1; + } + span_stack.truncate(actual_truncate_to); + } + } + + fn ref_expr(&mut self, expr: ExprId, place: Option<HirPlace>) { + if let Some(place) = place { + self.add_capture(place, CaptureKind::ByRef(BorrowKind::Shared)); } self.walk_expr(expr); } - fn add_capture(&mut self, place: HirPlace, kind: CaptureKind, span: MirSpan) { + fn add_capture(&mut self, place: HirPlace, kind: CaptureKind) { if self.is_upvar(&place) { - self.push_capture(CapturedItemWithoutTy { place, kind, span }); + self.push_capture(place, kind); } } - fn mutate_expr(&mut self, expr: ExprId) { - if let Some(place) = self.place_of_expr(expr) { + fn mutate_expr(&mut self, expr: ExprId, place: Option<HirPlace>) { + if let Some(place) = place { self.add_capture( place, CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::Default }), - expr.into(), ); } self.walk_expr(expr); @@ -457,12 +509,12 @@ impl InferenceContext<'_> { fn consume_expr(&mut self, expr: ExprId) { if let Some(place) = self.place_of_expr(expr) { - self.consume_place(place, expr.into()); + self.consume_place(place); } self.walk_expr(expr); } - fn consume_place(&mut self, place: HirPlace, span: MirSpan) { + fn consume_place(&mut self, place: HirPlace) { if self.is_upvar(&place) { let ty = place.ty(self); let kind = if self.is_ty_copy(ty) { @@ -470,7 +522,7 @@ impl InferenceContext<'_> { } else { CaptureKind::ByValue }; - self.push_capture(CapturedItemWithoutTy { place, kind, span }); + self.push_capture(place, kind); } } @@ -501,8 +553,10 @@ impl InferenceContext<'_> { Mutability::Not => CaptureKind::ByRef(BorrowKind::Shared), }; if let Some(place) = self.place_of_expr_without_adjust(tgt_expr) { - if let Some(place) = apply_adjusts_to_place(place, rest) { - self.add_capture(place, capture_kind, tgt_expr.into()); + if let Some(place) = + apply_adjusts_to_place(&mut self.current_capture_span_stack, place, rest) + { + self.add_capture(place, capture_kind); } } self.walk_expr_with_adjust(tgt_expr, rest); @@ -584,11 +638,7 @@ impl InferenceContext<'_> { self.walk_pat(&mut capture_mode, arm.pat); } if let Some(c) = capture_mode { - self.push_capture(CapturedItemWithoutTy { - place: discr_place, - kind: c, - span: (*expr).into(), - }) + self.push_capture(discr_place, c); } } } @@ -632,10 +682,11 @@ impl InferenceContext<'_> { } false }; + let place = self.place_of_expr(*expr); if mutability { - self.mutate_expr(*expr); + self.mutate_expr(*expr, place); } else { - self.ref_expr(*expr); + self.ref_expr(*expr, place); } } else { self.select_from_expr(*expr); @@ -650,16 +701,22 @@ impl InferenceContext<'_> { | Expr::Cast { expr, type_ref: _ } => { self.consume_expr(*expr); } - Expr::Ref { expr, rawness: _, mutability } => match mutability { - hir_def::type_ref::Mutability::Shared => self.ref_expr(*expr), - hir_def::type_ref::Mutability::Mut => self.mutate_expr(*expr), - }, + Expr::Ref { expr, rawness: _, mutability } => { + // We need to do this before we push the span so the order will be correct. + let place = self.place_of_expr(*expr); + self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr)); + match mutability { + hir_def::type_ref::Mutability::Shared => self.ref_expr(*expr, place), + hir_def::type_ref::Mutability::Mut => self.mutate_expr(*expr, place), + } + } Expr::BinaryOp { lhs, rhs, op } => { let Some(op) = op else { return; }; if matches!(op, BinaryOp::Assignment { .. }) { - self.mutate_expr(*lhs); + let place = self.place_of_expr(*lhs); + self.mutate_expr(*lhs, place); self.consume_expr(*rhs); return; } @@ -690,7 +747,11 @@ impl InferenceContext<'_> { ); let mut cc = mem::take(&mut self.current_captures); cc.extend(captures.iter().filter(|it| self.is_upvar(&it.place)).map(|it| { - CapturedItemWithoutTy { place: it.place.clone(), kind: it.kind, span: it.span } + CapturedItemWithoutTy { + place: it.place.clone(), + kind: it.kind, + span_stacks: it.span_stacks.clone(), + } })); self.current_captures = cc; } @@ -812,10 +873,13 @@ impl InferenceContext<'_> { } fn restrict_precision_for_unsafe(&mut self) { - for capture in &mut self.current_captures { + // FIXME: Borrow checker problems without this. + let mut current_captures = std::mem::take(&mut self.current_captures); + for capture in &mut current_captures { let mut ty = self.table.resolve_completely(self.result[capture.place.local].clone()); if ty.as_raw_ptr().is_some() || ty.is_union() { capture.kind = CaptureKind::ByRef(BorrowKind::Shared); + self.truncate_capture_spans(capture, 0); capture.place.projections.truncate(0); continue; } @@ -830,29 +894,35 @@ impl InferenceContext<'_> { ); if ty.as_raw_ptr().is_some() || ty.is_union() { capture.kind = CaptureKind::ByRef(BorrowKind::Shared); + self.truncate_capture_spans(capture, i + 1); capture.place.projections.truncate(i + 1); break; } } } + self.current_captures = current_captures; } fn adjust_for_move_closure(&mut self) { - for capture in &mut self.current_captures { + // FIXME: Borrow checker won't allow without this. + let mut current_captures = std::mem::take(&mut self.current_captures); + for capture in &mut current_captures { if let Some(first_deref) = capture.place.projections.iter().position(|proj| *proj == ProjectionElem::Deref) { + self.truncate_capture_spans(capture, first_deref); capture.place.projections.truncate(first_deref); } capture.kind = CaptureKind::ByValue; } + self.current_captures = current_captures; } fn minimize_captures(&mut self) { - self.current_captures.sort_by_key(|it| it.place.projections.len()); + self.current_captures.sort_unstable_by_key(|it| it.place.projections.len()); let mut hash_map = FxHashMap::<HirPlace, usize>::default(); let result = mem::take(&mut self.current_captures); - for item in result { + for mut item in result { let mut lookup_place = HirPlace { local: item.place.local, projections: vec![] }; let mut it = item.place.projections.iter(); let prev_index = loop { @@ -860,12 +930,17 @@ impl InferenceContext<'_> { break Some(*k); } match it.next() { - Some(it) => lookup_place.projections.push(it.clone()), + Some(it) => { + lookup_place.projections.push(it.clone()); + } None => break None, } }; match prev_index { Some(p) => { + let prev_projections_len = self.current_captures[p].place.projections.len(); + self.truncate_capture_spans(&mut item, prev_projections_len); + self.current_captures[p].span_stacks.extend(item.span_stacks); let len = self.current_captures[p].place.projections.len(); let kind_after_truncate = item.place.capture_kind_of_truncated_place(item.kind, len); @@ -880,113 +955,128 @@ impl InferenceContext<'_> { } } - fn consume_with_pat(&mut self, mut place: HirPlace, pat: PatId) { - let cnt = self.result.pat_adjustments.get(&pat).map(|it| it.len()).unwrap_or_default(); - place.projections = place - .projections - .iter() - .cloned() - .chain((0..cnt).map(|_| ProjectionElem::Deref)) - .collect::<Vec<_>>(); - match &self.body[pat] { - Pat::Missing | Pat::Wild => (), - Pat::Tuple { args, ellipsis } => { - let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); - let field_count = match self.result[pat].kind(Interner) { - TyKind::Tuple(_, s) => s.len(Interner), - _ => return, - }; - let fields = 0..field_count; - let it = al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); - for (arg, i) in it { - let mut p = place.clone(); - p.projections.push(ProjectionElem::Field(Either::Right(TupleFieldId { - tuple: TupleId(!0), // dummy this, as its unused anyways - index: i as u32, - }))); - self.consume_with_pat(p, *arg); - } - } - Pat::Or(pats) => { - for pat in pats.iter() { - self.consume_with_pat(place.clone(), *pat); + fn consume_with_pat(&mut self, mut place: HirPlace, tgt_pat: PatId) { + let adjustments_count = + self.result.pat_adjustments.get(&tgt_pat).map(|it| it.len()).unwrap_or_default(); + place.projections.extend((0..adjustments_count).map(|_| ProjectionElem::Deref)); + self.current_capture_span_stack + .extend((0..adjustments_count).map(|_| MirSpan::PatId(tgt_pat))); + 'reset_span_stack: { + match &self.body[tgt_pat] { + Pat::Missing | Pat::Wild => (), + Pat::Tuple { args, ellipsis } => { + let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); + let field_count = match self.result[tgt_pat].kind(Interner) { + TyKind::Tuple(_, s) => s.len(Interner), + _ => break 'reset_span_stack, + }; + let fields = 0..field_count; + let it = al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); + for (&arg, i) in it { + let mut p = place.clone(); + self.current_capture_span_stack.push(MirSpan::PatId(arg)); + p.projections.push(ProjectionElem::Field(Either::Right(TupleFieldId { + tuple: TupleId(!0), // dummy this, as its unused anyways + index: i as u32, + }))); + self.consume_with_pat(p, arg); + self.current_capture_span_stack.pop(); + } } - } - Pat::Record { args, .. } => { - let Some(variant) = self.result.variant_resolution_for_pat(pat) else { - return; - }; - match variant { - VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { - self.consume_place(place, pat.into()) + Pat::Or(pats) => { + for pat in pats.iter() { + self.consume_with_pat(place.clone(), *pat); } - VariantId::StructId(s) => { - let vd = &*self.db.struct_data(s).variant_data; - for field_pat in args.iter() { - let arg = field_pat.pat; - let Some(local_id) = vd.field(&field_pat.name) else { - continue; - }; - let mut p = place.clone(); - p.projections.push(ProjectionElem::Field(Either::Left(FieldId { - parent: variant, - local_id, - }))); - self.consume_with_pat(p, arg); + } + Pat::Record { args, .. } => { + let Some(variant) = self.result.variant_resolution_for_pat(tgt_pat) else { + break 'reset_span_stack; + }; + match variant { + VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { + self.consume_place(place) + } + VariantId::StructId(s) => { + let vd = &*self.db.struct_data(s).variant_data; + for field_pat in args.iter() { + let arg = field_pat.pat; + let Some(local_id) = vd.field(&field_pat.name) else { + continue; + }; + let mut p = place.clone(); + self.current_capture_span_stack.push(MirSpan::PatId(arg)); + p.projections.push(ProjectionElem::Field(Either::Left(FieldId { + parent: variant, + local_id, + }))); + self.consume_with_pat(p, arg); + self.current_capture_span_stack.pop(); + } } } } - } - Pat::Range { .. } - | Pat::Slice { .. } - | Pat::ConstBlock(_) - | Pat::Path(_) - | Pat::Lit(_) => self.consume_place(place, pat.into()), - Pat::Bind { id: _, subpat: _ } => { - let mode = self.result.binding_modes[pat]; - let capture_kind = match mode { - BindingMode::Move => { - self.consume_place(place, pat.into()); - return; - } - BindingMode::Ref(Mutability::Not) => BorrowKind::Shared, - BindingMode::Ref(Mutability::Mut) => { - BorrowKind::Mut { kind: MutBorrowKind::Default } - } - }; - self.add_capture(place, CaptureKind::ByRef(capture_kind), pat.into()); - } - Pat::TupleStruct { path: _, args, ellipsis } => { - let Some(variant) = self.result.variant_resolution_for_pat(pat) else { - return; - }; - match variant { - VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { - self.consume_place(place, pat.into()) - } - VariantId::StructId(s) => { - let vd = &*self.db.struct_data(s).variant_data; - let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); - let fields = vd.fields().iter(); - let it = - al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); - for (arg, (i, _)) in it { - let mut p = place.clone(); - p.projections.push(ProjectionElem::Field(Either::Left(FieldId { - parent: variant, - local_id: i, - }))); - self.consume_with_pat(p, *arg); + Pat::Range { .. } + | Pat::Slice { .. } + | Pat::ConstBlock(_) + | Pat::Path(_) + | Pat::Lit(_) => self.consume_place(place), + &Pat::Bind { id, subpat: _ } => { + let mode = self.result.binding_modes[tgt_pat]; + let capture_kind = match mode { + BindingMode::Move => { + self.consume_place(place); + break 'reset_span_stack; + } + BindingMode::Ref(Mutability::Not) => BorrowKind::Shared, + BindingMode::Ref(Mutability::Mut) => { + BorrowKind::Mut { kind: MutBorrowKind::Default } + } + }; + self.current_capture_span_stack.push(MirSpan::BindingId(id)); + self.add_capture(place, CaptureKind::ByRef(capture_kind)); + self.current_capture_span_stack.pop(); + } + Pat::TupleStruct { path: _, args, ellipsis } => { + let Some(variant) = self.result.variant_resolution_for_pat(tgt_pat) else { + break 'reset_span_stack; + }; + match variant { + VariantId::EnumVariantId(_) | VariantId::UnionId(_) => { + self.consume_place(place) + } + VariantId::StructId(s) => { + let vd = &*self.db.struct_data(s).variant_data; + let (al, ar) = + args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); + let fields = vd.fields().iter(); + let it = al + .iter() + .zip(fields.clone()) + .chain(ar.iter().rev().zip(fields.rev())); + for (&arg, (i, _)) in it { + let mut p = place.clone(); + self.current_capture_span_stack.push(MirSpan::PatId(arg)); + p.projections.push(ProjectionElem::Field(Either::Left(FieldId { + parent: variant, + local_id: i, + }))); + self.consume_with_pat(p, arg); + self.current_capture_span_stack.pop(); + } } } } + Pat::Ref { pat, mutability: _ } => { + self.current_capture_span_stack.push(MirSpan::PatId(tgt_pat)); + place.projections.push(ProjectionElem::Deref); + self.consume_with_pat(place, *pat); + self.current_capture_span_stack.pop(); + } + Pat::Box { .. } => (), // not supported } - Pat::Ref { pat, mutability: _ } => { - place.projections.push(ProjectionElem::Deref); - self.consume_with_pat(place, *pat) - } - Pat::Box { .. } => (), // not supported } + self.current_capture_span_stack + .truncate(self.current_capture_span_stack.len() - adjustments_count); } fn consume_exprs(&mut self, exprs: impl Iterator<Item = ExprId>) { @@ -1044,12 +1134,28 @@ impl InferenceContext<'_> { CaptureBy::Ref => (), } self.minimize_captures(); + self.strip_captures_ref_span(); let result = mem::take(&mut self.current_captures); let captures = result.into_iter().map(|it| it.with_ty(self)).collect::<Vec<_>>(); self.result.closure_info.insert(closure, (captures, closure_kind)); closure_kind } + fn strip_captures_ref_span(&mut self) { + // FIXME: Borrow checker won't allow without this. + let mut captures = std::mem::take(&mut self.current_captures); + for capture in &mut captures { + if matches!(capture.kind, CaptureKind::ByValue) { + for span_stack in &mut capture.span_stacks { + if self.is_ref_span(span_stack[span_stack.len() - 1]) { + span_stack.truncate(span_stack.len() - 1); + } + } + } + } + self.current_captures = captures; + } + pub(crate) fn infer_closures(&mut self) { let deferred_closures = self.sort_closures(); for (closure, exprs) in deferred_closures.into_iter().rev() { @@ -1110,10 +1216,17 @@ impl InferenceContext<'_> { } } -fn apply_adjusts_to_place(mut r: HirPlace, adjustments: &[Adjustment]) -> Option<HirPlace> { +/// Call this only when the last span in the stack isn't a split. +fn apply_adjusts_to_place( + current_capture_span_stack: &mut Vec<MirSpan>, + mut r: HirPlace, + adjustments: &[Adjustment], +) -> Option<HirPlace> { + let span = *current_capture_span_stack.last().expect("empty capture span stack"); for adj in adjustments { match &adj.kind { Adjust::Deref(None) => { + current_capture_span_stack.push(span); r.projections.push(ProjectionElem::Deref); } _ => return None, diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 3a0d0c933f..9e23550451 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -1180,8 +1180,15 @@ impl<'ctx> MirLowerCtx<'ctx> { let placeholder_subst = self.placeholder_subst(); let tmp_ty = capture.ty.clone().substitute(Interner, &placeholder_subst); - let tmp: Place = self.temp(tmp_ty, current, capture.span)?.into(); - self.push_assignment(current, tmp, Rvalue::Ref(*bk, p), capture.span); + // FIXME: Handle more than one span. + let capture_spans = capture.spans(); + let tmp: Place = self.temp(tmp_ty, current, capture_spans[0])?.into(); + self.push_assignment( + current, + tmp, + Rvalue::Ref(*bk, p), + capture_spans[0], + ); operands.push(Operand::Move(tmp)); } CaptureKind::ByValue => operands.push(Operand::Move(p)), diff --git a/crates/hir-ty/src/tests/closure_captures.rs b/crates/hir-ty/src/tests/closure_captures.rs index 782331a136..22cef3505b 100644 --- a/crates/hir-ty/src/tests/closure_captures.rs +++ b/crates/hir-ty/src/tests/closure_captures.rs @@ -1,11 +1,15 @@ use base_db::salsa::InternKey; use expect_test::{expect, Expect}; use hir_def::db::DefDatabase; +use hir_expand::files::InFileWrapper; use itertools::Itertools; +use span::{HirFileId, TextRange}; +use syntax::{AstNode, AstPtr}; use test_fixture::WithFixture; use crate::db::{HirDatabase, InternedClosureId}; use crate::display::HirDisplay; +use crate::mir::MirSpan; use crate::test_db::TestDB; use super::visit_module; @@ -30,45 +34,69 @@ fn check_closure_captures(ra_fixture: &str, expect: Expect) { .expect("failed to map closure to SyntaxNode") .value .text_range(); - captures.iter().flat_map(move |capture| { + captures.iter().map(move |capture| { + fn text_range<N: AstNode>( + db: &TestDB, + syntax: InFileWrapper<HirFileId, AstPtr<N>>, + ) -> TextRange { + let root = syntax.file_syntax(db); + syntax.value.to_node(&root).syntax().text_range() + } + // FIXME: Deduplicate this with hir::Local::sources(). let (body, source_map) = db.body_with_source_map(closure.0); - let local_text_ranges = match body.self_param.zip(source_map.self_param_syntax()) { + let local_text_range = match body.self_param.zip(source_map.self_param_syntax()) { Some((param, source)) if param == capture.local() => { - vec![source.file_syntax(db).text_range()] + format!("{:?}", text_range(db, source)) } _ => source_map .patterns_for_binding(capture.local()) .iter() .map(|&definition| { - let src = source_map.pat_syntax(definition).unwrap(); - src.file_syntax(db).text_range() + text_range(db, source_map.pat_syntax(definition).unwrap()) }) - .collect(), + .map(|it| format!("{it:?}")) + .join(", "), }; let place = capture.display_place(closure.0, db); let capture_ty = capture.ty.skip_binders().display_test(db).to_string(); - local_text_ranges.into_iter().map(move |local_text_range| { - ( - closure_text_range, - local_text_range, - place.clone(), - capture_ty.clone(), - capture.kind(), - ) - }) + let spans = capture + .spans() + .iter() + .flat_map(|span| match *span { + MirSpan::ExprId(expr) => { + vec![text_range(db, source_map.expr_syntax(expr).unwrap())] + } + MirSpan::PatId(pat) => { + vec![text_range(db, source_map.pat_syntax(pat).unwrap())] + } + MirSpan::BindingId(binding) => source_map + .patterns_for_binding(binding) + .iter() + .map(|pat| text_range(db, source_map.pat_syntax(*pat).unwrap())) + .collect(), + MirSpan::SelfParam => { + vec![text_range(db, source_map.self_param_syntax().unwrap())] + } + MirSpan::Unknown => Vec::new(), + }) + .sorted_by_key(|it| it.start()) + .map(|it| format!("{it:?}")) + .join(","); + + (closure_text_range, local_text_range, spans, place, capture_ty, capture.kind()) }) })); } captures_info.sort_unstable_by_key(|(closure_text_range, local_text_range, ..)| { - (closure_text_range.start(), local_text_range.start()) + (closure_text_range.start(), local_text_range.clone()) }); let rendered = captures_info .iter() - .map(|(closure_text_range, local_text_range, place, capture_ty, capture_kind)| { + .map(|(closure_text_range, local_text_range, spans, place, capture_ty, capture_kind)| { format!( - "{closure_text_range:?};{local_text_range:?} {capture_kind:?} {place} {capture_ty}" + "{closure_text_range:?};{local_text_range};{spans} {capture_kind:?} {place} {capture_ty}" ) }) .join("\n"); @@ -86,7 +114,7 @@ fn main() { let closure = || { let b = *a; }; } "#, - expect!["53..71;0..75 ByRef(Shared) *a &'? bool"], + expect!["53..71;20..21;66..68 ByRef(Shared) *a &'? bool"], ); } @@ -100,7 +128,7 @@ fn main() { let closure = || { let &mut ref b = a; }; } "#, - expect!["53..79;0..83 ByRef(Shared) *a &'? bool"], + expect!["53..79;20..21;67..72 ByRef(Shared) *a &'? bool"], ); check_closure_captures( r#" @@ -110,7 +138,7 @@ fn main() { let closure = || { let &mut ref mut b = a; }; } "#, - expect!["53..83;0..87 ByRef(Mut { kind: Default }) *a &'? mut bool"], + expect!["53..83;20..21;67..76 ByRef(Mut { kind: Default }) *a &'? mut bool"], ); } @@ -124,7 +152,7 @@ fn main() { let closure = || { *a = false; }; } "#, - expect!["53..71;0..75 ByRef(Mut { kind: Default }) *a &'? mut bool"], + expect!["53..71;20..21;58..60 ByRef(Mut { kind: Default }) *a &'? mut bool"], ); } @@ -138,7 +166,7 @@ fn main() { let closure = || { let ref mut b = *a; }; } "#, - expect!["53..79;0..83 ByRef(Mut { kind: Default }) *a &'? mut bool"], + expect!["53..79;20..21;62..71 ByRef(Mut { kind: Default }) *a &'? mut bool"], ); } @@ -152,7 +180,7 @@ fn main() { let closure = || { let _ = *a else { return; }; }; } "#, - expect!["53..88;0..92 ByRef(Shared) *a &'? bool"], + expect!["53..88;20..21;66..68 ByRef(Shared) *a &'? bool"], ); } @@ -167,7 +195,7 @@ fn main() { let closure = || { let b = a; }; } "#, - expect!["67..84;0..88 ByValue a NonCopy"], + expect!["67..84;36..37;80..81 ByValue a NonCopy"], ); } @@ -184,8 +212,8 @@ fn main() { } "#, expect![[r#" - 71..89;0..135 ByRef(Shared) a &'? NonCopy - 109..131;0..135 ByRef(Mut { kind: Default }) a &'? mut NonCopy"#]], + 71..89;36..41;84..86 ByRef(Shared) a &'? NonCopy + 109..131;36..41;122..128 ByRef(Mut { kind: Default }) a &'? mut NonCopy"#]], ); } @@ -200,7 +228,7 @@ fn main() { let closure = || { let b = a.a; }; } "#, - expect!["92..111;0..115 ByRef(Shared) a.a &'? i32"], + expect!["92..111;50..51;105..108 ByRef(Shared) a.a &'? i32"], ); } @@ -221,9 +249,9 @@ fn main() { } "#, expect![[r#" - 133..212;0..216 ByRef(Shared) a.a &'? i32 - 133..212;0..216 ByRef(Mut { kind: Default }) a.b &'? mut i32 - 133..212;0..216 ByValue a.c NonCopy"#]], + 133..212;87..92;154..158 ByRef(Shared) a.a &'? i32 + 133..212;87..92;176..184 ByRef(Mut { kind: Default }) a.b &'? mut i32 + 133..212;87..92;202..205 ByValue a.c NonCopy"#]], ); } @@ -244,8 +272,8 @@ fn main() { } "#, expect![[r#" - 123..133;0..168 ByRef(Shared) a &'? Foo - 153..164;0..168 ByRef(Mut { kind: Default }) a &'? mut Foo"#]], + 123..133;92..97;126..127 ByRef(Shared) a &'? Foo + 153..164;92..97;156..157 ByRef(Mut { kind: Default }) a &'? mut Foo"#]], ); } @@ -262,11 +290,6 @@ fn main() { *a = false; let b = &mut a; }; - // Max ByRef(Mut { kind: ClosureCapture }) - let closure = || { - let b = *a; - let c = &mut *a; - }; // Max ByValue let mut a = NonCopy; let closure = || { @@ -277,8 +300,134 @@ fn main() { } "#, expect![[r#" - 113..167;0..430 ByRef(Mut { kind: Default }) a &'? mut &'? mut bool - 234..289;0..430 ByRef(Mut { kind: Default }) *a &'? mut bool - 353..426;0..430 ByValue a NonCopy"#]], + 113..167;36..41;127..128,154..160 ByRef(Mut { kind: Default }) a &'? mut &'? mut bool + 231..304;196..201;252..253,276..277,296..297 ByValue a NonCopy"#]], + ); +} + +#[test] +fn let_underscore() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = true; + let closure = || { let _ = a; }; +} +"#, + expect![""], + ); +} + +#[test] +fn match_wildcard() { + check_closure_captures( + r#" +//- minicore:copy +struct NonCopy; +fn main() { + let mut a = NonCopy; + let closure = || match a { + _ => {} + }; + let closure = || match a { + ref b => {} + }; + let closure = || match a { + ref mut b => {} + }; +} +"#, + expect![[r#" + 125..163;36..41;134..135 ByRef(Shared) a &'? NonCopy + 183..225;36..41;192..193 ByRef(Mut { kind: Default }) a &'? mut NonCopy"#]], + ); +} + +#[test] +fn multiple_bindings() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = false; + let mut closure = || { let (b | b) = a; }; +} +"#, + expect!["57..80;20..25;76..77,76..77 ByRef(Shared) a &'? bool"], + ); +} + +#[test] +fn multiple_usages() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = false; + let mut closure = || { + let b = &a; + let c = &a; + let d = &mut a; + a = true; + }; +} +"#, + expect!["57..149;20..25;78..80,98..100,118..124,134..135 ByRef(Mut { kind: Default }) a &'? mut bool"], + ); +} + +#[test] +fn ref_then_deref() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = false; + let mut closure = || { let b = *&mut a; }; +} +"#, + expect!["57..80;20..25;71..77 ByRef(Mut { kind: Default }) a &'? mut bool"], + ); +} + +#[test] +fn ref_of_ref() { + check_closure_captures( + r#" +//- minicore:copy +fn main() { + let mut a = &false; + let closure = || { let b = &a; }; + let closure = || { let b = &mut a; }; + let a = &mut false; + let closure = || { let b = &a; }; + let closure = || { let b = &mut a; }; +} +"#, + expect![[r#" + 54..72;20..25;67..69 ByRef(Shared) a &'? &'? bool + 92..114;20..25;105..111 ByRef(Mut { kind: Default }) a &'? mut &'? bool + 158..176;124..125;171..173 ByRef(Shared) a &'? &'? mut bool + 196..218;124..125;209..215 ByRef(Mut { kind: Default }) a &'? mut &'? mut bool"#]], + ); +} + +#[test] +fn multiple_capture_usages() { + check_closure_captures( + r#" +//- minicore:copy +struct A { a: i32, b: bool } +fn main() { + let mut a = A { a: 123, b: false }; + let closure = |$0| { + let b = a.b; + a = A { a: 456, b: true }; + }; + closure(); +} +"#, + expect!["99..165;49..54;120..121,133..134 ByRef(Mut { kind: Default }) a &'? mut A"], ); } |