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| {

clippy_utils/src/higher.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::ty::is_type_diagnostic_item;
88

99
use rustc_ast::ast;
1010
use rustc_hir as hir;
11-
use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath};
11+
use rustc_hir::{Arm, Block, Expr, ExprKind, StructTailExpr, HirId, LoopSource, MatchSource, Node, Pat, QPath};
1212
use rustc_lint::LateContext;
1313
use rustc_span::{Span, sym, symbol};
1414

@@ -236,7 +236,7 @@ impl<'a> Range<'a> {
236236
limits: ast::RangeLimits::Closed,
237237
})
238238
},
239-
ExprKind::Struct(path, fields, None) => match (path, fields) {
239+
ExprKind::Struct(path, fields, StructTailExpr::None) => match (path, fields) {
240240
(QPath::LangItem(hir::LangItem::RangeFull, ..), []) => Some(Range {
241241
start: None,
242242
end: None,

clippy_utils/src/hir_utils.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rustc_hir::{
1010
AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr,
1111
ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime,
1212
LifetimeName, Pat, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitBoundModifiers, Ty,
13-
TyKind,
13+
TyKind, StructTailExpr,
1414
};
1515
use rustc_lexer::{TokenKind, tokenize};
1616
use rustc_lint::LateContext;
@@ -380,7 +380,12 @@ impl HirEqInterExpr<'_, '_, '_> {
380380
(ExprKind::Ret(l), ExprKind::Ret(r)) => both(l.as_ref(), r.as_ref(), |l, r| self.eq_expr(l, r)),
381381
(&ExprKind::Struct(l_path, lf, ref lo), &ExprKind::Struct(r_path, rf, ref ro)) => {
382382
self.eq_qpath(l_path, r_path)
383-
&& both(lo.as_ref(), ro.as_ref(), |l, r| self.eq_expr(l, r))
383+
&& match (lo, ro) {
384+
(StructTailExpr::Base(l),StructTailExpr::Base(r)) => self.eq_expr(l, r),
385+
(StructTailExpr::None, StructTailExpr::None) => true,
386+
(StructTailExpr::DefaultFields(_), StructTailExpr::DefaultFields(_)) => true,
387+
_ => false,
388+
}
384389
&& over(lf, rf, |l, r| self.eq_expr_field(l, r))
385390
},
386391
(&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
@@ -1017,7 +1022,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
10171022
self.hash_expr(f.expr);
10181023
}
10191024

1020-
if let Some(e) = *expr {
1025+
if let StructTailExpr::Base(e) = *expr {
10211026
self.hash_expr(e);
10221027
}
10231028
},

clippy_utils/src/visitors.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_hir::def::{CtorKind, DefKind, Res};
77
use rustc_hir::intravisit::{self, Visitor, walk_block, walk_expr};
88
use rustc_hir::{
99
AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, LetExpr, Pat, QPath,
10-
Safety, Stmt, UnOp, UnsafeSource,
10+
Safety, Stmt, UnOp, UnsafeSource, StructTailExpr,
1111
};
1212
use rustc_lint::LateContext;
1313
use rustc_middle::hir::nested_filter;
@@ -663,7 +663,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>(
663663
for field in fields {
664664
helper(typeck, true, field.expr, f)?;
665665
}
666-
if let Some(default) = default {
666+
if let StructTailExpr::Base(default) = default {
667667
helper(typeck, false, default, f)?;
668668
}
669669
},

0 commit comments

Comments
 (0)