Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 43 additions & 20 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,23 @@ use rustc_attr_ir::{EiiDecl, EiiImpl, EiiImplResolution, find_attr};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_data_structures::transitive_relation::TransitiveRelationBuilder;
use rustc_errors::codes::*;
use rustc_errors::{Applicability, ErrorGuaranteed, msg, pluralize, struct_span_code_err};
use rustc_errors::{
Applicability, ErrorGuaranteed, MultiSpan, msg, pluralize, struct_span_code_err,
};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{AmbigArg, ItemKind};
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::{BoundRegionConversionTime, SolverRegionConstraint, TyCtxtInferExt};
use rustc_infer::traits::{PredicateObligations, TraitErrors};
use rustc_lint_defs::builtin::{REDUNDANT_LIFETIMES, SHADOWING_SUPERTRAIT_ITEMS};
use rustc_lint_defs::builtin::{
REDUNDANT_LIFETIMES, SHADOWING_SUPERTRAIT_ITEMS, UNEVALUATED_DEFAULT_FIELD_VALUE,
};
use rustc_macros::{Diagnostic, TypeFoldable, TypeVisitable};
use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::traits::solve::NoSolution;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::region_constraint::{And, LeafRegionConstraint, Or};
use rustc_middle::ty::trait_def::TraitSpecializationKind;
use rustc_middle::ty::{
Expand All @@ -47,7 +52,9 @@ use tracing::{debug, instrument};
use super::compare_eii::{compare_eii_function_types, compare_eii_statics};
use crate::autoderef::Autoderef;
use crate::constrained_generic_params::{Parameter, identify_constrained_generic_params};
use crate::diagnostics::{self, InvalidReceiverTyHint, ParamInTyOfConstParam};
use crate::diagnostics::{
self, InvalidReceiverTyHint, ParamInTyOfConstParam, UnevaluatedDefaultFieldValue,
};

pub(super) struct WfCheckingCtxt<'a, 'tcx> {
pub(super) ocx: ObligationCtxt<'a, 'tcx, FulfillmentError<'tcx>>,
Expand Down Expand Up @@ -985,28 +992,44 @@ pub(crate) fn check_type_defn<'tcx>(
enter_wf_checking_ctxt(tcx, item, |wfcx| {
let variants = adt_def.variants();
let packed = adt_def.repr().packed();
let own_params_require_monomorphization =
tcx.generics_of(item).own_requires_monomorphization();

for variant in variants.iter() {
// All field types must be well-formed.
for field in &variant.fields {
if let Some(def_id) = field.value
&& let Some(_ty) = tcx.type_of(def_id).no_bound_vars()
{
// FIXME(generic_const_exprs, default_field_values): this is a hack and needs to
// be refactored to check the instantiate-ability of the code better.
if let Some(def_id) = def_id.as_local()
&& let DefKind::AnonConst = tcx.def_kind(def_id)
&& let hir::Node::AnonConst(anon) = tcx.hir_node_by_def_id(def_id)
&& let expr = &tcx.hir_body(anon.body).value
&& let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
&& let Res::Def(DefKind::ConstParam, _def_id) = path.res
{
// Do not evaluate bare `const` params, as those would ICE and are only
// usable if `#![feature(generic_const_exprs)]` is enabled.
} else {
// Evaluate the constant proactively, to emit an error if the constant has
// an unconditional error. We only do so if the const has no type params.
if let Some(def_id) = field.value {
if !own_params_require_monomorphization {
let _ = tcx.const_eval_poly(def_id);
} else if tcx.features().default_field_values()
&& let Some(local_def_id) = def_id.as_local()
{
// Do not redundantly trigger lint if the feature is not actually available.

// We use the callsite span for default values that are actually macro calls
// into other crates, like for example `field: () = panic!(),`.
let field_span = tcx.def_span(def_id).source_callsite();
let mut multispan: MultiSpan = field_span.into();
multispan
.push_span_label(field_span, "this can't be const-evaluated until use");
let struct_start = tcx.def_span(item).shrink_to_lo();
multispan.push_span_context(tcx.def_span(field.did));
multispan.push_span_context(struct_start);
tcx.emit_node_span_lint(
UNEVALUATED_DEFAULT_FIELD_VALUE,
tcx.local_def_id_to_hir_id(local_def_id),
multispan,
UnevaluatedDefaultFieldValue {
field: field.name,
ty: with_no_trimmed_paths!(tcx.def_path_str(item)),
padding: tcx
.sess
.source_map()
.indentation_before(struct_start)
.unwrap_or_default(),
struct_start,
},
);
}
}
let field_id = field.did.expect_local();
Expand Down
21 changes: 21 additions & 0 deletions compiler/rustc_hir_analysis/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2157,3 +2157,24 @@ pub(crate) struct ParamInTyOfConstParam<'tcx> {
pub(crate) span: Span,
pub(crate) ty: Ty<'tcx>,
}

#[derive(Diagnostic)]
#[diag(
"field `{$field}` has a default value that is only checked when a value of `{$ty}` is \
constructed"
)]
#[help(
"structs and enums with type and const parameters only evaluate their default field values \
during construction, not eagerly when declared"
)]
pub(crate) struct UnevaluatedDefaultFieldValue {
pub(crate) field: Symbol,
pub(crate) ty: String,
#[suggestion(
"if this behavior is acceptable, allow the lint and preferably write a test relying on the default value",
code = "{padding}#[expect(unevaluated_default_field_value)]\n",
applicability = "maybe-incorrect"
)]
pub(crate) struct_start: Span,
pub(crate) padding: String,
}
34 changes: 34 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ pub mod hardwired {
UNCONDITIONAL_PANIC,
UNCONDITIONAL_RECURSION,
UNCOVERED_PARAM_IN_PROJECTION,
UNEVALUATED_DEFAULT_FIELD_VALUE,
UNEXPECTED_CFGS,
UNFULFILLED_LINT_EXPECTATIONS,
UNINHABITED_STATIC,
Expand Down Expand Up @@ -5922,3 +5923,36 @@ declare_lint! {
reason: fcw!(FutureReleaseError #163263),
};
}

declare_lint! {
/// The `unevaluated_default_field_value` lint detects when a struct has a field with a default
/// value that requires a type parameter to be evaluated, meaning that checking that default for
/// correctness is delayed to *instantiation*, instead of happening eagerly.
///
/// ### Example
///
/// ```rust,no_run
/// #![feature(default_field_values)]
///
/// struct Struct<const T: u8> {
/// field: u8 = 100 + T, // Because `T` is unknown at this point, the value won't be checked
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Const evaluation requires all values to be known. In the case of default field values, they
/// will get evaluated eagerly, but if there are any references to a const parameter, the
/// evaluation of the default will only occur when materializing a value of its owning struct.
/// For the example prior, if you wrote `let _: Struct<180> = Struct { .. };`, you would get a
/// const evaluation compile error.
///
/// This inconsistency can be confusing, and surprising, because the fields *are* sometimes
/// evaluated eagerly.
pub UNEVALUATED_DEFAULT_FIELD_VALUE,
Warn,
r#"detects incompatible uses of `#[sanitize(realtime = "nonblocking")]` on async functions"#,
Comment thread
estebank marked this conversation as resolved.
@feature_gate = default_field_values;
}
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,9 @@ pub fn emit_lint_base<'a, D: Diagnostic<'a> + 'a>(
for (label_span, label) in span.span_labels_raw() {
err.span.push_span_diag(*label_span, label.clone());
}
for span in span.span_context() {
err.span.push_span_context(*span);
}
}

if disable_suggestions {
Expand Down
8 changes: 4 additions & 4 deletions tests/ui/structs/default-field-values/failures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ pub struct Bar {

#[derive(Default)]
pub struct Qux<const C: i32> {
bar: S = Self::S,
baz: i32 = foo(),
bat: i32 = <Qux<{ C }> as T>::K,
bay: i32 = C,
bar: S = Self::S, //~ WARN
baz: i32 = foo(), //~ WARN
bat: i32 = <Qux<{ C }> as T>::K, //~ WARN
bay: i32 = C, //~ WARN
}

pub struct Rak(i32 = 42); //~ ERROR default fields are not supported in tuple structs
Expand Down
62 changes: 61 additions & 1 deletion tests/ui/structs/default-field-values/failures.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,66 @@ error: unions cannot have default field values
LL | y: f32 = 2.,
| ^^

warning: field `bar` has a default value that is only checked when a value of `Qux` is constructed
--> $DIR/failures.rs:22:14
|
LL | pub struct Qux<const C: i32> {
LL | bar: S = Self::S,
| ^^^^^^^ this can't be const-evaluated until use
|
= help: structs and enums with type and const parameters only evaluate their default field values during construction, not eagerly when declared
= note: `#[warn(unevaluated_default_field_value)]` on by default
help: if this behavior is acceptable, allow the lint and preferably write a test relying on the default value
|
LL + #[expect(unevaluated_default_field_value)]
LL | pub struct Qux<const C: i32> {
|

warning: field `baz` has a default value that is only checked when a value of `Qux` is constructed
--> $DIR/failures.rs:23:16
|
LL | pub struct Qux<const C: i32> {
LL | bar: S = Self::S,
LL | baz: i32 = foo(),
| ^^^^^ this can't be const-evaluated until use
|
= help: structs and enums with type and const parameters only evaluate their default field values during construction, not eagerly when declared
help: if this behavior is acceptable, allow the lint and preferably write a test relying on the default value
|
LL + #[expect(unevaluated_default_field_value)]
LL | pub struct Qux<const C: i32> {
|

warning: field `bat` has a default value that is only checked when a value of `Qux` is constructed
--> $DIR/failures.rs:24:16
|
LL | pub struct Qux<const C: i32> {
...
LL | bat: i32 = <Qux<{ C }> as T>::K,
| ^^^^^^^^^^^^^^^^^^^^ this can't be const-evaluated until use
|
= help: structs and enums with type and const parameters only evaluate their default field values during construction, not eagerly when declared
help: if this behavior is acceptable, allow the lint and preferably write a test relying on the default value
|
LL + #[expect(unevaluated_default_field_value)]
LL | pub struct Qux<const C: i32> {
|

warning: field `bay` has a default value that is only checked when a value of `Qux` is constructed
--> $DIR/failures.rs:25:16
|
LL | pub struct Qux<const C: i32> {
...
LL | bay: i32 = C,
| ^ this can't be const-evaluated until use
|
= help: structs and enums with type and const parameters only evaluate their default field values during construction, not eagerly when declared
help: if this behavior is acceptable, allow the lint and preferably write a test relying on the default value
|
LL + #[expect(unevaluated_default_field_value)]
LL | pub struct Qux<const C: i32> {
|

error[E0277]: the trait bound `S: Default` is not satisfied
--> $DIR/failures.rs:16:5
|
Expand Down Expand Up @@ -114,7 +174,7 @@ LL - let _ = Rak(.., 0);
LL + let _ = Rak(0);
|

error: aborting due to 10 previous errors
error: aborting due to 10 previous errors; 4 warnings emitted

Some errors have detailed explanations: E0061, E0277, E0308.
For more information about an error, try `rustc --explain E0061`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//@ build-fail
// https://github.com/rust-lang/rust/issues/146496
#![feature(default_field_values)]

struct Z<const X: usize> {
// Ensure that proper context is shown in lint.
multiline_field:
()
= { //~ WARN default value
f::<X>();
panic!();
//~^ ERROR: explicit panic
//~| ERROR: explicit panic
},
}

pub const fn f<const N: usize>() {
// *If* we const evaluated `Z.multiline_field` at definition, but then bailed because `f` needs
// to be const evaluated, commenting out this line would suddenly allow `f` to be evaluated and
// cause the panic in `multiline_field` to be reached.
let _ = [0u8; N];
}

const fn const_use_generically<const X: usize>() {
let x: Z<X> = Z { .. };
}

fn use_generically<const X: usize>() {
let x: Z<X> = Z { .. };
}

fn main() {
let x: Z<1> = Z { .. };
use_generically::<2>();
const_use_generically::<3>();
const { const_use_generically::<4>() };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
warning: field `multiline_field` has a default value that is only checked when a value of `Z` is constructed
--> $DIR/field-references-param-accurate-span.rs:9:15
|
LL | struct Z<const X: usize> {
LL | // Ensure that proper context is shown in lint.
LL | multiline_field:
LL | ()
LL | = {
| _______________^
LL | | f::<X>();
LL | | panic!();
LL | |
LL | |
LL | | },
| |_____________^ this can't be const-evaluated until use
|
= help: structs and enums with type and const parameters only evaluate their default field values during construction, not eagerly when declared
= note: `#[warn(unevaluated_default_field_value)]` on by default
help: if this behavior is acceptable, allow the lint and preferably write a test relying on the default value
|
LL + #[expect(unevaluated_default_field_value)]
LL | struct Z<const X: usize> {
|

error[E0080]: evaluation panicked: explicit panic
--> $DIR/field-references-param-accurate-span.rs:11:17
|
LL | panic!();
| ^^^^^^^^ evaluation of `Z::<1>::multiline_field::{constant#0}` failed here

note: erroneous constant encountered
--> $DIR/field-references-param-accurate-span.rs:33:19
|
LL | let x: Z<1> = Z { .. };
| ^^^^^^^^

note: erroneous constant encountered
--> $DIR/field-references-param-accurate-span.rs:33:19
|
LL | let x: Z<1> = Z { .. };
| ^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error[E0080]: evaluation panicked: explicit panic
--> $DIR/field-references-param-accurate-span.rs:11:17
|
LL | panic!();
| ^^^^^^^^ evaluation of `Z::<4>::multiline_field::{constant#0}` failed here

note: erroneous constant encountered
--> $DIR/field-references-param-accurate-span.rs:25:19
|
LL | let x: Z<X> = Z { .. };
| ^^^^^^^^

note: erroneous constant encountered
--> $DIR/field-references-param-accurate-span.rs:36:5
|
LL | const { const_use_generically::<4>() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: erroneous constant encountered
--> $DIR/field-references-param-accurate-span.rs:36:5
|
LL | const { const_use_generically::<4>() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: aborting due to 2 previous errors; 1 warning emitted

For more information about this error, try `rustc --explain E0080`.
Loading
Loading