summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/utils/internal_lints/invalid_paths.rs
blob: 25532dd4e2681e6455921480339e937b536e3e92 (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
use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::def_path_res;
use clippy_utils::diagnostics::span_lint;
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::Item;
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, fast_reject::SimplifiedTypeGen, FloatTy};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;

declare_clippy_lint! {
    /// ### What it does
    /// Checks the paths module for invalid paths.
    ///
    /// ### Why is this bad?
    /// It indicates a bug in the code.
    ///
    /// ### Example
    /// None.
    pub INVALID_PATHS,
    internal,
    "invalid path"
}

declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);

impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
        let local_def_id = &cx.tcx.parent_module(item.hir_id());
        let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
        if_chain! {
            if mod_name.as_str() == "paths";
            if let hir::ItemKind::Const(ty, body_id) = item.kind;
            let ty = hir_ty_to_ty(cx.tcx, ty);
            if let ty::Array(el_ty, _) = &ty.kind();
            if let ty::Ref(_, el_ty, _) = &el_ty.kind();
            if el_ty.is_str();
            let body = cx.tcx.hir().body(body_id);
            let typeck_results = cx.tcx.typeck_body(body_id);
            if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, body.value);
            let path: Vec<&str> = path
                .iter()
                .map(|x| {
                    if let Constant::Str(s) = x {
                        s.as_str()
                    } else {
                        // We checked the type of the constant above
                        unreachable!()
                    }
                })
                .collect();
            if !check_path(cx, &path[..]);
            then {
                span_lint(cx, INVALID_PATHS, item.span, "invalid path");
            }
        }
    }
}

// This is not a complete resolver for paths. It works on all the paths currently used in the paths
// module.  That's all it does and all it needs to do.
pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
    if def_path_res(cx, path, None) != Res::Err {
        return true;
    }

    // Some implementations can't be found by `path_to_res`, particularly inherent
    // implementations of native types. Check lang items.
    let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
    let lang_items = cx.tcx.lang_items();
    // This list isn't complete, but good enough for our current list of paths.
    let incoherent_impls = [
        SimplifiedTypeGen::FloatSimplifiedType(FloatTy::F32),
        SimplifiedTypeGen::FloatSimplifiedType(FloatTy::F64),
        SimplifiedTypeGen::SliceSimplifiedType,
        SimplifiedTypeGen::StrSimplifiedType,
    ]
    .iter()
    .flat_map(|&ty| cx.tcx.incoherent_impls(ty));
    for item_def_id in lang_items.items().iter().flatten().chain(incoherent_impls) {
        let lang_item_path = cx.get_def_path(*item_def_id);
        if path_syms.starts_with(&lang_item_path) {
            if let [item] = &path_syms[lang_item_path.len()..] {
                if matches!(
                    cx.tcx.def_kind(*item_def_id),
                    DefKind::Mod | DefKind::Enum | DefKind::Trait
                ) {
                    for child in cx.tcx.module_children(*item_def_id) {
                        if child.ident.name == *item {
                            return true;
                        }
                    }
                } else {
                    for child in cx.tcx.associated_item_def_ids(*item_def_id) {
                        if cx.tcx.item_name(*child) == *item {
                            return true;
                        }
                    }
                }
            }
        }
    }

    false
}