]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
New upstream version 1.59.0+dfsg1
[rustc.git] / compiler / rustc_lint / src / enum_intrinsics_non_enums.rs
CommitLineData
c295e0f8
XL
1use crate::{context::LintContext, LateContext, LateLintPass};
2use rustc_hir as hir;
3use rustc_middle::ty::{fold::TypeFoldable, Ty};
4use rustc_span::{symbol::sym, Span};
5
6declare_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
36declare_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.
40fn is_non_enum(t: Ty<'_>) -> bool {
41 !t.is_enum() && !t.potentially_needs_subst()
42}
43
44fn 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
72fn 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
92impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums {
93 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
a2a8927a
XL
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 _ => {}
c295e0f8
XL
102 }
103 }
104}