mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 14:51:48 +00:00
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.
This commit is contained in:
commit
07a66c475c
6 changed files with 1511 additions and 13 deletions
|
@ -19,7 +19,8 @@ use hir_expand::name::Name;
|
|||
use intern::sym;
|
||||
use rustc_hash::FxHashMap;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use stdx::never;
|
||||
use stdx::{format_to, never};
|
||||
use syntax::utils::is_raw_identifier;
|
||||
|
||||
use crate::{
|
||||
db::{HirDatabase, InternedClosure},
|
||||
|
@ -251,6 +252,11 @@ impl CapturedItem {
|
|||
self.place.local
|
||||
}
|
||||
|
||||
/// Returns whether this place has any field (aka. non-deref) projections.
|
||||
pub fn has_field_projections(&self) -> bool {
|
||||
self.place.projections.iter().any(|it| !matches!(it, ProjectionElem::Deref))
|
||||
}
|
||||
|
||||
pub fn ty(&self, subst: &Substitution) -> Ty {
|
||||
self.ty.clone().substitute(Interner, utils::ClosureSubst(subst).parent_subst())
|
||||
}
|
||||
|
@ -263,6 +269,97 @@ impl CapturedItem {
|
|||
self.span_stacks.iter().map(|stack| *stack.last().expect("empty span stack")).collect()
|
||||
}
|
||||
|
||||
/// Converts the place to a name that can be inserted into source code.
|
||||
pub fn place_to_name(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
|
||||
let body = db.body(owner);
|
||||
let mut result = body[self.place.local].name.unescaped().display(db.upcast()).to_string();
|
||||
for proj in &self.place.projections {
|
||||
match proj {
|
||||
ProjectionElem::Deref => {}
|
||||
ProjectionElem::Field(Either::Left(f)) => {
|
||||
match &*f.parent.variant_data(db.upcast()) {
|
||||
VariantData::Record(fields) => {
|
||||
result.push('_');
|
||||
result.push_str(fields[f.local_id].name.as_str())
|
||||
}
|
||||
VariantData::Tuple(fields) => {
|
||||
let index = fields.iter().position(|it| it.0 == f.local_id);
|
||||
if let Some(index) = index {
|
||||
format_to!(result, "_{index}");
|
||||
}
|
||||
}
|
||||
VariantData::Unit => {}
|
||||
}
|
||||
}
|
||||
ProjectionElem::Field(Either::Right(f)) => format_to!(result, "_{}", f.index),
|
||||
&ProjectionElem::ClosureField(field) => format_to!(result, "_{field}"),
|
||||
ProjectionElem::Index(_)
|
||||
| ProjectionElem::ConstantIndex { .. }
|
||||
| ProjectionElem::Subslice { .. }
|
||||
| ProjectionElem::OpaqueCast(_) => {
|
||||
never!("Not happen in closure capture");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_raw_identifier(&result, db.crate_graph()[owner.module(db.upcast()).krate()].edition) {
|
||||
result.insert_str(0, "r#");
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn display_place_source_code(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
|
||||
let body = db.body(owner);
|
||||
let krate = owner.krate(db.upcast());
|
||||
let edition = db.crate_graph()[krate].edition;
|
||||
let mut result = body[self.place.local].name.display(db.upcast(), edition).to_string();
|
||||
for proj in &self.place.projections {
|
||||
match proj {
|
||||
// In source code autoderef kicks in.
|
||||
ProjectionElem::Deref => {}
|
||||
ProjectionElem::Field(Either::Left(f)) => {
|
||||
let variant_data = f.parent.variant_data(db.upcast());
|
||||
match &*variant_data {
|
||||
VariantData::Record(fields) => format_to!(
|
||||
result,
|
||||
".{}",
|
||||
fields[f.local_id].name.display(db.upcast(), edition)
|
||||
),
|
||||
VariantData::Tuple(fields) => format_to!(
|
||||
result,
|
||||
".{}",
|
||||
fields.iter().position(|it| it.0 == f.local_id).unwrap_or_default()
|
||||
),
|
||||
VariantData::Unit => {}
|
||||
}
|
||||
}
|
||||
ProjectionElem::Field(Either::Right(f)) => {
|
||||
let field = f.index;
|
||||
format_to!(result, ".{field}");
|
||||
}
|
||||
&ProjectionElem::ClosureField(field) => {
|
||||
format_to!(result, ".{field}");
|
||||
}
|
||||
ProjectionElem::Index(_)
|
||||
| ProjectionElem::ConstantIndex { .. }
|
||||
| ProjectionElem::Subslice { .. }
|
||||
| ProjectionElem::OpaqueCast(_) => {
|
||||
never!("Not happen in closure capture");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
let final_derefs_count = self
|
||||
.place
|
||||
.projections
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|proj| matches!(proj, ProjectionElem::Deref))
|
||||
.count();
|
||||
result.insert_str(0, &"*".repeat(final_derefs_count));
|
||||
result
|
||||
}
|
||||
|
||||
pub fn display_place(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
|
||||
let body = db.body(owner);
|
||||
let krate = owner.krate(db.upcast());
|
||||
|
@ -451,14 +548,6 @@ impl InferenceContext<'_> {
|
|||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -467,7 +556,7 @@ impl InferenceContext<'_> {
|
|||
let mut actual_truncate_to = 0;
|
||||
for &span in &*span_stack {
|
||||
actual_truncate_to += 1;
|
||||
if !self.is_ref_span(span) {
|
||||
if !span.is_ref_span(self.body) {
|
||||
remained -= 1;
|
||||
if remained == 0 {
|
||||
break;
|
||||
|
@ -475,7 +564,7 @@ impl InferenceContext<'_> {
|
|||
}
|
||||
}
|
||||
if actual_truncate_to < span_stack.len()
|
||||
&& self.is_ref_span(span_stack[actual_truncate_to])
|
||||
&& span_stack[actual_truncate_to].is_ref_span(self.body)
|
||||
{
|
||||
// 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;
|
||||
|
@ -1147,7 +1236,7 @@ impl InferenceContext<'_> {
|
|||
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]) {
|
||||
if span_stack[span_stack.len() - 1].is_ref_span(self.body) {
|
||||
span_stack.truncate(span_stack.len() - 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ use base_db::CrateId;
|
|||
use chalk_ir::Mutability;
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
hir::{BindingId, Expr, ExprId, Ordering, PatId},
|
||||
body::Body,
|
||||
hir::{BindingAnnotation, BindingId, Expr, ExprId, Ordering, PatId},
|
||||
DefWithBodyId, FieldId, StaticId, TupleFieldId, UnionId, VariantId,
|
||||
};
|
||||
use la_arena::{Arena, ArenaMap, Idx, RawIdx};
|
||||
|
@ -1174,6 +1175,20 @@ pub enum MirSpan {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
impl MirSpan {
|
||||
pub fn is_ref_span(&self, body: &Body) -> bool {
|
||||
match *self {
|
||||
MirSpan::ExprId(expr) => matches!(body[expr], Expr::Ref { .. }),
|
||||
// FIXME: Figure out if this is correct wrt. match ergonomics.
|
||||
MirSpan::BindingId(binding) => matches!(
|
||||
body.bindings[binding].mode,
|
||||
BindingAnnotation::Ref | BindingAnnotation::RefMut
|
||||
),
|
||||
MirSpan::PatId(_) | MirSpan::SelfParam | MirSpan::Unknown => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from!(ExprId, PatId for MirSpan);
|
||||
|
||||
impl From<&ExprId> for MirSpan {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue