Skip to content

Commit b195ff3

Browse files
committed
Auto merge of #17940 - ChayimFriedman2:closure-to-fn, r=Veykril
feat: Create an assist to convert closure to freestanding fn The assist converts all captures to parameters. Closes #17920. This was more work than I though, since it has to handle a bunch of edge cases... Based on #17941. Needs to merge it first.
2 parents f05888d + 34e50f5 commit b195ff3

File tree

6 files changed

+1511
-13
lines changed

6 files changed

+1511
-13
lines changed

src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs

+101-12
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ use hir_expand::name::Name;
1919
use intern::sym;
2020
use rustc_hash::FxHashMap;
2121
use smallvec::{smallvec, SmallVec};
22-
use stdx::never;
22+
use stdx::{format_to, never};
23+
use syntax::utils::is_raw_identifier;
2324

2425
use crate::{
2526
db::{HirDatabase, InternedClosure},
@@ -251,6 +252,11 @@ impl CapturedItem {
251252
self.place.local
252253
}
253254

255+
/// Returns whether this place has any field (aka. non-deref) projections.
256+
pub fn has_field_projections(&self) -> bool {
257+
self.place.projections.iter().any(|it| !matches!(it, ProjectionElem::Deref))
258+
}
259+
254260
pub fn ty(&self, subst: &Substitution) -> Ty {
255261
self.ty.clone().substitute(Interner, utils::ClosureSubst(subst).parent_subst())
256262
}
@@ -263,6 +269,97 @@ impl CapturedItem {
263269
self.span_stacks.iter().map(|stack| *stack.last().expect("empty span stack")).collect()
264270
}
265271

272+
/// Converts the place to a name that can be inserted into source code.
273+
pub fn place_to_name(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
274+
let body = db.body(owner);
275+
let mut result = body[self.place.local].name.unescaped().display(db.upcast()).to_string();
276+
for proj in &self.place.projections {
277+
match proj {
278+
ProjectionElem::Deref => {}
279+
ProjectionElem::Field(Either::Left(f)) => {
280+
match &*f.parent.variant_data(db.upcast()) {
281+
VariantData::Record(fields) => {
282+
result.push('_');
283+
result.push_str(fields[f.local_id].name.as_str())
284+
}
285+
VariantData::Tuple(fields) => {
286+
let index = fields.iter().position(|it| it.0 == f.local_id);
287+
if let Some(index) = index {
288+
format_to!(result, "_{index}");
289+
}
290+
}
291+
VariantData::Unit => {}
292+
}
293+
}
294+
ProjectionElem::Field(Either::Right(f)) => format_to!(result, "_{}", f.index),
295+
&ProjectionElem::ClosureField(field) => format_to!(result, "_{field}"),
296+
ProjectionElem::Index(_)
297+
| ProjectionElem::ConstantIndex { .. }
298+
| ProjectionElem::Subslice { .. }
299+
| ProjectionElem::OpaqueCast(_) => {
300+
never!("Not happen in closure capture");
301+
continue;
302+
}
303+
}
304+
}
305+
if is_raw_identifier(&result, db.crate_graph()[owner.module(db.upcast()).krate()].edition) {
306+
result.insert_str(0, "r#");
307+
}
308+
result
309+
}
310+
311+
pub fn display_place_source_code(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
312+
let body = db.body(owner);
313+
let krate = owner.krate(db.upcast());
314+
let edition = db.crate_graph()[krate].edition;
315+
let mut result = body[self.place.local].name.display(db.upcast(), edition).to_string();
316+
for proj in &self.place.projections {
317+
match proj {
318+
// In source code autoderef kicks in.
319+
ProjectionElem::Deref => {}
320+
ProjectionElem::Field(Either::Left(f)) => {
321+
let variant_data = f.parent.variant_data(db.upcast());
322+
match &*variant_data {
323+
VariantData::Record(fields) => format_to!(
324+
result,
325+
".{}",
326+
fields[f.local_id].name.display(db.upcast(), edition)
327+
),
328+
VariantData::Tuple(fields) => format_to!(
329+
result,
330+
".{}",
331+
fields.iter().position(|it| it.0 == f.local_id).unwrap_or_default()
332+
),
333+
VariantData::Unit => {}
334+
}
335+
}
336+
ProjectionElem::Field(Either::Right(f)) => {
337+
let field = f.index;
338+
format_to!(result, ".{field}");
339+
}
340+
&ProjectionElem::ClosureField(field) => {
341+
format_to!(result, ".{field}");
342+
}
343+
ProjectionElem::Index(_)
344+
| ProjectionElem::ConstantIndex { .. }
345+
| ProjectionElem::Subslice { .. }
346+
| ProjectionElem::OpaqueCast(_) => {
347+
never!("Not happen in closure capture");
348+
continue;
349+
}
350+
}
351+
}
352+
let final_derefs_count = self
353+
.place
354+
.projections
355+
.iter()
356+
.rev()
357+
.take_while(|proj| matches!(proj, ProjectionElem::Deref))
358+
.count();
359+
result.insert_str(0, &"*".repeat(final_derefs_count));
360+
result
361+
}
362+
266363
pub fn display_place(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
267364
let body = db.body(owner);
268365
let krate = owner.krate(db.upcast());
@@ -451,14 +548,6 @@ impl InferenceContext<'_> {
451548
});
452549
}
453550

