Skip to content

Commit 5b21064

Browse files
committed
Auto merge of #83908 - Flying-Toast:master, r=davidtwco
Add enum_intrinsics_non_enums lint There is a clippy lint to prevent calling [`mem::discriminant`](https://doc.rust-lang.org/std/mem/fn.discriminant.html) with a non-enum type. I think the lint is worthy of being included in rustc, given that `discriminant::<T>()` where `T` is a non-enum has an unspecified return value, and there are no valid use cases where you'd actually want this. I've also made the lint check [variant_count](https://doc.rust-lang.org/core/mem/fn.variant_count.html) (#73662). closes #83899
2 parents 1067e2c + f483676 commit 5b21064

22 files changed

+292
-315
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use crate::{context::LintContext, LateContext, LateLintPass};
2+
use rustc_hir as hir;
3+
use rustc_middle::ty::{fold::TypeFoldable, Ty};
4+
use rustc_span::{symbol::sym, Span};
5+
6+
declare_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+
36+
declare_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.
40+
fn is_non_enum(t: Ty<'_>) -> bool {
41+
!t.is_enum() && !t.potentially_needs_subst()
42+
}
43+
44+
fn 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+
72+
fn 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+
92+
impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums {
93+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
94+
if let hir::ExprKind::Call(ref func, ref args) = expr.kind {
95+
if let hir::ExprKind::Path(ref qpath) = func.kind {
96+
if let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() {
97+
if cx.tcx.is_diagnostic_item(sym::mem_discriminant, def_id) {
98+
enforce_mem_discriminant(cx, func, expr.span, args[0].span);
99+
} else if cx.tcx.is_diagnostic_item(sym::mem_variant_count, def_id) {
100+
enforce_mem_variant_count(cx, func, expr.span);
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ mod array_into_iter;
4747
pub mod builtin;
4848
mod context;
4949
mod early;
50+
mod enum_intrinsics_non_enums;
5051
mod internal;
5152
mod late;
5253
mod levels;
@@ -76,6 +77,7 @@ use rustc_span::Span;
7677

7778
use array_into_iter::ArrayIntoIter;
7879
use builtin::*;
80+
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
7981
use internal::*;
8082
use methods::*;
8183
use non_ascii_idents::*;
@@ -168,6 +170,7 @@ macro_rules! late_lint_passes {
168170
TemporaryCStringAsPtr: TemporaryCStringAsPtr,
169171
NonPanicFmt: NonPanicFmt,
170172
NoopMethodCall: NoopMethodCall,
173+
EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
171174
InvalidAtomicOrdering: InvalidAtomicOrdering,
172175
NamedAsmLabels: NamedAsmLabels,
173176
]

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@ symbols! {
816816
mem_size_of,
817817
mem_size_of_val,
818818
mem_uninitialized,
819+
mem_variant_count,
819820
mem_zeroed,
820821
member_constraints,
821822
memory,

library/core/src/mem/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,7 @@ pub const fn discriminant<T>(v: &T) -> Discriminant<T> {
10531053
#[inline(always)]
10541054
#[unstable(feature = "variant_count", issue = "73662")]
10551055
#[rustc_const_unstable(feature = "variant_count", issue = "73662")]
1056+
#[rustc_diagnostic_item = "mem_variant_count"]
10561057
pub const fn variant_count<T>() -> usize {
10571058
intrinsics::variant_count::<T>()
10581059
}

src/test/ui/consts/const-variant-count.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// run-pass
2-
#![allow(dead_code)]
2+
#![allow(dead_code, enum_intrinsics_non_enums)]
33
#![feature(variant_count)]
44
#![feature(never_type)]
55

src/test/ui/enum-discriminant/discriminant_value-wrapper.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
// run-pass
2+
3+
#![allow(enum_intrinsics_non_enums)]
4+
25
use std::mem;
36

47
enum ADT {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Test the enum_intrinsics_non_enums lint.
2+
3+
#![feature(variant_count)]
4+
5+
use std::mem::{discriminant, variant_count};
6+
7+
enum SomeEnum {
8+
A,
9+
B,
10+
}
11+
12+
struct SomeStruct;
13+
14+
fn generic_discriminant<T>(v: &T) {
15+
discriminant::<T>(v);
16+
}
17+
18+
fn generic_variant_count<T>() -> usize {
19+
variant_count::<T>()
20+
}
21+
22+
fn test_discriminant() {
23+
discriminant(&SomeEnum::A);
24+
generic_discriminant(&SomeEnum::B);
25+
26+
discriminant(&());
27+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
28+
29+
discriminant(&&SomeEnum::B);
30+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
31+
32+
discriminant(&SomeStruct);
33+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
34+
35+
discriminant(&123u32);
36+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
37+
38+
discriminant(&&123i8);
39+
//~^ error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
40+
}
41+
42+
fn test_variant_count() {
43+
variant_count::<SomeEnum>();
44+
generic_variant_count::<SomeEnum>();
45+
46+
variant_count::<&str>();
47+
//~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
48+
49+
variant_count::<*const u8>();
50+
//~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
51+
52+
variant_count::<()>();
53+
//~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
54+
55+
variant_count::<&SomeEnum>();
56+
//~^ error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
57+
}
58+
59+
fn main() {
60+
test_discriminant();
61+
test_variant_count();
62+
63+
// The lint ignores cases where the type is generic, so these should be
64+
// allowed even though their return values are unspecified
65+
generic_variant_count::<SomeStruct>();
66+
generic_discriminant::<SomeStruct>(&SomeStruct);
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
2+
--> $DIR/lint-enum-intrinsics-non-enums.rs:26:5
3+
|
4+
LL | discriminant(&());
5+
| ^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[deny(enum_intrinsics_non_enums)]` on by default
8+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `()`, which is not an enum.
9+
--> $DIR/lint-enum-intrinsics-non-enums.rs:26:18
10+
|
11+
LL | discriminant(&());
12+
| ^^^
13+
14+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
15+
--> $DIR/lint-enum-intrinsics-non-enums.rs:29:5
16+
|
17+
LL | discriminant(&&SomeEnum::B);
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
19+
|
20+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `&SomeEnum`, which is not an enum.
21+
--> $DIR/lint-enum-intrinsics-non-enums.rs:29:18
22+
|
23+
LL | discriminant(&&SomeEnum::B);
24+
| ^^^^^^^^^^^^^
25+
26+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
27+
--> $DIR/lint-enum-intrinsics-non-enums.rs:32:5
28+
|
29+
LL | discriminant(&SomeStruct);
30+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
31+
|
32+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `SomeStruct`, which is not an enum.
33+
--> $DIR/lint-enum-intrinsics-non-enums.rs:32:18
34+
|
35+
LL | discriminant(&SomeStruct);
36+
| ^^^^^^^^^^^
37+
38+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
39+
--> $DIR/lint-enum-intrinsics-non-enums.rs:35:5
40+
|
41+
LL | discriminant(&123u32);
42+
| ^^^^^^^^^^^^^^^^^^^^^
43+
|
44+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `u32`, which is not an enum.
45+
--> $DIR/lint-enum-intrinsics-non-enums.rs:35:18
46+
|
47+
LL | discriminant(&123u32);
48+
| ^^^^^^^
49+
50+
error: the return value of `mem::discriminant` is unspecified when called with a non-enum type
51+
--> $DIR/lint-enum-intrinsics-non-enums.rs:38:5
52+
|
53+
LL | discriminant(&&123i8);
54+
| ^^^^^^^^^^^^^^^^^^^^^
55+
|
56+
note: the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `&i8`, which is not an enum.
57+
--> $DIR/lint-enum-intrinsics-non-enums.rs:38:18
58+
|
59+
LL | discriminant(&&123i8);
60+
| ^^^^^^^
61+
62+
error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
63+
--> $DIR/lint-enum-intrinsics-non-enums.rs:46:5
64+
|
65+
LL | variant_count::<&str>();
66+
| ^^^^^^^^^^^^^^^^^^^^^^^
67+
|
68+
= note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `&str`, which is not an enum.
69+
70+
error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
71+
--> $DIR/lint-enum-intrinsics-non-enums.rs:49:5
72+
|
73+
LL | variant_count::<*const u8>();
74+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75+
|
76+
= note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `*const u8`, which is not an enum.
77+
78+
error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
79+
--> $DIR/lint-enum-intrinsics-non-enums.rs:52:5
80+
|
81+
LL | variant_count::<()>();
82+
| ^^^^^^^^^^^^^^^^^^^^^
83+
|
84+
= note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `()`, which is not an enum.
85+
86+
error: the return value of `mem::variant_count` is unspecified when called with a non-enum type
87+
--> $DIR/lint-enum-intrinsics-non-enums.rs:55:5
88+
|
89+
LL | variant_count::<&SomeEnum>();
90+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91+
|
92+
= note: the type parameter of `variant_count` should be an enum, but it was instantiated with the type `&SomeEnum`, which is not an enum.
93+
94+
error: aborting due to 9 previous errors
95+

src/tools/clippy/CHANGELOG.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -1873,10 +1873,10 @@ Released 2019-01-17
18731873

18741874
[2e26fdc2...b2601be](https://github.com/rust-lang/rust-clippy/compare/2e26fdc2...b2601be)
18751875

1876-
* New lints: [`slow_vector_initialization`], [`mem_discriminant_non_enum`],
1876+
* New lints: [`slow_vector_initialization`], `mem_discriminant_non_enum`,
18771877
[`redundant_clone`], [`wildcard_dependencies`],
18781878
[`into_iter_on_ref`], `into_iter_on_array`, [`deprecated_cfg_attr`],
1879-
[`mem_discriminant_non_enum`], [`cargo_common_metadata`]
1879+
[`cargo_common_metadata`]
18801880
* Add support for `u128` and `i128` to integer related lints
18811881
* Add float support to `mistyped_literal_suffixes`
18821882
* Fix false positives in `use_self`
@@ -2839,7 +2839,6 @@ Released 2018-09-13
28392839
[`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm
28402840
[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants
28412841
[`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter
2842-
[`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum
28432842
[`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget
28442843
[`mem_replace_option_with_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_none
28452844
[`mem_replace_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default

src/tools/clippy/clippy_lints/src/lib.register_all.rs

-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
127127
LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
128128
LintId::of(matches::SINGLE_MATCH),
129129
LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
130-
LintId::of(mem_discriminant::MEM_DISCRIMINANT_NON_ENUM),
131130
LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
132131
LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
133132
LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),

src/tools/clippy/clippy_lints/src/lib.register_correctness.rs

-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
3636
LintId::of(loops::ITER_NEXT_LOOP),
3737
LintId::of(loops::NEVER_LOOP),
3838
LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
39-
LintId::of(mem_discriminant::MEM_DISCRIMINANT_NON_ENUM),
4039
LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
4140
LintId::of(methods::CLONE_DOUBLE_REF),
4241
LintId::of(methods::ITERATOR_STEP_BY_ZERO),

src/tools/clippy/clippy_lints/src/lib.register_lints.rs

-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ store.register_lints(&[
241241
matches::SINGLE_MATCH_ELSE,
242242
matches::WILDCARD_ENUM_MATCH_ARM,
243243
matches::WILDCARD_IN_OR_PATTERNS,
244-
mem_discriminant::MEM_DISCRIMINANT_NON_ENUM,
245244
mem_forget::MEM_FORGET,
246245
mem_replace::MEM_REPLACE_OPTION_WITH_NONE,
247246
mem_replace::MEM_REPLACE_WITH_DEFAULT,

src/tools/clippy/clippy_lints/src/lib.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,6 @@ mod map_unit_fn;
266266
mod match_on_vec_items;
267267
mod match_result_ok;
268268
mod matches;
269-
mod mem_discriminant;
270269
mod mem_forget;
271270
mod mem_replace;
272271
mod methods;
@@ -600,7 +599,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
600599
let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
601600
store.register_late_pass(move || Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
602601
store.register_late_pass(|| Box::new(neg_multiply::NegMultiply));
603-
store.register_late_pass(|| Box::new(mem_discriminant::MemDiscriminant));
604602
store.register_late_pass(|| Box::new(mem_forget::MemForget));
605603
store.register_late_pass(|| Box::new(arithmetic::Arithmetic::default()));
606604
store.register_late_pass(|| Box::new(assign_ops::AssignOps));
@@ -850,6 +848,7 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
850848
ls.register_renamed("clippy::panic_params", "non_fmt_panics");
851849
ls.register_renamed("clippy::unknown_clippy_lints", "unknown_lints");
852850
ls.register_renamed("clippy::invalid_atomic_ordering", "invalid_atomic_ordering");
851+
ls.register_renamed("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums");
853852
}
854853

855854
// only exists to let the dogfood integration test works.

0 commit comments

Comments
 (0)