Skip to content

Commit

Permalink
Introduce default_field_values feature
Browse files Browse the repository at this point in the history
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> { .. }
```
  • Loading branch information
estebank committed Sep 24, 2024
1 parent 648d024 commit 0f49c94
Show file tree
Hide file tree
Showing 58 changed files with 617 additions and 286 deletions.
1 change: 1 addition & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2995,6 +2995,7 @@ pub struct FieldDef {
pub ident: Option<Ident>,

pub ty: P<Ty>,
pub default: Option<AnonConst>,
pub is_placeholder: bool,
}

Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1048,12 +1048,13 @@ pub fn walk_flat_map_field_def<T: MutVisitor>(
visitor: &mut T,
mut fd: FieldDef,
) -> SmallVec<[FieldDef; 1]> {
let FieldDef { span, ident, vis, id, ty, attrs, is_placeholder: _ } = &mut fd;
let FieldDef { span, ident, vis, id, ty, attrs, is_placeholder: _, default } = &mut fd;
visitor.visit_id(id);
visit_attrs(visitor, attrs);
visitor.visit_vis(vis);
visit_opt(ident, |ident| visitor.visit_ident(ident));
visitor.visit_ty(ty);
visit_opt(default, |default| visitor.visit_anon_const(default));
visitor.visit_span(span);
smallvec![fd]
}
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,11 +934,12 @@ pub fn walk_struct_def<'a, V: Visitor<'a>>(
}

pub fn walk_field_def<'a, V: Visitor<'a>>(visitor: &mut V, field: &'a FieldDef) -> V::Result {
let FieldDef { attrs, id: _, span: _, vis, ident, ty, is_placeholder: _ } = field;
let FieldDef { attrs, id: _, span: _, vis, ident, ty, is_placeholder: _, default } = field;
walk_list!(visitor, visit_attribute, attrs);
try_visit!(visitor.visit_vis(vis));
visit_opt!(visitor, visit_ident, *ident);
try_visit!(visitor.visit_ty(ty));
visit_opt!(visitor, visit_anon_const, &*default);
V::Result::output()
}

Expand Down
12 changes: 7 additions & 5 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,14 @@ impl<'hir> LoweringContext<'_, 'hir> {
),
ExprKind::Struct(se) => {
let rest = match &se.rest {
StructRest::Base(e) => Some(self.lower_expr(e)),
StructRest::Base(e) => hir::Rest::Base(self.lower_expr(e)),
StructRest::Rest(sp) => {
let guar = self.dcx().emit_err(BaseExpressionDoubleDot { span: *sp });
Some(&*self.arena.alloc(self.expr_err(*sp, guar)))
if !self.tcx.features().default_field_values {
self.dcx().emit_err(BaseExpressionDoubleDot { span: *sp });
}
hir::Rest::DefaultFields(*sp)
}
StructRest::None => None,
StructRest::None => hir::Rest::None,
};
hir::ExprKind::Struct(
self.arena.alloc(self.lower_qpath(
Expand Down Expand Up @@ -1460,7 +1462,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::ExprKind::Struct(
self.arena.alloc(hir::QPath::LangItem(lang_item, self.lower_span(span))),
fields,
None,
hir::Rest::None,
)
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
None => Ident::new(sym::integer(index), self.lower_span(f.span)),
},
vis_span: self.lower_span(f.vis.span),
default: f.default.as_ref().map(|v| self.lower_anon_const_to_anon_const(v)),
ty,
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
gate_all!(explicit_tail_calls, "`become` expression is experimental");
gate_all!(generic_const_items, "generic const items are experimental");
gate_all!(default_field_values, "default values on `struct` fields aren't supported");
gate_all!(unnamed_fields, "unnamed fields are not yet fully implemented");
gate_all!(fn_delegation, "functions delegation is not yet fully implemented");
gate_all!(postfix_match, "postfix match is experimental");
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1146,7 +1146,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
expr: &hir::Expr<'_>,
) {
let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());
let hir::ExprKind::Struct(struct_qpath, fields, Some(base)) = expr.kind else { return };
let hir::ExprKind::Struct(struct_qpath, fields, hir::Rest::Base(base)) = expr.kind else {
return;
};
let hir::QPath::Resolved(_, path) = struct_qpath else { return };
let hir::def::Res::Def(_, def_id) = path.res else { return };
let Some(expr_ty) = typeck_results.node_type_opt(expr.hir_id) else { return };
Expand Down Expand Up @@ -1234,7 +1236,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
use_spans: Option<UseSpans<'tcx>>,
) {
if let hir::ExprKind::Struct(_, _, Some(_)) = expr.kind {
if let hir::ExprKind::Struct(_, _, hir::Rest::Base(_)) = expr.kind {
// We have `S { foo: val, ..base }`. In `check_aggregate_rvalue` we have a single
// `Location` that covers both the `S { ... }` literal, all of its fields and the
// `base`. If the move happens because of `S { foo: val, bar: base.bar }` the `expr`
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_builtin_macros/src/deriving/decodable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ where
let fields = fields
.iter()
.enumerate()
.map(|(i, &(ident, span))| {
.map(|(i, &(ident, span, _))| {
let arg = getarg(cx, span, ident.name, i);
cx.field_imm(span, ident, arg)
})
Expand Down
67 changes: 56 additions & 11 deletions compiler/rustc_builtin_macros/src/deriving/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,38 @@ pub(crate) fn expand_deriving_default(
trait_def.expand(cx, mitem, item, push)
}

fn default_call(cx: &ExtCtxt<'_>, span: Span) -> ast::ptr::P<ast::Expr> {
// Note that `kw::Default` is "default" and `sym::Default` is "Default"!
let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]);
cx.expr_call_global(span, default_ident, ThinVec::new())
}

fn default_struct_substructure(
cx: &ExtCtxt<'_>,
trait_span: Span,
substr: &Substructure<'_>,
summary: &StaticFields,
) -> BlockOrExpr {
// 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(), ThinVec::new());

let expr = match summary {
Unnamed(_, IsTuple::No) => cx.expr_ident(trait_span, substr.type_ident),
Unnamed(fields, IsTuple::Yes) => {
let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
let exprs = fields.iter().map(|sp| default_call(cx, *sp)).collect();
cx.expr_call_ident(trait_span, substr.type_ident, exprs)
}
Named(fields) => {
let default_fields = fields
.iter()
.map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
.map(|(ident, span, default_val)| {
let value = match default_val {
// We use `Default::default()`.
None => default_call(cx, *span),
// We use the field default const expression.
Some(val) => {
cx.expr(val.value.span, ast::ExprKind::ConstBlock(val.clone()))
}
};
cx.field_imm(*span, *ident, value)
})
.collect();
cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
}
Expand All @@ -93,10 +105,38 @@ fn default_enum_substructure(
} {
Ok(default_variant) => {
// 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,
]))
match &default_variant.data {
VariantData::Unit(_) => cx.expr_path(cx.path(default_variant.span, vec![
Ident::new(kw::SelfUpper, default_variant.span),
default_variant.ident,
])),
VariantData::Struct { fields, .. } => {
// This only happens if `#![feature(default_field_values)]`. We have validated
// all fields have default values in the definition.
let default_fields = fields
.iter()
.map(|field| {
cx.field_imm(field.span, field.ident.unwrap(), match &field.default {
// We use `Default::default()`.
None => default_call(cx, field.span),
// We use the field default const expression.
Some(val) => {
cx.expr(val.value.span, ast::ExprKind::ConstBlock(val.clone()))
}
})
})
.collect();
let path = cx.path(default_variant.span, vec![
Ident::new(kw::SelfUpper, default_variant.span),
default_variant.ident,
]);
cx.expr_struct(default_variant.span, path, default_fields)
}
// Logic error in `extract_default_variant`.
VariantData::Tuple(..) => {
cx.dcx().bug("encountered tuple variant annotated with `#[default]`")
}
}
}
Err(guar) => DummyResult::raw_expr(trait_span, Some(guar)),
};
Expand Down Expand Up @@ -156,7 +196,12 @@ fn extract_default_variant<'a>(
}
};

