Skip to content

Commit 7ff7c2b

Browse files
Create an assist to convert closure to freestanding fn
The assist converts all captures to parameters.
1 parent 52462ad commit 7ff7c2b

File tree

6 files changed

+1503
-12
lines changed

6 files changed

+1503
-12
lines changed

crates/hir-ty/src/infer/closure.rs

Lines changed: 106 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use intern::sym;
2020
use rustc_hash::FxHashMap;
2121
use smallvec::{smallvec, SmallVec};
2222
use stdx::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,103 @@ 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+
use std::fmt::Write;
275+
276+
let body = db.body(owner);
277+
let mut result = body[self.place.local].name.unescaped().display(db.upcast()).to_string();
278+
for proj in &self.place.projections {
279+
match proj {
280+
ProjectionElem::Deref => {}
281+
ProjectionElem::Field(Either::Left(f)) => {
282+
match &*f.parent.variant_data(db.upcast()) {
283+
VariantData::Record(fields) => {
284+
result.push('_');
285+
result.push_str(fields[f.local_id].name.as_str())
286+
}
287+
VariantData::Tuple(fields) => {
288+
let index = fields.iter().position(|it| it.0 == f.local_id);
289+
if let Some(index) = index {
290+
write!(result, "_{index}").unwrap();
291+
}
292+
}
293+
VariantData::Unit => {}
294+
}
295+
}
296+
ProjectionElem::Field(Either::Right(f)) => write!(result, "_{}", f.index).unwrap(),
297+
&ProjectionElem::ClosureField(field) => write!(result, "_{field}").unwrap(),
298+
ProjectionElem::Index(_)
299+
| ProjectionElem::ConstantIndex { .. }
300+
| ProjectionElem::Subslice { .. }
301+
| ProjectionElem::OpaqueCast(_) => {
302+
never!("Not happen in closure capture");
303+
continue;
304+
}
305+
}
306+
}
307+
if is_raw_identifier(&result, db.crate_graph()[owner.module(db.upcast()).krate()].edition) {
308+
result.insert_str(0, "r#");
309+
}
310+
result
311+
}
312+
313+
pub fn display_place_source_code(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
314+
use std::fmt::Write;
315+
316+
let body = db.body(owner);
317+
let krate = owner.krate(db.upcast());
318+
let edition = db.crate_graph()[krate].edition;
319+
let mut result = body[self.place.local].name.display(db.upcast(), edition).to_string();
320+
for proj in &self.place.projections {
321+
match proj {
322+
// In source code autoderef kicks in.
323+
ProjectionElem::Deref => {}
324+
ProjectionElem::Field(Either::Left(f)) => {
325+
let variant_data = f.parent.variant_data(db.upcast());
326+
match &*variant_data {
327+
VariantData::Record(fields) => write!(
328+
result,
329+
".{}",
330+
fields[f.local_id].name.display(db.upcast(), edition)
331+
)
332+
.unwrap(),
333+
VariantData::Tuple(fields) => write!(
334+
result,
335+
".{}",
336+
fields.iter().position(|it| it.0 == f.local_id).unwrap_or_default()
337+
)
338+
.unwrap(),
339+
VariantData::Unit => {}
340+
}
341+
}
342+
ProjectionElem::Field(Either::Right(f)) => {
343+
let field = f.index;
344+
write!(result, ".{field}").unwrap();
345+
}
346+
&ProjectionElem::ClosureField(field) => {
347+
write!(result, ".{field}").unwrap();
348+
}
349+
ProjectionElem::Index(_)
350+
| ProjectionElem::ConstantIndex { .. }
351+
| ProjectionElem::Subslice { .. }
352+
| ProjectionElem::OpaqueCast(_) => {
353+
never!("Not happen in closure capture");
354+
continue;
355+
}
356+
}
357+
}
358+
let final_derefs_count = self
359+
.place
360+
.projections
361+
.iter()
362+
.rev()
363+
.take_while(|proj| matches!(proj, ProjectionElem::Deref))
364+
.count();
365+
result.insert_str(0, &"*".repeat(final_derefs_count));
366+
result
367+
}
368+
266369
pub fn display_place(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
267370
let body = db.body(owner);
268371
let krate = owner.krate(db.upcast());
@@ -451,14 +554,6 @@ impl InferenceContext<'_> {
451554
});
452555
}
453556

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-
462557
fn truncate_capture_spans(&self, capture: &mut CapturedItemWithoutTy, mut truncate_to: usize) {
463558
// The first span is the identifier, and it must always remain.
464559
truncate_to += 1;
@@ -467,15 +562,15 @@ impl InferenceContext<'_> {
467562
let mut actual_truncate_to = 0;
468563
for &span in &*span_stack {
469564
actual_truncate_to += 1;
470-
if !self.is_ref_span(span) {
565+
if !span.is_ref_span(self.body) {
471566
remained -= 1;
472567
if remained == 0 {
473568
break;
474569
}
475570
}
476571
}
477572
if actual_truncate_to < span_stack.len()
478-
&& self.is_ref_span(span_stack[actual_truncate_to])
573+
&& span_stack[actual_truncate_to].is_ref_span(self.body)
479574
{
480575
// Include the ref operator if there is one, we will fix it later (in `strip_captures_ref_span()`) if it's incorrect.
481576
actual_truncate_to += 1;
@@ -1147,7 +1242,7 @@ impl InferenceContext<'_> {
11471242
for capture in &mut captures {
11481243
if matches!(capture.kind, CaptureKind::ByValue) {
11491244
for span_stack in &mut capture.span_stacks {
1150-
if self.is_ref_span(span_stack[span_stack.len() - 1]) {
1245+
if span_stack[span_stack.len() - 1].is_ref_span(self.body) {
11511246
span_stack.truncate(span_stack.len() - 1);
11521247
}
11531248
}

crates/hir-ty/src/mir.rs

Lines changed: 16 additions & 1 deletion
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 {

crates/hir/src/lib.rs

Lines changed: 87 additions & 0 deletions
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)