ide_assists/utils/
ref_field_expr.rs

1//! This module contains a helper for converting a field access expression into a
2//! path expression. This is used when destructuring a tuple or struct.
3//!
4//! It determines whether to deref the new expression and/or wrap it in parentheses,
5//! based on the parent of the existing expression.
6use syntax::{
7    AstNode, T,
8    ast::{self, FieldExpr, MethodCallExpr, make},
9};
10
11use crate::AssistContext;
12
13/// Decides whether the new path expression needs to be dereferenced and/or wrapped in parens.
14/// Returns the relevant parent expression to replace and the [RefData].
15pub(crate) fn determine_ref_and_parens(
16    ctx: &AssistContext<'_>,
17    field_expr: &FieldExpr,
18) -> (ast::Expr, RefData) {
19    let s = field_expr.syntax();
20    let mut ref_data = RefData { needs_deref: true, needs_parentheses: true };
21    let mut target_node = field_expr.clone().into();
22
23    let parent = match s.parent().map(ast::Expr::cast) {
24        Some(Some(parent)) => parent,
25        Some(None) => {
26            ref_data.needs_parentheses = false;
27            return (target_node, ref_data);
28        }
29        None => return (target_node, ref_data),
30    };
31
32    match parent {
33        ast::Expr::ParenExpr(it) => {
34            // already parens in place -> don't replace
35            ref_data.needs_parentheses = false;
36            // there might be a ref outside: `&(t.0)` -> can be removed
37            if let Some(it) = it.syntax().parent().and_then(ast::RefExpr::cast) {
38                ref_data.needs_deref = false;
39                target_node = it.into();
40            }
41        }
42        ast::Expr::RefExpr(it) => {
43            // `&*` -> cancel each other out
44            ref_data.needs_deref = false;
45            ref_data.needs_parentheses = false;
46            // might be surrounded by parens -> can be removed too
47            match it.syntax().parent().and_then(ast::ParenExpr::cast) {
48                Some(parent) => target_node = parent.into(),
49                None => target_node = it.into(),
50            };
51        }
52        // higher precedence than deref `*`
53        // https://doc.rust-lang.org/reference/expressions.html#expression-precedence
54        // -> requires parentheses
55        ast::Expr::PathExpr(_it) => {}
56        ast::Expr::MethodCallExpr(it) => {
57            // `field_expr` is `self_param` (otherwise it would be in `ArgList`)
58
59            // test if there's already auto-ref in place (`value` -> `&value`)
60            // -> no method accepting `self`, but `&self` -> no need for deref
61            //
62            // other combinations (`&value` -> `value`, `&&value` -> `&value`, `&value` -> `&&value`) might or might not be able to auto-ref/deref,
63            // but there might be trait implementations an added `&` might resolve to
64            // -> ONLY handle auto-ref from `value` to `&value`
65            fn is_auto_ref(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> bool {
66                fn impl_(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> Option<bool> {
67                    let rec = call_expr.receiver()?;
68                    let rec_ty = ctx.sema.type_of_expr(&rec)?.original();
69                    // input must be actual value
70                    if rec_ty.is_reference() {
71                        return Some(false);
72                    }
73
74                    // doesn't resolve trait impl
75                    let f = ctx.sema.resolve_method_call(call_expr)?;
76                    let self_param = f.self_param(ctx.db())?;
77                    // self must be ref
78                    match self_param.access(ctx.db()) {
79                        hir::Access::Shared | hir::Access::Exclusive => Some(true),
80                        hir::Access::Owned => Some(false),
81                    }
82                }
83                impl_(ctx, call_expr).unwrap_or(false)
84            }
85
86            if is_auto_ref(ctx, &it) {
87                ref_data.needs_deref = false;
88                ref_data.needs_parentheses = false;
89            }
90        }
91        ast::Expr::FieldExpr(_it) => {
92            // `t.0.my_field`
93            ref_data.needs_deref = false;
94            ref_data.needs_parentheses = false;
95        }
96        ast::Expr::IndexExpr(_it) => {
97            // `t.0[1]`
98            ref_data.needs_deref = false;
99            ref_data.needs_parentheses = false;
100        }
101        ast::Expr::TryExpr(_it) => {
102            // `t.0?`
103            // requires deref and parens: `(*_0)`
104        }
105        // lower precedence than deref `*` -> no parens
106        _ => {
107            ref_data.needs_parentheses = false;
108        }
109    };
110
111    (target_node, ref_data)
112}
113
114/// Indicates whether to deref an expression or wrap it in parens
115pub(crate) struct RefData {
116    needs_deref: bool,
117    needs_parentheses: bool,
118}
119
120impl RefData {
121    /// Derefs `expr` and wraps it in parens if necessary
122    pub(crate) fn wrap_expr(&self, mut expr: ast::Expr) -> ast::Expr {
123        if self.needs_deref {
124            expr = make::expr_prefix(T![*], expr).into();
125        }
126
127        if self.needs_parentheses {
128            expr = make::expr_paren(expr).into();
129        }
130
131        expr
132    }
133}