about summary refs log tree commit diff
path: root/compiler/rustc_lint/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_lint/src')
-rw-r--r--compiler/rustc_lint/src/default_field_always_invalid.rs91
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/lints.rs9
3 files changed, 103 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/default_field_always_invalid.rs b/compiler/rustc_lint/src/default_field_always_invalid.rs
new file mode 100644
index 00000000000..46cffb53b4b
--- /dev/null
+++ b/compiler/rustc_lint/src/default_field_always_invalid.rs
@@ -0,0 +1,91 @@
+use rustc_hir as hir;
+use rustc_middle::lint::LintLevelSource;
+use rustc_middle::mir::interpret::ErrorHandled;
+use rustc_session::lint::Level;
+use rustc_session::{declare_lint, declare_lint_pass};
+
+use crate::lints::DefaultFieldAlwaysInvalidConst;
+use crate::{LateContext, LateLintPass};
+
+declare_lint! {
+    /// The `default_field_always_invalid_const` lint checks for structs with
+    /// default fields const values that will *always* fail to be created.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![feature(default_field_values)]
+    /// #[deny(default_field_always_invalid_const)]
+    /// struct Foo {
+    ///     bar: u8 = 130 + 130, // `260` doesn't fit in `u8`
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Without this lint, the error would only happen only during construction
+    /// of the affected type. For example, given the type above, `Foo { .. }`
+    /// would always fail to build, but `Foo { bar: 0 }` would be accepted. This
+    /// lint will catch accidental cases of const values that would fail to
+    /// compile, but won't detect cases that are only partially evaluated.
+    pub DEFAULT_FIELD_ALWAYS_INVALID_CONST,
+    Deny,
+    "using this default field will always fail to compile"
+}
+
+declare_lint_pass!(DefaultFieldAlwaysInvalid => [DEFAULT_FIELD_ALWAYS_INVALID_CONST]);
+
+impl<'tcx> LateLintPass<'tcx> for DefaultFieldAlwaysInvalid {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        let data = match item.kind {
+            hir::ItemKind::Struct(data, _generics) => data,
+            _ => return,
+        };
+        let hir::VariantData::Struct { fields, recovered: _ } = data else {
+            return;
+        };
+
+        let (level, source) =
+            cx.tcx.lint_level_at_node(DEFAULT_FIELD_ALWAYS_INVALID_CONST, item.hir_id());
+        match level {
+            Level::Deny | Level::Forbid => {}
+            Level::Warn | Level::ForceWarn(_) | Level::Expect(_) => {
+                // We *can't* turn the const eval error into a warning, so we make it a
+                // warning to not use `#[warn(default_field_always_invalid_const)]`.
+                let invalid_msg = "lint `default_field_always_invalid_const` can't be warned on";
+                #[allow(rustc::diagnostic_outside_of_impl, rustc::untranslatable_diagnostic)]
+                if let LintLevelSource::Node { span, .. } = source {
+                    let mut err = cx.tcx.sess.dcx().struct_span_warn(span, invalid_msg);
+                    err.span_label(
+                        span,
+                        "either `deny` or `allow`, no other lint level is supported for this lint",
+                    );
+                    err.emit();
+                } else {
+                    cx.tcx.sess.dcx().warn(invalid_msg);
+                }
+            }
+            Level::Allow => {
+                // We don't even look at the fields.
+                return;
+            }
+        }
+        for field in fields {
+            if let Some(c) = field.default
+                && let Some(_ty) = cx.tcx.type_of(c.def_id).no_bound_vars()
+                && let Err(ErrorHandled::Reported(_, _)) = cx.tcx.const_eval_poly(c.def_id.into())
+            {
+                // We use the item's hir id because the const's hir id might resolve inside of a
+                // foreign macro, meaning the lint won't trigger.
+                cx.tcx.emit_node_span_lint(
+                    DEFAULT_FIELD_ALWAYS_INVALID_CONST,
+                    item.hir_id(),
+                    field.span,
+                    DefaultFieldAlwaysInvalidConst { span: field.span, help: () },
+                );
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index a99c94592b3..baf2703511e 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -41,6 +41,7 @@ mod async_fn_in_trait;
 pub mod builtin;
 mod context;
 mod dangling;
+mod default_field_always_invalid;
 mod deref_into_dyn_supertrait;
 mod drop_forget_useless;
 mod early;
@@ -85,6 +86,7 @@ use async_closures::AsyncClosureUsage;
 use async_fn_in_trait::AsyncFnInTrait;
 use builtin::*;
 use dangling::*;
+use default_field_always_invalid::*;
 use deref_into_dyn_supertrait::*;
 use drop_forget_useless::*;
 use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
@@ -193,6 +195,7 @@ late_lint_methods!(
             DropForgetUseless: DropForgetUseless,
             ImproperCTypesDeclarations: ImproperCTypesDeclarations,
             ImproperCTypesDefinitions: ImproperCTypesDefinitions,
+            DefaultFieldAlwaysInvalid: DefaultFieldAlwaysInvalid,
             InvalidFromUtf8: InvalidFromUtf8,
             VariantSizeDifferences: VariantSizeDifferences,
             PathStatements: PathStatements,
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 9fa263799eb..07f1c48bafb 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -730,6 +730,15 @@ pub(crate) struct UndroppedManuallyDropsSuggestion {
     pub end_span: Span,
 }
 
+#[derive(LintDiagnostic)]
+#[diag(lint_default_field_always_invalid_const)]
+pub(crate) struct DefaultFieldAlwaysInvalidConst {
+    #[label]
+    pub span: Span,
+    #[help]
+    pub help: (),
+}
+
 // invalid_from_utf8.rs
 #[derive(LintDiagnostic)]
 pub(crate) enum InvalidFromUtf8Diag {