ide_assists/utils/ref_field_expr.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 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 deref the new expression and/or wrap it in 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 dereferenced and/or wrapped in parens.
/// Returns the relevant parent expression to replace and the [RefData].
pub(crate) 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 deref an expression or wrap it in parens
pub(crate) struct RefData {
needs_deref: bool,
needs_parentheses: bool,
}
impl RefData {
/// Derefs `expr` and wraps it in parens if necessary
pub(crate) 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
}
}