summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs
blob: 27acad45ccf729e00e63982d50ead26f92ec6b7b (plain)
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
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::{intravisit::FnKind, Body, ExprKind, FnDecl, HirId, ImplicitSelfKind, Unsafety};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Span;

use std::iter;

use super::MISNAMED_GETTERS;

pub fn check_fn(
    cx: &LateContext<'_>,
    kind: FnKind<'_>,
    decl: &FnDecl<'_>,
    body: &Body<'_>,
    span: Span,
    _hir_id: HirId,
) {
    let FnKind::Method(ref ident, sig) = kind else {
            return;
        };

    // Takes only &(mut) self
    if decl.inputs.len() != 1 {
        return;
    }

    let name = ident.name.as_str();

    let name = match decl.implicit_self {
        ImplicitSelfKind::MutRef => {
            let Some(name) = name.strip_suffix("_mut") else {
                    return;
                };
            name
        },
        ImplicitSelfKind::Imm | ImplicitSelfKind::Mut | ImplicitSelfKind::ImmRef => name,
        ImplicitSelfKind::None => return,
    };

    let name = if sig.header.unsafety == Unsafety::Unsafe {
        name.strip_suffix("_unchecked").unwrap_or(name)
    } else {
        name
    };

    // Body must be &(mut) <self_data>.name
    // self_data is not neccessarilly self, to also lint sub-getters, etc…

    let block_expr = if_chain! {
        if let ExprKind::Block(block,_) = body.value.kind;
        if block.stmts.is_empty();
        if let Some(block_expr) = block.expr;
        then {
            block_expr
        } else {
            return;
        }
    };
    let expr_span = block_expr.span;

    // Accept &<expr>, &mut <expr> and <expr>
    let expr = if let ExprKind::AddrOf(_, _, tmp) = block_expr.kind {
        tmp
    } else {
        block_expr
    };
    let (self_data, used_ident) = if_chain! {
        if let ExprKind::Field(self_data, ident) = expr.kind;
        if ident.name.as_str() != name;
        then {
            (self_data, ident)
        } else {
            return;
        }
    };

    let mut used_field = None;
    let mut correct_field = None;
    let typeck_results = cx.typeck_results();
    for adjusted_type in iter::once(typeck_results.expr_ty(self_data))
        .chain(typeck_results.expr_adjustments(self_data).iter().map(|adj| adj.target))
    {
        let ty::Adt(def,_) = adjusted_type.kind() else {
            continue;
        };

        for f in def.all_fields() {
            if f.name.as_str() == name {
                correct_field = Some(f);
            }
            if f.name == used_ident.name {
                used_field = Some(f);
            }
        }
    }

    let Some(used_field) = used_field else {
        // Can happen if the field access is a tuple. We don't lint those because the getter name could not start with a number.
        return;
    };

    let Some(correct_field) = correct_field else {
        // There is no field corresponding to the getter name.
        // FIXME: This can be a false positive if the correct field is reachable trought deeper autodereferences than used_field is
        return;
    };

    if cx.tcx.type_of(used_field.did) == cx.tcx.type_of(correct_field.did) {
        let left_span = block_expr.span.until(used_ident.span);
        let snippet = snippet(cx, left_span, "..");
        let sugg = format!("{snippet}{name}");
        span_lint_and_then(
            cx,
            MISNAMED_GETTERS,
            span,
            "getter function appears to return the wrong field",
            |diag| {
                diag.span_suggestion(expr_span, "consider using", sugg, Applicability::MaybeIncorrect);
            },
        );
    }
}