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