summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_lint/src/enum_intrinsics_non_enums.rs')
-rw-r--r--compiler/rustc_lint/src/enum_intrinsics_non_enums.rs88
1 files changed, 88 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
new file mode 100644
index 000000000..f41ee6404
--- /dev/null
+++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
@@ -0,0 +1,88 @@
+use crate::{context::LintContext, LateContext, LateLintPass};
+use rustc_errors::fluent;
+use rustc_hir as hir;
+use rustc_middle::ty::{visit::TypeVisitable, Ty};
+use rustc_span::{symbol::sym, Span};
+
+declare_lint! {
+ /// The `enum_intrinsics_non_enums` lint detects calls to
+ /// intrinsic functions that require an enum ([`core::mem::discriminant`],
+ /// [`core::mem::variant_count`]), but are called with a non-enum type.
+ ///
+ /// [`core::mem::discriminant`]: https://doc.rust-lang.org/core/mem/fn.discriminant.html
+ /// [`core::mem::variant_count`]: https://doc.rust-lang.org/core/mem/fn.variant_count.html
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(enum_intrinsics_non_enums)]
+ /// core::mem::discriminant::<i32>(&123);
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// In order to accept any enum, the `mem::discriminant` and
+ /// `mem::variant_count` functions are generic over a type `T`.
+ /// This makes it technically possible for `T` to be a non-enum,
+ /// in which case the return value is unspecified.
+ ///
+ /// This lint prevents such incorrect usage of these functions.
+ ENUM_INTRINSICS_NON_ENUMS,
+ Deny,
+ "detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types"
+}
+
+declare_lint_pass!(EnumIntrinsicsNonEnums => [ENUM_INTRINSICS_NON_ENUMS]);
+
+/// Returns `true` if we know for sure that the given type is not an enum. Note that for cases where
+/// the type is generic, we can't be certain if it will be an enum so we have to assume that it is.
+fn is_non_enum(t: Ty<'_>) -> bool {
+ !t.is_enum() && !t.needs_subst()
+}
+
+fn enforce_mem_discriminant(
+ cx: &LateContext<'_>,
+ func_expr: &hir::Expr<'_>,
+ expr_span: Span,
+ args_span: Span,
+) {
+ let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0);
+ if is_non_enum(ty_param) {
+ cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, expr_span, |builder| {
+ builder
+ .build(fluent::lint::enum_intrinsics_mem_discriminant)
+ .set_arg("ty_param", ty_param)
+ .span_note(args_span, fluent::lint::note)
+ .emit();
+ });
+ }
+}
+
+fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) {
+ let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0);
+ if is_non_enum(ty_param) {
+ cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, |builder| {
+ builder
+ .build(fluent::lint::enum_intrinsics_mem_variant)
+ .set_arg("ty_param", ty_param)
+ .note(fluent::lint::note)
+ .emit();
+ });
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ let hir::ExprKind::Call(func, args) = &expr.kind else { return };
+ let hir::ExprKind::Path(qpath) = &func.kind else { return };
+ let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() else { return };
+ let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return };
+ match name {
+ sym::mem_discriminant => enforce_mem_discriminant(cx, func, expr.span, args[0].span),
+ sym::mem_variant_count => enforce_mem_variant_count(cx, func, expr.span),
+ _ => {}
+ }
+ }
+}