Preserve all spans for closure captures, not just one

This is important for the "convert closure to fn" assist, as it needs to find and modify the places the captures are used.
This commit is contained in:
Chayim Refael Friedman 2024-08-24 22:46:45 +03:00
parent da931e7398
commit 506b9663bf
4 changed files with 457 additions and 180 deletions

View file

@ -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(),

View file

@ -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);
}
}
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())
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();
}
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::Or(pats) => {
for pat in pats.iter() {
self.consume_with_pat(place.clone(), *pat);
}
}
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,

View file

@ -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)),

View file

@ -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"],
);
}