rustc_lint/
enum_intrinsics_non_enums.rs

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