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

default_field_values grammar #18938

Open
sbant opened this issue Jan 14, 2025 · 1 comment
Open

default_field_values grammar #18938

sbant opened this issue Jan 14, 2025 · 1 comment
Labels
A-mir A-parser parser issues C-feature Category: feature request E-has-instructions Issue has some instructions and pointers to code to get started

Comments

@sbant
Copy link

sbant commented Jan 14, 2025

Allow struct definitions to provide default values for individual fields and thereby allowing those to be omitted from initializers.

https://rust-lang.github.io/rfcs/3681-default-field-values.html

dtolnay/syn#1774

@sbant sbant added the C-feature Category: feature request label Jan 14, 2025
@lnicola lnicola added A-parser parser issues A-mir labels Jan 14, 2025
@Veykril
Copy link
Member

Veykril commented Jan 15, 2025

Dropping some pointers here (for anyone interested, no need to do all of this at once, just the parsing part on its own for now is fine)

So the parser needs to be adjusted as follows:

  • Update the grammar
    RecordField =
    Attr* Visibility?
    Name ':' Type
  • run cargo codegen grammar to update the syntax tree
  • Adjust the parser for field defs
    fn record_field(p: &mut Parser<'_>) {
    let m = p.start();
    // test record_field_attrs
    // struct S { #[attr] f: f32 }
    attributes::outer_attrs(p);
    opt_visibility(p, false);
    if p.at(IDENT) {
    name(p);
    p.expect(T![:]);
    types::type_(p);
    m.complete(p, RECORD_FIELD);
    } else {
    m.abandon(p);
    p.err_and_bump("expected field declaration");
    }
    }
  • And here for constructor expressions
    pub(crate) fn record_expr_field_list(p: &mut Parser<'_>) {
    assert!(p.at(T!['{']));
    let m = p.start();
    p.bump(T!['{']);
    while !p.at(EOF) && !p.at(T!['}']) {
    let m = p.start();
    // test record_literal_field_with_attr
    // fn main() {
    // S { #[cfg(test)] field: 1 }
    // }
    attributes::outer_attrs(p);
    match p.current() {
    IDENT | INT_NUMBER if p.nth_at(1, T![::]) => {
    // test_err record_literal_missing_ellipsis_recovery
    // fn main() {
    // S { S::default() };
    // S { 0::default() };
    // }
    m.abandon(p);
    p.expect(T![..]);
    expr(p);
    }
    IDENT | INT_NUMBER if p.nth_at(1, T![..]) => {
    // test_err record_literal_before_ellipsis_recovery
    // fn main() {
    // S { field ..S::default() }
    // S { 0 ..S::default() }
    // }
    name_ref_or_index(p);
    p.error("expected `:`");
    m.complete(p, RECORD_EXPR_FIELD);
    }
    IDENT | INT_NUMBER => {
    // test_err record_literal_field_eq_recovery
    // fn main() {
    // S { field = foo }
    // S { 0 = foo }
    // }
    if p.nth_at(1, T![:]) {
    name_ref_or_index(p);
    p.bump(T![:]);
    } else if p.nth_at(1, T![=]) {
    name_ref_or_index(p);
    p.err_and_bump("expected `:`");
    }
    expr(p);
    m.complete(p, RECORD_EXPR_FIELD);
    }
    T![.] if p.at(T![..]) => {
    m.abandon(p);
    p.bump(T![..]);
    // test destructuring_assignment_struct_rest_pattern
    // fn foo() {
    // S { .. } = S {};
    // }
    // We permit `.. }` on the left-hand side of a destructuring assignment.
    if !p.at(T!['}']) {
    expr(p);
    if p.at(T![,]) {
    // test_err comma_after_functional_update_syntax
    // fn foo() {
    // S { ..x, };
    // S { ..x, a: 0 }
    // }
    // Do not bump, so we can support additional fields after this comma.
    p.error("cannot use a comma after the base struct");
    }
    }
    }
    T!['{'] => {
    error_block(p, "expected a field");
    m.abandon(p);
    }
    _ => {
    p.err_and_bump("expected identifier");
    m.abandon(p);
    }
    }
    if !p.at(T!['}']) {
    p.expect(T![,]);
    }
    }
    p.expect(T!['}']);
    m.complete(p, RECORD_EXPR_FIELD_LIST);
    }

The trickier part is that all fields now become potential bodies which still kind of struggle to represent properly, generally we'd need to add a new variant to

/// The defs which have a body.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DefWithBodyId {
FunctionId(FunctionId),
StaticId(StaticId),
ConstId(ConstId),
InTypeConstId(InTypeConstId),
VariantId(EnumVariantId),
}
that represents struct fields FieldId(FieldId). Then the compiler should guide you through most changes required.

Adjust the default derive

fn default_expand(
db: &dyn ExpandDatabase,
span: Span,
tt: &tt::TopSubtree,
) -> ExpandResult<tt::TopSubtree> {
let krate = &dollar_crate(span);
expand_simple_derive(db, span, tt, quote! {span => #krate::default::Default }, |adt| {
let body = match &adt.shape {
AdtShape::Struct(fields) => {
let name = &adt.name;
fields.as_pattern_map(
quote!(span =>#name),
span,
|_| quote!(span =>#krate::default::Default::default()),
)
}
AdtShape::Enum { default_variant, variants } => {
if let Some(d) = default_variant {
let (name, fields) = &variants[*d];
let adt_name = &adt.name;
fields.as_pattern_map(
quote!(span =>#adt_name :: #name),
span,
|_| quote!(span =>#krate::default::Default::default()),
)
} else {
// FIXME: Return expand error here
quote!(span =>)
}
}
AdtShape::Union => {
// FIXME: Return expand error here
quote!(span =>)
}
};
quote! {span =>
fn default() -> Self {
#body
}
}
})
}

@Veykril Veykril added the E-has-instructions Issue has some instructions and pointers to code to get started label Jan 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-mir A-parser parser issues C-feature Category: feature request E-has-instructions Issue has some instructions and pointers to code to get started
Projects
None yet
Development

No branches or pull requests

3 participants