Skip to content

Commit c70147f

Browse files
committed
Permit deriving default on enums with #[default]
1 parent fd853c0 commit c70147f

File tree

13 files changed

+371
-74
lines changed

13 files changed

+371
-74
lines changed

compiler/rustc_builtin_macros/src/deriving/default.rs

+166-31
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ use crate::deriving::generic::ty::*;
22
use crate::deriving::generic::*;
33

44
use rustc_ast::ptr::P;
5+
use rustc_ast::EnumDef;
6+
use rustc_ast::VariantData;
57
use rustc_ast::{Expr, MetaItem};
6-
use rustc_errors::struct_span_err;
8+
use rustc_errors::Applicability;
79
use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
10+
use rustc_span::symbol::Ident;
811
use rustc_span::symbol::{kw, sym};
912
use rustc_span::Span;
13+
use smallvec::SmallVec;
1014

1115
pub fn expand_deriving_default(
1216
cx: &mut ExtCtxt<'_>,
@@ -34,53 +38,184 @@ pub fn expand_deriving_default(
3438
attributes: attrs,
3539
is_unsafe: false,
3640
unify_fieldless_variants: false,
37-
combine_substructure: combine_substructure(Box::new(|a, b, c| {
38-
default_substructure(a, b, c)
41+
combine_substructure: combine_substructure(Box::new(|cx, trait_span, substr| {
42+
match substr.fields {
43+
StaticStruct(_, fields) => {
44+
default_struct_substructure(cx, trait_span, substr, fields)
45+
}
46+
StaticEnum(enum_def, _) => {
47+
if !cx.sess.features_untracked().derive_default_enum {
48+
rustc_session::parse::feature_err(
49+
cx.parse_sess(),
50+
sym::derive_default_enum,
51+
span,
52+
"deriving `Default` on enums is experimental",
53+
)
54+
.emit();
55+
}
56+
default_enum_substructure(cx, trait_span, enum_def)
57+
}
58+
_ => cx.span_bug(trait_span, "method in `derive(Default)`"),
59+
}
3960
})),
4061
}],
4162
associated_types: Vec::new(),
4263
};
4364
trait_def.expand(cx, mitem, item, push)
4465
}
4566

46-
fn default_substructure(
67+
fn default_struct_substructure(
4768
cx: &mut ExtCtxt<'_>,
4869
trait_span: Span,
4970
substr: &Substructure<'_>,
71+
summary: &StaticFields,
5072
) -> P<Expr> {
5173
// Note that `kw::Default` is "default" and `sym::Default` is "Default"!
5274
let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]);
5375
let default_call = |span| cx.expr_call_global(span, default_ident.clone(), Vec::new());
5476

55-
match *substr.fields {
56-
StaticStruct(_, ref summary) => match *summary {
57-
Unnamed(ref fields, is_tuple) => {
58-
if !is_tuple {
59-
cx.expr_ident(trait_span, substr.type_ident)
60-
} else {
61-
let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
62-
cx.expr_call_ident(trait_span, substr.type_ident, exprs)
63-
}
64-
}
65-
Named(ref fields) => {
66-
let default_fields = fields
67-
.iter()
68-
.map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
69-
.collect();
70-
cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
77+
match summary {
78+
Unnamed(ref fields, is_tuple) => {
79+
if !is_tuple {
80+
cx.expr_ident(trait_span, substr.type_ident)
81+
} else {
82+
let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
83+
cx.expr_call_ident(trait_span, substr.type_ident, exprs)
7184
}
72-
},
73-
StaticEnum(..) => {
74-
struct_span_err!(
75-
&cx.sess.parse_sess.span_diagnostic,
76-
trait_span,
77-
E0665,
78-
"`Default` cannot be derived for enums, only structs"
79-
)
85+
}
86+
Named(ref fields) => {
87+
let default_fields = fields
88+
.iter()
89+
.map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
90+
.collect();
91+
cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
92+
}
93+
}
94+
}
95+
96+
fn default_enum_substructure(
97+
cx: &mut ExtCtxt<'_>,
98+
trait_span: Span,
99+
enum_def: &EnumDef,
100+
) -> P<Expr> {
101+
let default_variant = match extract_default_variant(cx, enum_def, trait_span) {
102+
Ok(value) => value,
103+
Err(()) => return DummyResult::raw_expr(trait_span, true),
104+
};
105+
106+
// At this point, we know that there is exactly one variant with a `#[default]` attribute. The
107+
// attribute hasn't yet been validated.
108+
109+
if let Err(()) = validate_default_attribute(cx, default_variant) {
110+
return DummyResult::raw_expr(trait_span, true);
111+
}
112+
113+
// We now know there is exactly one unit variant with exactly one `#[default]` attribute.
114+
115+
cx.expr_path(cx.path(
116+
default_variant.span,
117+
vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident],
118+
))
119+
}
120+
121+
fn extract_default_variant<'a>(
122+
cx: &mut ExtCtxt<'_>,
123+
enum_def: &'a EnumDef,
124+
trait_span: Span,
125+
) -> Result<&'a rustc_ast::Variant, ()> {
126+
let default_variants: SmallVec<[_; 1]> = enum_def
127+
.variants
128+
.iter()
129+
.filter(|variant| cx.sess.contains_name(&variant.attrs, kw::Default))
130+
.collect();
131+
132+
let variant = match default_variants.as_slice() {
133+
[variant] => variant,
134+
[] => {
135+
cx.struct_span_err(trait_span, "no default declared")
136+
.help("make a unit variant default by placing `#[default]` above it")
137+
.emit();
138+
139+
return Err(());
140+
}
141+
[first, rest @ ..] => {
142+
cx.struct_span_err(trait_span, "multiple declared defaults")
143+
.span_label(first.span, "first default")
144+
.span_labels(rest.iter().map(|variant| variant.span), "additional default")
145+
.note("only one variant can be default")
146+
.emit();
147+
148+
return Err(());
149+
}
150+
};
151+
152+
if !matches!(variant.data, VariantData::Unit(..)) {
153+
cx.struct_span_err(variant.ident.span, "`#[default]` may only be used on unit variants")
154+
.help("consider a manual implementation of `Default`")
155+
.emit();
156+
157+
return Err(());
158+
}
159+
160+
if let Some(non_exhaustive_attr) = cx.sess.find_by_name(&variant.attrs, sym::non_exhaustive) {
161+
cx.struct_span_err(variant.ident.span, "default variant must be exhaustive")
162+
.span_label(non_exhaustive_attr.span, "declared `#[non_exhaustive]` here")
163+
.help("consider a manual implementation of `Default`")
80164
.emit();
81-
// let compilation continue
82-
DummyResult::raw_expr(trait_span, true)
165+
166+
return Err(());
167+
}
168+
169+
Ok(variant)
170+
}
171+
172+
fn validate_default_attribute(
173+
cx: &mut ExtCtxt<'_>,
174+
default_variant: &rustc_ast::Variant,
175+
) -> Result<(), ()> {
176+
let attrs: SmallVec<[_; 1]> =
177+
cx.sess.filter_by_name(&default_variant.attrs, kw::Default).collect();
178+
179+
let attr = match attrs.as_slice() {
180+
[attr] => attr,
181+
[] => cx.bug(
182+
"this method must only be called with a variant that has a `#[default]` attribute",
183+
),
184+
[first, rest @ ..] => {
185+
// FIXME(jhpratt) Do we want to perform this check? It doesn't exist
186+
// for `#[inline]`, `#[non_exhaustive]`, and presumably others.
187+
188+
let suggestion_text =
189+
if rest.len() == 1 { "try removing this" } else { "try removing these" };
190+
191+
cx.struct_span_err(default_variant.ident.span, "multiple `#[default]` attributes")
192+
.note("only one `#[default]` attribute is needed")
193+
.span_label(first.span, "`#[default]` used here")
194+
.span_label(rest[0].span, "`#[default]` used again here")
195+
.span_help(rest.iter().map(|attr| attr.span).collect::<Vec<_>>(), suggestion_text)
196+
// This would otherwise display the empty replacement, hence the otherwise
197+
// repetitive `.span_help` call above.
198+
.tool_only_multipart_suggestion(
199+
suggestion_text,
200+
rest.iter().map(|attr| (attr.span, String::new())).collect(),
201+
Applicability::MachineApplicable,
202+
)
203+
.emit();
204+
205+
return Err(());
83206
}
84-
_ => cx.span_bug(trait_span, "method in `derive(Default)`"),
207+
};
208+
if !attr.is_word() {
209+
cx.struct_span_err(attr.span, "`#[default]` attribute does not accept a value")
210+
.span_suggestion_hidden(
211+
attr.span,
212+
"try using `#[default]`",
213+
"#[default]".into(),
214+
Applicability::MaybeIncorrect,
215+
)
216+
.emit();
217+
218+
return Err(());
85219
}
220+
Ok(())
86221
}

compiler/rustc_feature/src/active.rs

+3
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,9 @@ declare_features! (
683683
/// Infer generic args for both consts and types.
684684
(active, generic_arg_infer, "1.55.0", Some(85077), None),
685685

686+
/// Allows `#[derive(Default)]` and `#[default]` on enums.
687+
(active, derive_default_enum, "1.56.0", Some(86985), None),
688+
686689
// -------------------------------------------------------------------------
687690
// feature-group-end: actual feature gates
688691
// -------------------------------------------------------------------------

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@ symbols! {
489489
deref_mut,
490490
deref_target,
491491
derive,
492+
derive_default_enum,
492493
destructuring_assignment,
493494
diagnostic,
494495
direct,

library/core/src/default.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ pub fn default<T: Default>() -> T {
161161
}
162162

163163
/// Derive macro generating an impl of the trait `Default`.
164-
#[rustc_builtin_macro]
164+
#[cfg_attr(not(bootstrap), rustc_builtin_macro(Default, attributes(default)))]
165+
#[cfg_attr(bootstrap, rustc_builtin_macro)]
165166
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
166167
#[allow_internal_unstable(core_intrinsics)]
167168
pub macro Default($item:item) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// run-pass
2+
3+
#![feature(derive_default_enum)]
4+
5+
// nb: does not impl Default
6+
#[derive(Debug, PartialEq)]
7+
struct NotDefault;
8+
9+
#[derive(Debug, Default, PartialEq)]
10+
enum Foo {
11+
#[default]
12+
Alpha,
13+
#[allow(dead_code)]
14+
Beta(NotDefault),
15+
}
16+
17+
fn main() {
18+
assert_eq!(Foo::default(), Foo::Alpha);
19+
}

src/test/ui/deriving/deriving-with-helper.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#![feature(lang_items)]
66
#![feature(no_core)]
77
#![feature(rustc_attrs)]
8+
#![feature(derive_default_enum)]
89

910
#![no_core]
1011

@@ -30,7 +31,7 @@ mod default {
3031
trait Sized {}
3132

3233
#[derive(Default)]
33-
struct S {
34+
enum S {
3435
#[default] // OK
35-
field: u8,
36+
Foo,
3637
}

src/test/ui/error-codes/E0665.rs

-8
This file was deleted.

src/test/ui/error-codes/E0665.stderr

-11
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#[derive(Default)] //~ ERROR deriving `Default` on enums is experimental
2+
enum Foo {
3+
#[default]
4+
Alpha,
5+
}
6+
7+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0658]: deriving `Default` on enums is experimental
2+
--> $DIR/feature-gate-derive_default_enum.rs:1:10
3+
|
4+
LL | #[derive(Default)]
5+
| ^^^^^^^
6+
|
7+
= note: see issue #86985 <https://github.com/rust-lang/rust/issues/86985> for more information
8+
= help: add `#![feature(derive_default_enum)]` to the crate attributes to enable
9+
= note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error: aborting due to previous error
12+
13+
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)