rustc_lint/
enum_intrinsics_non_enums.rs

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