454-
fn is_ref_span(&self, span: MirSpan) -> bool {
455-
match span {
456-
MirSpan::ExprId(expr) => matches!(self.body[expr], Expr::Ref { .. }),
457-
MirSpan::BindingId(_) => true,
458-
MirSpan::PatId(_) | MirSpan::SelfParam | MirSpan::Unknown => false,
459-
}
460-
}
461-
462551
fn truncate_capture_spans(&self, capture: &mut CapturedItemWithoutTy, mut truncate_to: usize) {
463552
// The first span is the identifier, and it must always remain.
464553
truncate_to += 1;
@@ -467,15 +556,15 @@ impl InferenceContext<'_> {
467556
let mut actual_truncate_to = 0;
468557
for &span in &*span_stack {
469558
actual_truncate_to += 1;
470-
if !self.is_ref_span(span) {
559+
if !span.is_ref_span(self.body) {
471560
remained -= 1;
472561
if remained == 0 {
473562
break;
474563
}
475564
}
476565
}
477566
if actual_truncate_to < span_stack.len()
478-
&& self.is_ref_span(span_stack[actual_truncate_to])
567+
&& span_stack[actual_truncate_to].is_ref_span(self.body)
479568
{
480569
// Include the ref operator if there is one, we will fix it later (in `strip_captures_ref_span()`) if it's incorrect.
481570
actual_truncate_to += 1;
@@ -1147,7 +1236,7 @@ impl InferenceContext<'_> {
11471236
for capture in &mut captures {
11481237
if matches!(capture.kind, CaptureKind::ByValue) {
11491238
for span_stack in &mut capture.span_stacks {
1150-
if self.is_ref_span(span_stack[span_stack.len() - 1]) {
1239+
if span_stack[span_stack.len() - 1].is_ref_span(self.body) {
11511240
span_stack.truncate(span_stack.len() - 1);
11521241
}
11531242
}

src/tools/rust-analyzer/crates/hir-ty/src/mir.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use base_db::CrateId;
1616
use chalk_ir::Mutability;
1717
use either::Either;
1818
use hir_def::{
19-
hir::{BindingId, Expr, ExprId, Ordering, PatId},
19+
body::Body,
20+
hir::{BindingAnnotation, BindingId, Expr, ExprId, Ordering, PatId},
2021
DefWithBodyId, FieldId, StaticId, TupleFieldId, UnionId, VariantId,
2122
};
2223
use la_arena::{Arena, ArenaMap, Idx, RawIdx};
@@ -1174,6 +1175,20 @@ pub enum MirSpan {
11741175
Unknown,
11751176
}
11761177

1178+
impl MirSpan {
1179+
pub fn is_ref_span(&self, body: &Body) -> bool {
1180+
match *self {
1181+
MirSpan::ExprId(expr) => matches!(body[expr], Expr::Ref { .. }),
1182+
// FIXME: Figure out if this is correct wrt. match ergonomics.
1183+
MirSpan::BindingId(binding) => matches!(
1184+
body.bindings[binding].mode,
1185+
BindingAnnotation::Ref | BindingAnnotation::RefMut
1186+
),
1187+
MirSpan::PatId(_) | MirSpan::SelfParam | MirSpan::Unknown => false,
1188+
}
1189+
}
1190+
}
1191+
11771192
impl_from!(ExprId, PatId for MirSpan);
11781193

11791194
impl From<&ExprId> for MirSpan {

src/tools/rust-analyzer/crates/hir/src/lib.rs

+87
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ use hir_ty::{
7878
use itertools::Itertools;
7979
use nameres::diagnostics::DefDiagnosticKind;
8080
use rustc_hash::FxHashSet;
81+
use smallvec::SmallVec;
8182
use span::{Edition, EditionedFileId, FileId, MacroCallId, SyntaxContextId};
8283
use stdx::{impl_from, never};
8384
use syntax::{
@@ -4113,6 +4114,15 @@ impl ClosureCapture {
41134114
Local { parent: self.owner, binding_id: self.capture.local() }
41144115
}
41154116

4117+
/// Returns whether this place has any field (aka. non-deref) projections.
4118+
pub fn has_field_projections(&self) -> bool {
4119+
self.capture.has_field_projections()
4120+
}
4121+
4122+
pub fn usages(&self) -> CaptureUsages {
4123+
CaptureUsages { parent: self.owner, spans: self.capture.spans() }
4124+
}
4125+
41164126
pub fn kind(&self) -> CaptureKind {
41174127
match self.capture.kind() {
41184128
hir_ty::CaptureKind::ByRef(
@@ -4128,6 +4138,15 @@ impl ClosureCapture {
41284138
}
41294139
}
41304140

4141+
/// Converts the place to a name that can be inserted into source code.
4142+
pub fn place_to_name(&self, db: &dyn HirDatabase) -> String {
4143+
self.capture.place_to_name(self.owner, db)
4144+
}
4145+
4146+
pub fn display_place_source_code(&self, db: &dyn HirDatabase) -> String {
4147+
self.capture.display_place_source_code(self.owner, db)
4148+
}
4149+
41314150
pub fn display_place(&self, db: &dyn HirDatabase) -> String {
41324151
self.capture.display_place(self.owner, db)
41334152
}
@@ -4141,6 +4160,74 @@ pub enum CaptureKind {
41414160
Move,
41424161
}
41434162

4163+
#[derive(Debug, Clone)]
4164+
pub struct CaptureUsages {
4165+
parent: DefWithBodyId,
4166+
spans: SmallVec<[mir::MirSpan; 3]>,
4167+
}
4168+
4169+
impl CaptureUsages {
4170+
pub fn sources(&self, db: &dyn HirDatabase) -> Vec<CaptureUsageSource> {
4171+
let (body, source_map) = db.body_with_source_map(self.parent);
4172+
let mut result = Vec::with_capacity(self.spans.len());
4173+
for &span in self.spans.iter() {
4174+
let is_ref = span.is_ref_span(&body);
4175+
match span {
4176+
mir::MirSpan::ExprId(expr) => {
4177+
if let Ok(expr) = source_map.expr_syntax(expr) {
4178+
result.push(CaptureUsageSource {
4179+
is_ref,
4180+
source: expr.map(AstPtr::wrap_left),
4181+
})
4182+
}
4183+
}
4184+
mir::MirSpan::PatId(pat) => {
4185+
if let Ok(pat) = source_map.pat_syntax(pat) {
4186+
result.push(CaptureUsageSource {
4187+
is_ref,
4188+
source: pat.map(AstPtr::wrap_right),
4189+
});
4190+
}
4191+
}
4192+
mir::MirSpan::BindingId(binding) => result.extend(
4193+
source_map
4194+
.patterns_for_binding(binding)
4195+
.iter()
4196+
.filter_map(|&pat| source_map.pat_syntax(pat).ok())
4197+
.map(|pat| CaptureUsageSource {
4198+
is_ref,
4199+
source: pat.map(AstPtr::wrap_right),
4200+
}),
4201+
),
4202+
mir::MirSpan::SelfParam | mir::MirSpan::Unknown => {
4203+
unreachable!("invalid capture usage span")
4204+
}
4205+
}
4206+
}
4207+
result
4208+
}
4209+
}
4210+
4211+
#[derive(Debug)]
4212+
pub struct CaptureUsageSource {
4213+
is_ref: bool,
4214+
source: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
4215+
}
4216+
4217+
impl CaptureUsageSource {
4218+
pub fn source(&self) -> AstPtr<Either<ast::Expr, ast::Pat>> {
4219+
self.source.value
4220+
}
4221+
4222+
pub fn file_id(&self) -> HirFileId {
4223+
self.source.file_id
4224+
}
4225+
4226+
pub fn is_ref(&self) -> bool {
4227+
self.is_ref
4228+
}
4229+
}
4230+
41444231
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
41454232
pub struct Type {
41464233
env: Arc<TraitEnvironment>,

0 commit comments

Comments
 (0)