Skip to content

Commit 59392be

Browse files
committed
Introduce default_field_values feature
Initial implementation of `#[feature(default_field_values]`, proposed in rust-lang/rfcs#3681. Support default fields in enum struct variant Allow default values in an enum struct variant definition: ```rust pub enum Bar { Foo { bar: S = S, baz: i32 = 42 + 3, } } ``` Allow using `..` without a base on an enum struct variant ```rust Bar::Foo { .. } ``` `#[derive(Default)]` doesn't account for these as it is still gating `#[default]` only being allowed on unit variants. Support `#[derive(Default)]` on enum struct variants with all defaulted fields ```rust pub enum Bar { #[default] Foo { bar: S = S, baz: i32 = 42 + 3, } } ``` Check for missing fields in typeck instead of mir_build. Expand test with `const` param case (needs `generic_const_exprs` enabled). Properly instantiate MIR const The following works: ```rust struct S<A> { a: Vec<A> = Vec::new(), } S::<i32> { .. } ``` Add lint for default fields that will always fail const-eval We *allow* this to happen for API writers that might want to rely on users' getting a compile error when using the default field, different to the error that they would get when the field isn't default. We could change this to *always* error instead of being a lint, if we wanted. This will *not* catch errors for partially evaluated consts, like when the expression relies on a const parameter. Suggestions when encountering `Foo { .. }` without `#[feature(default_field_values)]`: - Suggest adding a base expression if there are missing fields. - Suggest enabling the feature if all the missing fields have optional values. - Suggest removing `..` if there are no missing fields.
1 parent dc71301 commit 59392be

13 files changed

+44
-29
lines changed

clippy_lints/src/default.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_
55
use rustc_data_structures::fx::FxHashSet;
66
use rustc_errors::Applicability;
77
use rustc_hir::def::Res;
8-
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
8+
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind, StructTailExpr};
99
use rustc_lint::{LateContext, LateLintPass};
1010
use rustc_middle::ty;
1111
use rustc_middle::ty::print::with_forced_trimmed_paths;
@@ -285,7 +285,7 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
285285
/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
286286
fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
287287
if let Some(parent) = get_parent_expr(cx, expr)
288-
&& let ExprKind::Struct(_, _, Some(base)) = parent.kind
288+
&& let ExprKind::Struct(_, _, StructTailExpr::Base(base)) = parent.kind
289289
{
290290
base.hir_id == expr.hir_id
291291
} else {

clippy_lints/src/default_numeric_fallback.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clippy_utils::source::snippet_opt;
44
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
55
use rustc_errors::Applicability;
66
use rustc_hir::intravisit::{Visitor, walk_expr, walk_stmt};
7-
use rustc_hir::{Block, Body, ConstContext, Expr, ExprKind, FnRetTy, HirId, Lit, Stmt, StmtKind};
7+
use rustc_hir::{Block, Body, ConstContext, Expr, ExprKind, FnRetTy, HirId, Lit, Stmt, StmtKind, StructTailExpr};
88
use rustc_lint::{LateContext, LateLintPass, LintContext};
99
use rustc_middle::lint::in_external_macro;
1010
use rustc_middle::ty::{self, FloatTy, IntTy, PolyFnSig, Ty};
@@ -197,7 +197,7 @@ impl<'tcx> Visitor<'tcx> for NumericFallbackVisitor<'_, 'tcx> {
197197
}
198198

199199
// Visit base with no bound.
200-
if let Some(base) = base {
200+
if let StructTailExpr::Base(base) = base {
201201
self.ty_bounds.push(ExplicitTyBound(false));
202202
self.visit_expr(base);
203203
self.ty_bounds.pop();

clippy_lints/src/inconsistent_struct_constructor.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use clippy_utils::fulfill_or_allowed;
33
use clippy_utils::source::snippet;
44
use rustc_data_structures::fx::FxHashMap;
55
use rustc_errors::Applicability;
6-
use rustc_hir::{self as hir, ExprKind};
6+
use rustc_hir::{self as hir, ExprKind, StructTailExpr};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_session::declare_lint_pass;
99
use rustc_span::symbol::Symbol;
@@ -95,7 +95,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
9595
}
9696
fields_snippet.push_str(&last_ident.to_string());
9797

98-
let base_snippet = if let Some(base) = base {
98+
let base_snippet = if let StructTailExpr::Base(base) = base {
9999
format!(", ..{}", snippet(cx, base.span, ".."))
100100
} else {
101101
String::new()

clippy_lints/src/init_numbered_fields.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
33
use rustc_errors::Applicability;
44
use rustc_hir::def::{DefKind, Res};
5-
use rustc_hir::{Expr, ExprKind};
5+
use rustc_hir::{Expr, ExprKind, StructTailExpr};
66
use rustc_lint::{LateContext, LateLintPass};
77
use rustc_session::declare_lint_pass;
88
use rustc_span::SyntaxContext;
@@ -43,7 +43,7 @@ declare_lint_pass!(NumberedFields => [INIT_NUMBERED_FIELDS]);
4343

4444
impl<'tcx> LateLintPass<'tcx> for NumberedFields {
4545
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
46-
if let ExprKind::Struct(path, fields @ [field, ..], None) = e.kind
46+
if let ExprKind::Struct(path, fields @ [field, ..], StructTailExpr::None) = e.kind
4747
// If the first character of any field is a digit it has to be a tuple.
4848
&& field.ident.as_str().as_bytes().first().is_some_and(u8::is_ascii_digit)
4949
// Type aliases can't be used as functions.

clippy_lints/src/loops/never_loop.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clippy_utils::higher::ForLoop;
55
use clippy_utils::macros::root_macro_call_first_node;
66
use clippy_utils::source::snippet;
77
use rustc_errors::Applicability;
8-
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
8+
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind, StructTailExpr};
99
use rustc_lint::LateContext;
1010
use rustc_span::{Span, sym};
1111
use std::iter::once;
@@ -164,7 +164,7 @@ fn never_loop_expr<'tcx>(
164164
},
165165
ExprKind::Struct(_, fields, base) => {
166166
let fields = never_loop_expr_all(cx, fields.iter().map(|f| f.expr), local_labels, main_loop_id);
167-
if let Some(base) = base {
167+
if let StructTailExpr::Base(base) = base {
168168
combine_seq(fields, || never_loop_expr(cx, base, local_labels, main_loop_id))
169169
} else {
170170
fields

clippy_lints/src/needless_update.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint;
2-
use rustc_hir::{Expr, ExprKind};
2+
use rustc_hir::{Expr, ExprKind, StructTailExpr};
33
use rustc_lint::{LateContext, LateLintPass};
44
use rustc_middle::ty;
55
use rustc_session::declare_lint_pass;
@@ -51,7 +51,7 @@ declare_lint_pass!(NeedlessUpdate => [NEEDLESS_UPDATE]);
5151

5252
impl<'tcx> LateLintPass<'tcx> for NeedlessUpdate {
5353
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
54-
if let ExprKind::Struct(_, fields, Some(base)) = expr.kind {
54+
if let ExprKind::Struct(_, fields, StructTailExpr::Base(base)) = expr.kind {
5555
let ty = cx.typeck_results().expr_ty(expr);
5656
if let ty::Adt(def, _) = ty.kind() {
5757
if fields.len() == def.non_enum_variant().fields.len()

clippy_lints/src/no_effect.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_errors::Applicability;
88
use rustc_hir::def::{DefKind, Res};
99
use rustc_hir::{
1010
BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, LocalSource, Node, PatKind, Stmt, StmtKind,
11-
UnsafeSource, is_range_literal,
11+
UnsafeSource, StructTailExpr, is_range_literal,
1212
};
1313
use rustc_infer::infer::TyCtxtInferExt as _;
1414
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -238,7 +238,10 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
238238
ExprKind::Struct(_, fields, ref base) => {
239239
!has_drop(cx, cx.typeck_results().expr_ty(expr))
240240
&& fields.iter().all(|field| has_no_effect(cx, field.expr))
241-
&& base.as_ref().is_none_or(|base| has_no_effect(cx, base))
241+
&& match &base {
242+
StructTailExpr::None | StructTailExpr::DefaultFields(_) => true,
243+
StructTailExpr::Base(base) => has_no_effect(cx, base),
244+
}
242245
},
243246
ExprKind::Call(callee, args) => {
244247
if let ExprKind::Path(ref qpath) = callee.kind {
@@ -342,6 +345,10 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
342345
if has_drop(cx, cx.typeck_results().expr_ty(expr)) {
343346
None
344347
} else {
348+
let base = match base {
349+
StructTailExpr::Base(base) => Some(base),
350+
StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
351+
};
345352
Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect())
346353
}
347354
},

clippy_lints/src/single_range_in_vec_init.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use clippy_utils::source::SpanRangeExt;
66
use clippy_utils::ty::implements_trait;
77
use rustc_ast::{LitIntType, LitKind, UintTy};
88
use rustc_errors::Applicability;
9-
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
9+
use rustc_hir::{Expr, ExprKind, LangItem, QPath, StructTailExpr};
1010
use rustc_lint::{LateContext, LateLintPass};
1111
use rustc_session::declare_lint_pass;
1212
use std::fmt::{self, Display, Formatter};
@@ -86,7 +86,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
8686
return;
8787
};
8888

89-
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], None) = inner_expr.kind else {
89+
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], StructTailExpr::None) = inner_expr.kind else {
9090
return;
9191
};
9292

clippy_lints/src/unnecessary_struct_initialization.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::source::snippet;
33
use clippy_utils::ty::is_copy;
44
use clippy_utils::{get_parent_expr, path_to_local};
5-
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, UnOp};
5+
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, UnOp, StructTailExpr};
66
use rustc_lint::{LateContext, LateLintPass};
77
use rustc_session::declare_lint_pass;
88

@@ -59,15 +59,15 @@ impl LateLintPass<'_> for UnnecessaryStruct {
5959
let field_path = same_path_in_all_fields(cx, expr, fields);
6060

6161
let sugg = match (field_path, base) {
62-
(Some(&path), None) => {
62+
(Some(&path), StructTailExpr::None | StructTailExpr::DefaultFields(_)) => {
6363
// all fields match, no base given
6464
path.span
6565
},
66-
(Some(path), Some(base)) if base_is_suitable(cx, expr, base) && path_matches_base(path, base) => {
66+
(Some(path), StructTailExpr::Base(base)) if base_is_suitable(cx, expr, base) && path_matches_base(path, base) => {
6767
// all fields match, has base: ensure that the path of the base matches
6868
base.span
6969
},
70-
(None, Some(base)) if fields.is_empty() && base_is_suitable(cx, expr, base) => {
70+
(None, StructTailExpr::Base(base)) if fields.is_empty() && base_is_suitable(cx, expr, base) => {
7171
// just the base, no explicit fields
7272
base.span
7373
},

clippy_lints/src/utils/author.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc_ast::ast::{LitFloatType, LitKind};
44
use rustc_data_structures::fx::FxHashMap;
55
use rustc_hir::{
66
self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind,
7-
ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind,
7+
ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind, StructTailExpr,
88
};
99
use rustc_lint::{LateContext, LateLintPass, LintContext};
1010
use rustc_session::declare_lint_pass;
@@ -598,7 +598,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
598598
},
599599
ExprKind::Struct(qpath, fields, base) => {
600600
bind!(self, qpath, fields);
601-
opt_bind!(self, base);
601+
let base = OptionPat::new(match base {
602+
StructTailExpr::Base(base) => Some(self.bind("base", base)),
603+
StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
604+
});
602605
kind!("Struct({qpath}, {fields}, {base})");
603606
self.qpath(qpath);
604607
self.slice(fields, |field| {

0 commit comments

Comments
 (0)