]>
Commit | Line | Data |
---|---|---|
c295e0f8 XL |
1 | use crate::{context::LintContext, LateContext, LateLintPass}; |
2 | use rustc_hir as hir; | |
3 | use rustc_middle::ty::{fold::TypeFoldable, Ty}; | |
4 | use rustc_span::{symbol::sym, Span}; | |
5 | ||
6 | declare_lint! { | |
7 | /// The `enum_intrinsics_non_enums` lint detects calls to | |
8 | /// intrinsic functions that require an enum ([`core::mem::discriminant`], | |
9 | /// [`core::mem::variant_count`]), but are called with a non-enum type. | |
10 | /// | |
11 | /// [`core::mem::discriminant`]: https://doc.rust-lang.org/core/mem/fn.discriminant.html | |
12 | /// [`core::mem::variant_count`]: https://doc.rust-lang.org/core/mem/fn.variant_count.html | |
13 | /// | |
14 | /// ### Example | |
15 | /// | |
16 | /// ```rust,compile_fail | |
17 | /// #![deny(enum_intrinsics_non_enums)] | |
18 | /// core::mem::discriminant::<i32>(&123); | |
19 | /// ``` | |
20 | /// | |
21 | /// {{produces}} | |
22 | /// | |
23 | /// ### Explanation | |
24 | /// | |
25 | /// In order to accept any enum, the `mem::discriminant` and | |
26 | /// `mem::variant_count` functions are generic over a type `T`. | |
27 | /// This makes it technically possible for `T` to be a non-enum, | |
28 | /// in which case the return value is unspecified. | |
29 | /// | |
30 | /// This lint prevents such incorrect usage of these functions. | |
31 | ENUM_INTRINSICS_NON_ENUMS, | |
32 | Deny, | |
33 | "detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types" | |
34 | } | |
35 | ||
36 | declare_lint_pass!(EnumIntrinsicsNonEnums => [ENUM_INTRINSICS_NON_ENUMS]); | |
37 | ||
38 | /// Returns `true` if we know for sure that the given type is not an enum. Note that for cases where | |
39 | /// the type is generic, we can't be certain if it will be an enum so we have to assume that it is. | |
40 | fn is_non_enum(t: Ty<'_>) -> bool { | |
41 | !t.is_enum() && !t.potentially_needs_subst() | |
42 | } | |
43 | ||
44 | fn enforce_mem_discriminant( | |
45 | cx: &LateContext<'_>, | |
46 | func_expr: &hir::Expr<'_>, | |
47 | expr_span: Span, | |
48 | args_span: Span, | |
49 | ) { | |
50 | let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); | |
51 | if is_non_enum(ty_param) { | |
52 | cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, expr_span, |builder| { | |
53 | builder | |
54 | .build( | |
55 | "the return value of `mem::discriminant` is \ | |
56 | unspecified when called with a non-enum type", | |
57 | ) | |
58 | .span_note( | |
59 | args_span, | |
60 | &format!( | |
61 | "the argument to `discriminant` should be a \ | |
62 | reference to an enum, but it was passed \ | |
63 | a reference to a `{}`, which is not an enum.", | |
64 | ty_param, | |
65 | ), | |
66 | ) | |
67 | .emit(); | |
68 | }); | |
69 | } | |
70 | } | |
71 | ||
72 | fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) { | |
73 | let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); | |
74 | if is_non_enum(ty_param) { | |
75 | cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, |builder| { | |
76 | builder | |
77 | .build( | |
78 | "the return value of `mem::variant_count` is \ | |
79 | unspecified when called with a non-enum type", | |
80 | ) | |
81 | .note(&format!( | |
82 | "the type parameter of `variant_count` should \ | |
83 | be an enum, but it was instantiated with \ | |
84 | the type `{}`, which is not an enum.", | |
85 | ty_param, | |
86 | )) | |
87 | .emit(); | |
88 | }); | |
89 | } | |
90 | } | |
91 | ||
92 | impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums { | |
93 | fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { | |
a2a8927a XL |
94 | let hir::ExprKind::Call(func, args) = &expr.kind else { return }; |
95 | let hir::ExprKind::Path(qpath) = &func.kind else { return }; | |
96 | let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() else { return }; | |
97 | let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return }; | |
98 | match name { | |
99 | sym::mem_discriminant => enforce_mem_discriminant(cx, func, expr.span, args[0].span), | |
100 | sym::mem_variant_count => enforce_mem_variant_count(cx, func, expr.span), | |
101 | _ => {} | |
c295e0f8 XL |
102 | } |
103 | } | |
104 | } |