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