Unnamed repository; edit this file 'description' to name the repository.
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 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
    }
}