Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-ty/src/infer.rs8
-rw-r--r--crates/hir-ty/src/infer/closure.rs385
-rw-r--r--crates/hir-ty/src/mir/lower.rs11
-rw-r--r--crates/hir-ty/src/tests/closure_captures.rs231
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"],
);
}