]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
Document that pre-expansion lint passes are deprecated
[rust.git] / compiler / rustc_lint / src / enum_intrinsics_non_enums.rs
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.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<'_>) {
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             _ => {}
102         }
103     }
104 }