if !matches!(variant.data, VariantData::Unit(..)) {
if cx.ecfg.features.default_field_values
&& let VariantData::Struct { fields, .. } = &variant.data
&& fields.iter().all(|f| f.default.is_some())
{
// Allowed
} else if !matches!(variant.data, VariantData::Unit(..)) {
let guar = cx.dcx().emit_err(errors::NonUnitDefault { span: variant.ident.span });
return Err(guar);
}
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ pub(crate) use StaticFields::*;
pub(crate) use SubstructureFields::*;
use rustc_ast::ptr::P;
use rustc_ast::{
self as ast, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind, Generics,
Mutability, PatKind, VariantData,
self as ast, AnonConst, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind,
Generics, Mutability, PatKind, VariantData,
};
use rustc_attr as attr;
use rustc_expand::base::{Annotatable, ExtCtxt};
Expand Down Expand Up @@ -296,7 +296,7 @@ pub(crate) enum StaticFields {
/// Tuple and unit structs/enum variants like this.
Unnamed(Vec<Span>, IsTuple),
/// Normal structs/struct variants.
Named(Vec<(Ident, Span)>),
Named(Vec<(Ident, Span, Option<AnonConst>)>),
}

/// A summary of the possible sets of fields.
Expand Down Expand Up @@ -1443,7 +1443,7 @@ impl<'a> TraitDef<'a> {
for field in struct_def.fields() {
let sp = field.span.with_ctxt(self.span.ctxt());
match field.ident {
Some(ident) => named_idents.push((ident, sp)),
Some(ident) => named_idents.push((ident, sp, field.default.clone())),
_ => just_spans.push(sp),
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_expand/src/placeholders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ pub(crate) fn placeholder(
ty: ty(),
vis,
is_placeholder: true,
default: None,
}]),
AstFragmentKind::Variants => AstFragment::Variants(smallvec![ast::Variant {
attrs: Default::default(),
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,11 @@ declare_features! (
(unstable, custom_test_frameworks, "1.30.0", Some(50297)),
/// Allows declarative macros 2.0 (`macro`).
(unstable, decl_macro, "1.17.0", Some(39412)),
/// Allows the use of default values on struct definitions and the construction of struct
/// literals with the functional update syntax without a base.
// FIXME: change tracking issue number from RFCS repo to rust repo after RFC is accepted an
// tracking ticket is created.
(unstable, default_field_values, "CURRENT_RUSTC_VERSION", Some(3681)),
/// Allows using `#[deprecated_safe]` to deprecate the safeness of a function or trait
(unstable, deprecated_safe, "1.61.0", Some(94978)),
/// Allows having using `suggestion` in the `#[deprecated]` attribute.
Expand Down
41 changes: 31 additions & 10 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1873,7 +1873,12 @@ impl Expr<'_> {
base.can_have_side_effects()
}
ExprKind::Struct(_, fields, init) => {
fields.iter().map(|field| field.expr).chain(init).any(|e| e.can_have_side_effects())
let init_side_effects = match init {
Rest::Base(init) => init.can_have_side_effects(),
Rest::DefaultFields(_) | Rest::None => false,
};
fields.iter().map(|field| field.expr).any(|e| e.can_have_side_effects())
|| init_side_effects
}

ExprKind::Array(args)
Expand Down Expand Up @@ -1942,20 +1947,28 @@ impl Expr<'_> {
ExprKind::Path(QPath::Resolved(None, path2)),
) => path1.res == path2.res,
(
ExprKind::Struct(QPath::LangItem(LangItem::RangeTo, _), [val1], None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeTo, _), [val2], None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeTo, _), [val1], Rest::None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeTo, _), [val2], Rest::None),
)
| (
ExprKind::Struct(QPath::LangItem(LangItem::RangeToInclusive, _), [val1], None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeToInclusive, _), [val2], None),
ExprKind::Struct(
QPath::LangItem(LangItem::RangeToInclusive, _),
[val1],
Rest::None,
),
ExprKind::Struct(
QPath::LangItem(LangItem::RangeToInclusive, _),
[val2],
Rest::None,
),
)
| (
ExprKind::Struct(QPath::LangItem(LangItem::RangeFrom, _), [val1], None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeFrom, _), [val2], None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeFrom, _), [val1], Rest::None),
ExprKind::Struct(QPath::LangItem(LangItem::RangeFrom, _), [val2], Rest::None),
) => val1.expr.equivalent_for_indexing(val2.expr),
(
ExprKind::Struct(QPath::LangItem(LangItem::Range, _), [val1, val3], None),
ExprKind::Struct(QPath::LangItem(LangItem::Range, _), [val2, val4], None),
ExprKind::Struct(QPath::LangItem(LangItem::Range, _), [val1, val3], Rest::None),
ExprKind::Struct(QPath::LangItem(LangItem::Range, _), [val2, val4], Rest::None),
) => {
val1.expr.equivalent_for_indexing(val2.expr)
&& val3.expr.equivalent_for_indexing(val4.expr)
Expand Down Expand Up @@ -2112,7 +2125,7 @@ pub enum ExprKind<'hir> {
///
/// E.g., `Foo {x: 1, y: 2}`, or `Foo {x: 1, .. base}`,
/// where `base` is the `Option<Expr>`.
Struct(&'hir QPath<'hir>, &'hir [ExprField<'hir>], Option<&'hir Expr<'hir>>),
Struct(&'hir QPath<'hir>, &'hir [ExprField<'hir>], Rest<'hir>),

/// An array literal constructed from one repeated element.
///
Expand All @@ -2127,6 +2140,13 @@ pub enum ExprKind<'hir> {
Err(rustc_span::ErrorGuaranteed),
}

#[derive(Debug, Clone, Copy, HashStable_Generic)]
pub enum Rest<'hir> {
Base(&'hir Expr<'hir>),
DefaultFields(Span),
None,
}

/// Represents an optionally `Self`-qualified value/type path or associated extension.
///
/// To resolve the path to a `DefId`, call [`qpath_res`].
Expand Down Expand Up @@ -3181,6 +3201,7 @@ pub struct FieldDef<'hir> {
pub hir_id: HirId,
pub def_id: LocalDefId,
pub ty: &'hir Ty<'hir>,
pub default: Option<&'hir AnonConst>,
}

impl FieldDef<'_> {
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_hir/src/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,10 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
ExprKind::Struct(ref qpath, fields, ref optional_base) => {
try_visit!(visitor.visit_qpath(qpath, expression.hir_id, expression.span));
walk_list!(visitor, visit_expr_field, fields);
visit_opt!(visitor, visit_expr, optional_base);
match optional_base {
Rest::Base(base) => try_visit!(visitor.visit_expr(base)),
Rest::None | Rest::DefaultFields(_) => {}
}
}
ExprKind::Tup(subexpressions) => {
walk_list!(visitor, visit_expr, subexpressions);
Expand Down Expand Up @@ -1197,6 +1200,7 @@ pub fn walk_struct_def<'v, V: Visitor<'v>>(
pub fn walk_field_def<'v, V: Visitor<'v>>(visitor: &mut V, field: &'v FieldDef<'v>) -> V::Result {
try_visit!(visitor.visit_id(field.hir_id));
try_visit!(visitor.visit_ident(field.ident));
visit_opt!(visitor, visit_anon_const, &field.default);
visitor.visit_ty(field.ty)
}

Expand Down
7 changes: 4 additions & 3 deletions compiler/rustc_hir_analysis/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1082,12 +1082,12 @@ impl<'tcx> FieldUniquenessCheckContext<'tcx> {
}
}

fn lower_variant(
tcx: TyCtxt<'_>,
fn lower_variant<'tcx>(
tcx: TyCtxt<'tcx>,
variant_did: Option<LocalDefId>,
ident: Ident,
discr: ty::VariantDiscr,
def: &hir::VariantData<'_>,
def: &hir::VariantData<'tcx>,
adt_kind: ty::AdtKind,
parent_did: LocalDefId,
is_anonymous: bool,
Expand All @@ -1109,6 +1109,7 @@ fn lower_variant(
did: f.def_id.to_def_id(),
name: f.ident.name,
vis: tcx.visibility(f.def_id),
value: f.default.map(|v| v.def_id.to_def_id()),
})
.collect();
let recovered = match def {
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_hir_analysis/src/collect/type_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> {
return ty;
}

Node::Field(&hir::FieldDef { default: Some(_), ty, .. }) => {
ItemCtxt::new(tcx, def_id).lower_ty(ty)
}

_ => Ty::new_error_with_message(
tcx,
span,
Expand Down
Loading

0 comments on commit 0f49c94

Please sign in to comment.