mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-31 23:38:45 +00:00
Handle bindings to refs
This commit is contained in:
parent
ed230048dc
commit
b203a07d92
4 changed files with 212 additions and 142 deletions
|
@ -10,7 +10,10 @@ use itertools::Itertools;
|
||||||
use syntax::{ast, ted, AstNode, SmolStr};
|
use syntax::{ast, ted, AstNode, SmolStr};
|
||||||
use text_edit::TextRange;
|
use text_edit::TextRange;
|
||||||
|
|
||||||
use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder};
|
use crate::{
|
||||||
|
assist_context::{AssistContext, Assists, SourceChangeBuilder},
|
||||||
|
utils::ref_field_expr::determine_ref_and_parens,
|
||||||
|
};
|
||||||
|
|
||||||
// Assist: destructure_struct_binding
|
// Assist: destructure_struct_binding
|
||||||
//
|
//
|
||||||
|
@ -58,11 +61,12 @@ fn destructure_struct_binding_impl(
|
||||||
builder: &mut SourceChangeBuilder,
|
builder: &mut SourceChangeBuilder,
|
||||||
data: &StructEditData,
|
data: &StructEditData,
|
||||||
) {
|
) {
|
||||||
let assignment_edit = build_assignment_edit(ctx, builder, data);
|
let field_names = generate_field_names(ctx, data);
|
||||||
let usage_edits = build_usage_edits(ctx, builder, data, &assignment_edit.field_name_map);
|
let assignment_edit = build_assignment_edit(ctx, builder, data, &field_names);
|
||||||
|
let usage_edits = build_usage_edits(ctx, builder, data, &field_names.into_iter().collect());
|
||||||
|
|
||||||
assignment_edit.apply();
|
assignment_edit.apply();
|
||||||
for edit in usage_edits.unwrap_or_default() {
|
for edit in usage_edits.into_iter().flatten() {
|
||||||
edit.apply(builder);
|
edit.apply(builder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,14 +78,16 @@ struct StructEditData {
|
||||||
visible_fields: Vec<hir::Field>,
|
visible_fields: Vec<hir::Field>,
|
||||||
usages: Option<UsageSearchResult>,
|
usages: Option<UsageSearchResult>,
|
||||||
names_in_scope: FxHashSet<SmolStr>, // TODO currently always empty
|
names_in_scope: FxHashSet<SmolStr>, // TODO currently always empty
|
||||||
add_rest: bool,
|
has_private_members: bool,
|
||||||
is_nested: bool,
|
is_nested: bool,
|
||||||
|
is_ref: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<StructEditData> {
|
fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<StructEditData> {
|
||||||
let ty = ctx.sema.type_of_binding_in_pat(&ident_pat)?.strip_references().as_adt()?;
|
let ty = ctx.sema.type_of_binding_in_pat(&ident_pat)?;
|
||||||
|
let is_ref = ty.is_reference();
|
||||||
|
|
||||||
let hir::Adt::Struct(struct_type) = ty else { return None };
|
let hir::Adt::Struct(struct_type) = ty.strip_references().as_adt()? else { return None };
|
||||||
|
|
||||||
let module = ctx.sema.scope(ident_pat.syntax())?.module();
|
let module = ctx.sema.scope(ident_pat.syntax())?.module();
|
||||||
let struct_def = hir::ModuleDef::from(struct_type);
|
let struct_def = hir::ModuleDef::from(struct_type);
|
||||||
|
@ -97,8 +103,9 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
|
||||||
let visible_fields =
|
let visible_fields =
|
||||||
fields.into_iter().filter(|field| field.is_visible_from(ctx.db(), module)).collect_vec();
|
fields.into_iter().filter(|field| field.is_visible_from(ctx.db(), module)).collect_vec();
|
||||||
|
|
||||||
let add_rest = (is_non_exhaustive && is_foreign_crate) || visible_fields.len() < n_fields;
|
let has_private_members =
|
||||||
if !matches!(kind, hir::StructKind::Record) && add_rest {
|
(is_non_exhaustive && is_foreign_crate) || visible_fields.len() < n_fields;
|
||||||
|
if !matches!(kind, hir::StructKind::Record) && has_private_members {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,17 +130,19 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
|
||||||
kind,
|
kind,
|
||||||
struct_def_path,
|
struct_def_path,
|
||||||
usages,
|
usages,
|
||||||
add_rest,
|
has_private_members,
|
||||||
visible_fields,
|
visible_fields,
|
||||||
names_in_scope: FxHashSet::default(), // TODO
|
names_in_scope: FxHashSet::default(), // TODO
|
||||||
is_nested,
|
is_nested,
|
||||||
|
is_ref,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_assignment_edit(
|
fn build_assignment_edit(
|
||||||
ctx: &AssistContext<'_>,
|
_ctx: &AssistContext<'_>,
|
||||||
builder: &mut SourceChangeBuilder,
|
builder: &mut SourceChangeBuilder,
|
||||||
data: &StructEditData,
|
data: &StructEditData,
|
||||||
|
field_names: &[(SmolStr, SmolStr)],
|
||||||
) -> AssignmentEdit {
|
) -> AssignmentEdit {
|
||||||
let ident_pat = builder.make_mut(data.ident_pat.clone());
|
let ident_pat = builder.make_mut(data.ident_pat.clone());
|
||||||
|
|
||||||
|
@ -141,8 +150,6 @@ fn build_assignment_edit(
|
||||||
let is_ref = ident_pat.ref_token().is_some();
|
let is_ref = ident_pat.ref_token().is_some();
|
||||||
let is_mut = ident_pat.mut_token().is_some();
|
let is_mut = ident_pat.mut_token().is_some();
|
||||||
|
|
||||||
let field_names = generate_field_names(ctx, data);
|
|
||||||
|
|
||||||
let new_pat = match data.kind {
|
let new_pat = match data.kind {
|
||||||
hir::StructKind::Tuple => {
|
hir::StructKind::Tuple => {
|
||||||
let ident_pats = field_names.iter().map(|(_, new_name)| {
|
let ident_pats = field_names.iter().map(|(_, new_name)| {
|
||||||
|
@ -169,7 +176,7 @@ fn build_assignment_edit(
|
||||||
|
|
||||||
let field_list = ast::make::record_pat_field_list(
|
let field_list = ast::make::record_pat_field_list(
|
||||||
fields,
|
fields,
|
||||||
data.add_rest.then_some(ast::make::rest_pat()),
|
data.has_private_members.then_some(ast::make::rest_pat()),
|
||||||
);
|
);
|
||||||
ast::Pat::RecordPat(ast::make::record_pat_with_fields(struct_path, field_list))
|
ast::Pat::RecordPat(ast::make::record_pat_with_fields(struct_path, field_list))
|
||||||
}
|
}
|
||||||
|
@ -185,7 +192,7 @@ fn build_assignment_edit(
|
||||||
NewPat::Pat(new_pat.clone_for_update())
|
NewPat::Pat(new_pat.clone_for_update())
|
||||||
};
|
};
|
||||||
|
|
||||||
AssignmentEdit { ident_pat, new_pat, field_name_map: field_names.into_iter().collect() }
|
AssignmentEdit { ident_pat, new_pat }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(SmolStr, SmolStr)> {
|
fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(SmolStr, SmolStr)> {
|
||||||
|
@ -195,8 +202,8 @@ fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, _)| {
|
.map(|(index, _)| {
|
||||||
let new_name = format!("_{}", index);
|
let new_name = new_field_name((format!("_{}", index)).into(), &data.names_in_scope);
|
||||||
(index.to_string().into(), new_name.into())
|
(index.to_string().into(), new_name)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
hir::StructKind::Record => data
|
hir::StructKind::Record => data
|
||||||
|
@ -204,8 +211,8 @@ fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
let field_name = field.name(ctx.db()).to_smol_str();
|
let field_name = field.name(ctx.db()).to_smol_str();
|
||||||
let new_field_name = new_field_name(field_name.clone(), &data.names_in_scope);
|
let new_name = new_field_name(field_name.clone(), &data.names_in_scope);
|
||||||
(field_name, new_field_name)
|
(field_name, new_name)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
hir::StructKind::Unit => Vec::new(),
|
hir::StructKind::Unit => Vec::new(),
|
||||||
|
@ -225,7 +232,6 @@ fn new_field_name(base_name: SmolStr, names_in_scope: &FxHashSet<SmolStr>) -> Sm
|
||||||
struct AssignmentEdit {
|
struct AssignmentEdit {
|
||||||
ident_pat: ast::IdentPat,
|
ident_pat: ast::IdentPat,
|
||||||
new_pat: NewPat,
|
new_pat: NewPat,
|
||||||
field_name_map: FxHashMap<SmolStr, SmolStr>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NewPat {
|
enum NewPat {
|
||||||
|
@ -260,14 +266,16 @@ fn build_usage_edits(
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|(file_id, refs)| (*file_id == ctx.file_id()).then_some(refs))?
|
.find_map(|(file_id, refs)| (*file_id == ctx.file_id()).then_some(refs))?
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|r| build_usage_edit(builder, r, field_names))
|
.filter_map(|r| build_usage_edit(ctx, builder, data, r, field_names))
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
||||||
Some(edits)
|
Some(edits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_usage_edit(
|
fn build_usage_edit(
|
||||||
|
ctx: &AssistContext<'_>,
|
||||||
builder: &mut SourceChangeBuilder,
|
builder: &mut SourceChangeBuilder,
|
||||||
|
data: &StructEditData,
|
||||||
usage: &FileReference,
|
usage: &FileReference,
|
||||||
field_names: &FxHashMap<SmolStr, SmolStr>,
|
field_names: &FxHashMap<SmolStr, SmolStr>,
|
||||||
) -> Option<StructUsageEdit> {
|
) -> Option<StructUsageEdit> {
|
||||||
|
@ -275,11 +283,20 @@ fn build_usage_edit(
|
||||||
Some(field_expr) => Some({
|
Some(field_expr) => Some({
|
||||||
let field_name: SmolStr = field_expr.name_ref()?.to_string().into();
|
let field_name: SmolStr = field_expr.name_ref()?.to_string().into();
|
||||||
let new_field_name = field_names.get(&field_name)?;
|
let new_field_name = field_names.get(&field_name)?;
|
||||||
|
let new_expr = ast::make::expr_path(ast::make::ext::ident_path(new_field_name));
|
||||||
|
|
||||||
let expr = builder.make_mut(field_expr).into();
|
if data.is_ref {
|
||||||
let new_expr =
|
let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &field_expr);
|
||||||
ast::make::expr_path(ast::make::ext::ident_path(new_field_name)).clone_for_update();
|
StructUsageEdit::IndexField(
|
||||||
StructUsageEdit::IndexField(expr, new_expr)
|
builder.make_mut(replace_expr),
|
||||||
|
ref_data.wrap_expr(new_expr).clone_for_update(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
StructUsageEdit::IndexField(
|
||||||
|
builder.make_mut(field_expr).into(),
|
||||||
|
new_expr.clone_for_update(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
None => Some(StructUsageEdit::Path(usage.range)),
|
None => Some(StructUsageEdit::Path(usage.range)),
|
||||||
}
|
}
|
||||||
|
@ -602,4 +619,33 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mut_ref() {
|
||||||
|
check_assist(
|
||||||
|
destructure_struct_binding,
|
||||||
|
r#"
|
||||||
|
struct Foo {
|
||||||
|
bar: i32,
|
||||||
|
baz: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let $0foo = &mut Foo { bar: 1, baz: 2 };
|
||||||
|
foo.bar = 5;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
struct Foo {
|
||||||
|
bar: i32,
|
||||||
|
baz: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let Foo { bar, baz } = &mut Foo { bar: 1, baz: 2 };
|
||||||
|
*bar = 5;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,15 @@ use ide_db::{
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, make, AstNode, FieldExpr, HasName, IdentPat, MethodCallExpr},
|
ast::{self, make, AstNode, FieldExpr, HasName, IdentPat},
|
||||||
ted, T,
|
ted,
|
||||||
};
|
};
|
||||||
use text_edit::TextRange;
|
use text_edit::TextRange;
|
||||||
|
|
||||||
use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder};
|
use crate::{
|
||||||
|
assist_context::{AssistContext, Assists, SourceChangeBuilder},
|
||||||
|
utils::ref_field_expr::determine_ref_and_parens,
|
||||||
|
};
|
||||||
|
|
||||||
// Assist: destructure_tuple_binding
|
// Assist: destructure_tuple_binding
|
||||||
//
|
//
|
||||||
|
@ -274,7 +277,7 @@ fn edit_tuple_field_usage(
|
||||||
let field_name = make::expr_path(make::ext::ident_path(field_name));
|
let field_name = make::expr_path(make::ext::ident_path(field_name));
|
||||||
|
|
||||||
if data.ref_type.is_some() {
|
if data.ref_type.is_some() {
|
||||||
let (replace_expr, ref_data) = handle_ref_field_usage(ctx, &index.field_expr);
|
let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &index.field_expr);
|
||||||
let replace_expr = builder.make_mut(replace_expr);
|
let replace_expr = builder.make_mut(replace_expr);
|
||||||
EditTupleUsage::ReplaceExpr(replace_expr, ref_data.wrap_expr(field_name))
|
EditTupleUsage::ReplaceExpr(replace_expr, ref_data.wrap_expr(field_name))
|
||||||
} else {
|
} else {
|
||||||
|
@ -361,119 +364,6 @@ fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RefData {
|
|
||||||
needs_deref: bool,
|
|
||||||
needs_parentheses: bool,
|
|
||||||
}
|
|
||||||
impl RefData {
|
|
||||||
fn wrap_expr(&self, mut expr: ast::Expr) -> ast::Expr {
|
|
||||||
if self.needs_deref {
|
|
||||||
expr = make::expr_prefix(T![*], expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.needs_parentheses {
|
|
||||||
expr = make::expr_paren(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> (ast::Expr, RefData) {
|
|
||||||
let s = field_expr.syntax();
|
|
||||||
let mut ref_data = RefData { needs_deref: true, needs_parentheses: true };
|
|
||||||
let mut target_node = field_expr.clone().into();
|
|
||||||
|
|
||||||
let parent = match s.parent().map(ast::Expr::cast) {
|
|
||||||
Some(Some(parent)) => parent,
|
|
||||||
Some(None) => {
|
|
||||||
ref_data.needs_parentheses = false;
|
|
||||||
return (target_node, ref_data);
|
|
||||||
}
|
|
||||||
None => return (target_node, ref_data),
|
|
||||||
};
|
|
||||||
|
|
||||||
match parent {
|
|
||||||
ast::Expr::ParenExpr(it) => {
|
|
||||||
// already parens in place -> don't replace
|
|
||||||
ref_data.needs_parentheses = false;
|
|
||||||
// there might be a ref outside: `&(t.0)` -> can be removed
|
|
||||||
if let Some(it) = it.syntax().parent().and_then(ast::RefExpr::cast) {
|
|
||||||
ref_data.needs_deref = false;
|
|
||||||
target_node = it.into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast::Expr::RefExpr(it) => {
|
|
||||||
// `&*` -> cancel each other out
|
|
||||||
ref_data.needs_deref = false;
|
|
||||||
ref_data.needs_parentheses = false;
|
|
||||||
// might be surrounded by parens -> can be removed too
|
|
||||||
match it.syntax().parent().and_then(ast::ParenExpr::cast) {
|
|
||||||
Some(parent) => target_node = parent.into(),
|
|
||||||
None => target_node = it.into(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// higher precedence than deref `*`
|
|
||||||
// https://doc.rust-lang.org/reference/expressions.html#expression-precedence
|
|
||||||
// -> requires parentheses
|
|
||||||
ast::Expr::PathExpr(_it) => {}
|
|
||||||
ast::Expr::MethodCallExpr(it) => {
|
|
||||||
// `field_expr` is `self_param` (otherwise it would be in `ArgList`)
|
|
||||||
|
|
||||||
// test if there's already auto-ref in place (`value` -> `&value`)
|
|
||||||
// -> no method accepting `self`, but `&self` -> no need for deref
|
|
||||||
//
|
|
||||||
// other combinations (`&value` -> `value`, `&&value` -> `&value`, `&value` -> `&&value`) might or might not be able to auto-ref/deref,
|
|
||||||
// but there might be trait implementations an added `&` might resolve to
|
|
||||||
// -> ONLY handle auto-ref from `value` to `&value`
|
|
||||||
fn is_auto_ref(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> bool {
|
|
||||||
fn impl_(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> Option<bool> {
|
|
||||||
let rec = call_expr.receiver()?;
|
|
||||||
let rec_ty = ctx.sema.type_of_expr(&rec)?.original();
|
|
||||||
// input must be actual value
|
|
||||||
if rec_ty.is_reference() {
|
|
||||||
return Some(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// doesn't resolve trait impl
|
|
||||||
let f = ctx.sema.resolve_method_call(call_expr)?;
|
|
||||||
let self_param = f.self_param(ctx.db())?;
|
|
||||||
// self must be ref
|
|
||||||
match self_param.access(ctx.db()) {
|
|
||||||
hir::Access::Shared | hir::Access::Exclusive => Some(true),
|
|
||||||
hir::Access::Owned => Some(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl_(ctx, call_expr).unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_auto_ref(ctx, &it) {
|
|
||||||
ref_data.needs_deref = false;
|
|
||||||
ref_data.needs_parentheses = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast::Expr::FieldExpr(_it) => {
|
|
||||||
// `t.0.my_field`
|
|
||||||
ref_data.needs_deref = false;
|
|
||||||
ref_data.needs_parentheses = false;
|
|
||||||
}
|
|
||||||
ast::Expr::IndexExpr(_it) => {
|
|
||||||
// `t.0[1]`
|
|
||||||
ref_data.needs_deref = false;
|
|
||||||
ref_data.needs_parentheses = false;
|
|
||||||
}
|
|
||||||
ast::Expr::TryExpr(_it) => {
|
|
||||||
// `t.0?`
|
|
||||||
// requires deref and parens: `(*_0)`
|
|
||||||
}
|
|
||||||
// lower precedence than deref `*` -> no parens
|
|
||||||
_ => {
|
|
||||||
ref_data.needs_parentheses = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(target_node, ref_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -22,6 +22,7 @@ use syntax::{
|
||||||
use crate::assist_context::{AssistContext, SourceChangeBuilder};
|
use crate::assist_context::{AssistContext, SourceChangeBuilder};
|
||||||
|
|
||||||
mod gen_trait_fn_body;
|
mod gen_trait_fn_body;
|
||||||
|
pub(crate) mod ref_field_expr;
|
||||||
pub(crate) mod suggest_name;
|
pub(crate) mod suggest_name;
|
||||||
|
|
||||||
pub(crate) fn unwrap_trivial_block(block_expr: ast::BlockExpr) -> ast::Expr {
|
pub(crate) fn unwrap_trivial_block(block_expr: ast::BlockExpr) -> ast::Expr {
|
||||||
|
|
133
crates/ide-assists/src/utils/ref_field_expr.rs
Normal file
133
crates/ide-assists/src/utils/ref_field_expr.rs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
//! This module contains a helper for converting a field access expression into a
|
||||||
|
//! path expression. This is used when destructuring a tuple or struct.
|
||||||
|
//!
|
||||||
|
//! It determines whether to wrap the new expression in a deref and/or parentheses,
|
||||||
|
//! based on the parent of the existing expression.
|
||||||
|
use syntax::{
|
||||||
|
ast::{self, make, FieldExpr, MethodCallExpr},
|
||||||
|
AstNode, T,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::AssistContext;
|
||||||
|
|
||||||
|
/// Decides whether the new path expression needs to be wrapped in parentheses and dereferenced.
|
||||||
|
/// Returns the relevant parent expression to replace and the [RefData].
|
||||||
|
pub fn determine_ref_and_parens(
|
||||||
|
ctx: &AssistContext<'_>,
|
||||||
|
field_expr: &FieldExpr,
|
||||||
|
) -> (ast::Expr, RefData) {
|
||||||
|
let s = field_expr.syntax();
|
||||||
|
let mut ref_data = RefData { needs_deref: true, needs_parentheses: true };
|
||||||
|
let mut target_node = field_expr.clone().into();
|
||||||
|
|
||||||
|
let parent = match s.parent().map(ast::Expr::cast) {
|
||||||
|
Some(Some(parent)) => parent,
|
||||||
|
Some(None) => {
|
||||||
|
ref_data.needs_parentheses = false;
|
||||||
|
return (target_node, ref_data);
|
||||||
|
}
|
||||||
|
None => return (target_node, ref_data),
|
||||||
|
};
|
||||||
|
|
||||||
|
match parent {
|
||||||
|
ast::Expr::ParenExpr(it) => {
|
||||||
|
// already parens in place -> don't replace
|
||||||
|
ref_data.needs_parentheses = false;
|
||||||
|
// there might be a ref outside: `&(t.0)` -> can be removed
|
||||||
|
if let Some(it) = it.syntax().parent().and_then(ast::RefExpr::cast) {
|
||||||
|
ref_data.needs_deref = false;
|
||||||
|
target_node = it.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::Expr::RefExpr(it) => {
|
||||||
|
// `&*` -> cancel each other out
|
||||||
|
ref_data.needs_deref = false;
|
||||||
|
ref_data.needs_parentheses = false;
|
||||||
|
// might be surrounded by parens -> can be removed too
|
||||||
|
match it.syntax().parent().and_then(ast::ParenExpr::cast) {
|
||||||
|
Some(parent) => target_node = parent.into(),
|
||||||
|
None => target_node = it.into(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// higher precedence than deref `*`
|
||||||
|
// https://doc.rust-lang.org/reference/expressions.html#expression-precedence
|
||||||
|
// -> requires parentheses
|
||||||
|
ast::Expr::PathExpr(_it) => {}
|
||||||
|
ast::Expr::MethodCallExpr(it) => {
|
||||||
|
// `field_expr` is `self_param` (otherwise it would be in `ArgList`)
|
||||||
|
|
||||||
|
// test if there's already auto-ref in place (`value` -> `&value`)
|
||||||
|
// -> no method accepting `self`, but `&self` -> no need for deref
|
||||||
|
//
|
||||||
|
// other combinations (`&value` -> `value`, `&&value` -> `&value`, `&value` -> `&&value`) might or might not be able to auto-ref/deref,
|
||||||
|
// but there might be trait implementations an added `&` might resolve to
|
||||||
|
// -> ONLY handle auto-ref from `value` to `&value`
|
||||||
|
fn is_auto_ref(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> bool {
|
||||||
|
fn impl_(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> Option<bool> {
|
||||||
|
let rec = call_expr.receiver()?;
|
||||||
|
let rec_ty = ctx.sema.type_of_expr(&rec)?.original();
|
||||||
|
// input must be actual value
|
||||||
|
if rec_ty.is_reference() {
|
||||||
|
return Some(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// doesn't resolve trait impl
|
||||||
|
let f = ctx.sema.resolve_method_call(call_expr)?;
|
||||||
|
let self_param = f.self_param(ctx.db())?;
|
||||||
|
// self must be ref
|
||||||
|
match self_param.access(ctx.db()) {
|
||||||
|
hir::Access::Shared | hir::Access::Exclusive => Some(true),
|
||||||
|
hir::Access::Owned => Some(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl_(ctx, call_expr).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_auto_ref(ctx, &it) {
|
||||||
|
ref_data.needs_deref = false;
|
||||||
|
ref_data.needs_parentheses = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::Expr::FieldExpr(_it) => {
|
||||||
|
// `t.0.my_field`
|
||||||
|
ref_data.needs_deref = false;
|
||||||
|
ref_data.needs_parentheses = false;
|
||||||
|
}
|
||||||
|
ast::Expr::IndexExpr(_it) => {
|
||||||
|
// `t.0[1]`
|
||||||
|
ref_data.needs_deref = false;
|
||||||
|
ref_data.needs_parentheses = false;
|
||||||
|
}
|
||||||
|
ast::Expr::TryExpr(_it) => {
|
||||||
|
// `t.0?`
|
||||||
|
// requires deref and parens: `(*_0)`
|
||||||
|
}
|
||||||
|
// lower precedence than deref `*` -> no parens
|
||||||
|
_ => {
|
||||||
|
ref_data.needs_parentheses = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(target_node, ref_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates whether to wrap the new expression in a deref and/or parentheses.
|
||||||
|
pub struct RefData {
|
||||||
|
needs_deref: bool,
|
||||||
|
needs_parentheses: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RefData {
|
||||||
|
/// Wraps the given `expr` in parentheses and/or dereferences it if necessary.
|
||||||
|
pub fn wrap_expr(&self, mut expr: ast::Expr) -> ast::Expr {
|
||||||
|
if self.needs_deref {
|
||||||
|
expr = make::expr_prefix(T![*], expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.needs_parentheses {
|
||||||
|
expr = make::expr_paren(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue