Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RFC 3107: #[derive(Default)] on enums with a #[default] attribute #86735

Merged
merged 4 commits into from
Jul 28, 2021
Merged
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
257 changes: 230 additions & 27 deletions compiler/rustc_builtin_macros/src/deriving/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ use crate::deriving::generic::ty::*;
use crate::deriving::generic::*;

use rustc_ast::ptr::P;
use rustc_ast::walk_list;
use rustc_ast::EnumDef;
use rustc_ast::VariantData;
use rustc_ast::{Expr, MetaItem};
use rustc_errors::struct_span_err;
use rustc_errors::Applicability;
use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
use rustc_span::symbol::Ident;
use rustc_span::symbol::{kw, sym};
use rustc_span::Span;
use smallvec::SmallVec;

pub fn expand_deriving_default(
cx: &mut ExtCtxt<'_>,
Expand All @@ -15,6 +20,8 @@ pub fn expand_deriving_default(
item: &Annotatable,
push: &mut dyn FnMut(Annotatable),
) {
item.visit_with(&mut DetectNonVariantDefaultAttr { cx });

let inline = cx.meta_word(span, sym::inline);
let attrs = vec![cx.attribute(inline)];
let trait_def = TraitDef {
Expand All @@ -34,53 +41,249 @@ pub fn expand_deriving_default(
attributes: attrs,
is_unsafe: false,
unify_fieldless_variants: false,
combine_substructure: combine_substructure(Box::new(|a, b, c| {
default_substructure(a, b, c)
combine_substructure: combine_substructure(Box::new(|cx, trait_span, substr| {
match substr.fields {
StaticStruct(_, fields) => {
default_struct_substructure(cx, trait_span, substr, fields)
}
StaticEnum(enum_def, _) => {
if !cx.sess.features_untracked().derive_default_enum {
rustc_session::parse::feature_err(
cx.parse_sess(),
sym::derive_default_enum,
span,
"deriving `Default` on enums is experimental",
)
.emit();
}
default_enum_substructure(cx, trait_span, enum_def)
}
_ => cx.span_bug(trait_span, "method in `derive(Default)`"),
}
})),
}],
associated_types: Vec::new(),
};
trait_def.expand(cx, mitem, item, push)
}

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

match *substr.fields {
StaticStruct(_, ref summary) => match *summary {
Unnamed(ref fields, is_tuple) => {
if !is_tuple {
cx.expr_ident(trait_span, substr.type_ident)
} else {
let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
cx.expr_call_ident(trait_span, substr.type_ident, exprs)
}
match summary {
Unnamed(ref fields, is_tuple) => {
if !is_tuple {
cx.expr_ident(trait_span, substr.type_ident)
} else {
let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
cx.expr_call_ident(trait_span, substr.type_ident, exprs)
}
}
Named(ref fields) => {
let default_fields = fields
.iter()
.map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
.collect();
cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
}
}
}

fn default_enum_substructure(
cx: &mut ExtCtxt<'_>,
trait_span: Span,
enum_def: &EnumDef,
) -> P<Expr> {
let default_variant = match extract_default_variant(cx, enum_def, trait_span) {
Ok(value) => value,
Err(()) => return DummyResult::raw_expr(trait_span, true),
};

// At this point, we know that there is exactly one variant with a `#[default]` attribute. The
// attribute hasn't yet been validated.

if let Err(()) = validate_default_attribute(cx, default_variant) {
return DummyResult::raw_expr(trait_span, true);
}

// We now know there is exactly one unit variant with exactly one `#[default]` attribute.

cx.expr_path(cx.path(
default_variant.span,
vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident],
))
}

fn extract_default_variant<'a>(
cx: &mut ExtCtxt<'_>,
enum_def: &'a EnumDef,
trait_span: Span,
) -> Result<&'a rustc_ast::Variant, ()> {
let default_variants: SmallVec<[_; 1]> = enum_def
.variants
.iter()
.filter(|variant| cx.sess.contains_name(&variant.attrs, kw::Default))
.collect();
jhpratt marked this conversation as resolved.
Show resolved Hide resolved

let variant = match default_variants.as_slice() {
[variant] => variant,
[] => {
let possible_defaults = enum_def
.variants
.iter()
.filter(|variant| matches!(variant.data, VariantData::Unit(..)))
.filter(|variant| !cx.sess.contains_name(&variant.attrs, sym::non_exhaustive));

let mut diag = cx.struct_span_err(trait_span, "no default declared");
diag.help("make a unit variant default by placing `#[default]` above it");
for variant in possible_defaults {
// Suggest making each unit variant default.
diag.tool_only_span_suggestion(
variant.span,
&format!("make `{}` default", variant.ident),
format!("#[default] {}", variant.ident),
Applicability::MaybeIncorrect,
);
}
Named(ref fields) => {
let default_fields = fields
diag.emit();

return Err(());
}
[first, rest @ ..] => {
let mut diag = cx.struct_span_err(trait_span, "multiple declared defaults");
diag.span_label(first.span, "first default");
diag.span_labels(rest.iter().map(|variant| variant.span), "additional default");
diag.note("only one variant can be default");
for variant in &default_variants {
// Suggest making each variant already tagged default.
let suggestion = default_variants
.iter()
.map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
.filter_map(|v| {
if v.ident == variant.ident {
None
} else {
Some((cx.sess.find_by_name(&v.attrs, kw::Default)?.span, String::new()))
}
})
.collect();
cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)

diag.tool_only_multipart_suggestion(
&format!("make `{}` default", variant.ident),
suggestion,
Applicability::MaybeIncorrect,
);
}
},
StaticEnum(..) => {
struct_span_err!(
&cx.sess.parse_sess.span_diagnostic,
trait_span,
E0665,
"`Default` cannot be derived for enums, only structs"
diag.emit();

return Err(());
}
};

if !matches!(variant.data, VariantData::Unit(..)) {
cx.struct_span_err(
variant.ident.span,
"the `#[default]` attribute may only be used on unit enum variants",
)
.help("consider a manual implementation of `Default`")
.emit();

return Err(());
}

if let Some(non_exhaustive_attr) = cx.sess.find_by_name(&variant.attrs, sym::non_exhaustive) {
cx.struct_span_err(variant.ident.span, "default variant must be exhaustive")
.span_label(non_exhaustive_attr.span, "declared `#[non_exhaustive]` here")
.help("consider a manual implementation of `Default`")
.emit();

return Err(());
}

Ok(variant)
}

fn validate_default_attribute(
cx: &mut ExtCtxt<'_>,
default_variant: &rustc_ast::Variant,
) -> Result<(), ()> {
let attrs: SmallVec<[_; 1]> =
cx.sess.filter_by_name(&default_variant.attrs, kw::Default).collect();

let attr = match attrs.as_slice() {
[attr] => attr,
[] => cx.bug(
"this method must only be called with a variant that has a `#[default]` attribute",
),
[first, rest @ ..] => {
// FIXME(jhpratt) Do we want to perform this check? It doesn't exist
// for `#[inline]`, `#[non_exhaustive]`, and presumably others.

let suggestion_text =
if rest.len() == 1 { "try removing this" } else { "try removing these" };

cx.struct_span_err(default_variant.ident.span, "multiple `#[default]` attributes")
.note("only one `#[default]` attribute is needed")
.span_label(first.span, "`#[default]` used here")
.span_label(rest[0].span, "`#[default]` used again here")
.span_help(rest.iter().map(|attr| attr.span).collect::<Vec<_>>(), suggestion_text)
// This would otherwise display the empty replacement, hence the otherwise
// repetitive `.span_help` call above.
.tool_only_multipart_suggestion(
suggestion_text,
rest.iter().map(|attr| (attr.span, String::new())).collect(),
Applicability::MachineApplicable,
)
.emit();

return Err(());
}
};
if !attr.is_word() {
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
cx.struct_span_err(attr.span, "`#[default]` attribute does not accept a value")
.span_suggestion_hidden(
attr.span,
"try using `#[default]`",
"#[default]".into(),
Applicability::MaybeIncorrect,
)
.emit();
// let compilation continue
DummyResult::raw_expr(trait_span, true)

return Err(());
}
Ok(())
}

struct DetectNonVariantDefaultAttr<'a, 'b> {
cx: &'a ExtCtxt<'b>,
}

impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, 'b> {
fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) {
if attr.has_name(kw::Default) {
self.cx
.struct_span_err(
attr.span,
"the `#[default]` attribute may only be used on unit enum variants",
)
.emit();
}
jhpratt marked this conversation as resolved.
Show resolved Hide resolved

rustc_ast::visit::walk_attribute(self, attr);
}
fn visit_variant(&mut self, v: &'a rustc_ast::Variant) {
self.visit_ident(v.ident);
self.visit_vis(&v.vis);
self.visit_variant_data(&v.data);
walk_list!(self, visit_anon_const, &v.disr_expr);
for attr in &v.attrs {
rustc_ast::visit::walk_attribute(self, attr);
}
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
_ => cx.span_bug(trait_span, "method in `derive(Default)`"),
}
}
4 changes: 3 additions & 1 deletion compiler/rustc_error_codes/src/error_codes/E0665.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#### Note: this error code is no longer emitted by the compiler.

The `Default` trait was derived on an enum.

Erroneous code example:

```compile_fail,E0665
```compile_fail
#[derive(Default)]
enum Food {
Sweet,
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_feature/src/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,9 @@ declare_features! (
/// Infer generic args for both consts and types.
(active, generic_arg_infer, "1.55.0", Some(85077), None),

/// Allows `#[derive(Default)]` and `#[default]` on enums.
(active, derive_default_enum, "1.56.0", Some(86985), None),

// -------------------------------------------------------------------------
// feature-group-end: actual feature gates
// -------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ symbols! {
deref_mut,
deref_target,
derive,
derive_default_enum,
destructuring_assignment,
diagnostic,
direct,
Expand Down
3 changes: 2 additions & 1 deletion library/core/src/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ pub fn default<T: Default>() -> T {
}

/// Derive macro generating an impl of the trait `Default`.
#[rustc_builtin_macro]
#[cfg_attr(not(bootstrap), rustc_builtin_macro(Default, attributes(default)))]
#[cfg_attr(bootstrap, rustc_builtin_macro)]
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
#[allow_internal_unstable(core_intrinsics)]
pub macro Default($item:item) {
Expand Down
19 changes: 19 additions & 0 deletions src/test/ui/deriving/deriving-default-enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// run-pass

#![feature(derive_default_enum)]

// nb: does not impl Default
#[derive(Debug, PartialEq)]
struct NotDefault;

#[derive(Debug, Default, PartialEq)]
enum Foo {
#[default]
Alpha,
#[allow(dead_code)]
Beta(NotDefault),
}

fn main() {
assert_eq!(Foo::default(), Foo::Alpha);
}
5 changes: 3 additions & 2 deletions src/test/ui/deriving/deriving-with-helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#![feature(lang_items)]
#![feature(no_core)]
#![feature(rustc_attrs)]
#![feature(derive_default_enum)]

#![no_core]

Expand All @@ -30,7 +31,7 @@ mod default {
trait Sized {}

#[derive(Default)]
struct S {
enum S {
#[default] // OK
field: u8,
Foo,
}
8 changes: 0 additions & 8 deletions src/test/ui/error-codes/E0665.rs

This file was deleted.

Loading