Allow elem synthesized fields to take a default value (#2687)

This commit is contained in:
Sébastien d'Herbais de Thun 2023-11-17 10:39:08 +01:00 committed by GitHub
parent 624ff5cb7a
commit 5aaaacbf47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 19 deletions

View File

@ -484,19 +484,23 @@ pub struct FigureCaption {
/// The figure's supplement. /// The figure's supplement.
#[synthesized] #[synthesized]
#[default(None)]
pub supplement: Option<Content>, pub supplement: Option<Content>,
/// How to number the figure. /// How to number the figure.
#[synthesized] #[synthesized]
#[default(None)]
pub numbering: Option<Numbering>, pub numbering: Option<Numbering>,
/// The counter for the figure. /// The counter for the figure.
#[synthesized] #[synthesized]
#[default(None)]
pub counter: Option<Counter>, pub counter: Option<Counter>,
/// The figure's location. /// The figure's location.
#[internal] #[internal]
#[synthesized] #[synthesized]
#[default(None)]
pub figure_location: Option<Location>, pub figure_location: Option<Location>,
} }

View File

@ -270,7 +270,12 @@ impl Show for FootnoteEntry {
let default = StyleChain::default(); let default = StyleChain::default();
let numbering = note.numbering(default); let numbering = note.numbering(default);
let counter = Counter::of(FootnoteElem::elem()); let counter = Counter::of(FootnoteElem::elem());
let loc = note.location().unwrap(); let Some(loc) = note.location() else {
bail!(error!(self.span(), "footnote entry must have a location").with_hint(
"try using a query or a show rule to customize the footnote instead"
))
};
let num = counter.at(vt, loc)?.display(vt, numbering)?; let num = counter.at(vt, loc)?.display(vt, numbering)?;
let sup = SuperElem::new(num) let sup = SuperElem::new(num)
.pack() .pack()

View File

@ -489,7 +489,14 @@ impl Show for OutlineEntry {
// In case a user constructs an outline entry with an arbitrary element. // In case a user constructs an outline entry with an arbitrary element.
let Some(location) = elem.location() else { let Some(location) = elem.location() else {
if elem.can::<dyn Locatable>() && elem.can::<dyn Outlinable>() {
bail!(error!(self.span(), "{} must have a location", elem.func().name())
.with_hint(
"try using a query or a show rule to customize the outline.entry instead",
))
} else {
bail!(self.span(), "cannot outline {}", elem.func().name()) bail!(self.span(), "cannot outline {}", elem.func().name())
}
}; };
// The body text remains overridable. // The body text remains overridable.

View File

@ -129,7 +129,7 @@ struct Field {
borrowed: bool, borrowed: bool,
forced_variant: Option<usize>, forced_variant: Option<usize>,
parse: Option<BlockWithReturn>, parse: Option<BlockWithReturn>,
default: syn::Expr, default: Option<syn::Expr>,
} }
impl Field { impl Field {
@ -229,9 +229,7 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
fold: has_attr(&mut attrs, "fold"), fold: has_attr(&mut attrs, "fold"),
resolve: has_attr(&mut attrs, "resolve"), resolve: has_attr(&mut attrs, "resolve"),
parse: parse_attr(&mut attrs, "parse")?.flatten(), parse: parse_attr(&mut attrs, "parse")?.flatten(),
default: parse_attr::<syn::Expr>(&mut attrs, "default")? default: parse_attr::<syn::Expr>(&mut attrs, "default")?.flatten(),
.flatten()
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }),
vis: field.vis.clone(), vis: field.vis.clone(),
ident: ident.clone(), ident: ident.clone(),
ident_in: Ident::new(&format!("{}_in", ident), ident.span()), ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
@ -344,11 +342,17 @@ fn create(element: &Elem) -> TokenStream {
/// Create a field declaration. /// Create a field declaration.
fn create_field(field: &Field) -> TokenStream { fn create_field(field: &Field) -> TokenStream {
let Field { ident, ty, docs, required, .. } = field; let Field {
ident, ty, docs, required, synthesized, default, ..
} = field;
let ty = required let ty = required.then(|| quote! { #ty }).unwrap_or_else(|| {
.then(|| quote! { #ty }) if *synthesized && default.is_some() {
.unwrap_or_else(|| quote! { Option<#ty> }); quote! { #ty }
} else {
quote! { ::std::option::Option<#ty> }
}
});
quote! { quote! {
#[doc = #docs] #[doc = #docs]
#ident: #ty #ident: #ty
@ -457,9 +461,18 @@ fn create_new_func(element: &Elem) -> TokenStream {
let defaults = element let defaults = element
.fields .fields
.iter() .iter()
.filter(|field| !field.external && (field.synthesized || !field.inherent())) .filter(|field| !field.external && !field.inherent() && !field.synthesized)
.map(|Field { ident, .. }| { .map(|Field { ident, .. }| quote! { #ident: None });
let default_synthesized = element
.fields
.iter()
.filter(|field| !field.external && field.synthesized)
.map(|Field { ident, default, .. }| {
if let Some(expr) = default {
quote! { #ident: #expr }
} else {
quote! { #ident: None } quote! { #ident: None }
}
}); });
let label_and_location = element.unless_capability("Unlabellable", || { let label_and_location = element.unless_capability("Unlabellable", || {
@ -479,6 +492,7 @@ fn create_new_func(element: &Elem) -> TokenStream {
guards: ::std::vec::Vec::with_capacity(0), guards: ::std::vec::Vec::with_capacity(0),
#(#required,)* #(#required,)*
#(#defaults,)* #(#defaults,)*
#(#default_synthesized,)*
} }
} }
} }
@ -486,10 +500,19 @@ fn create_new_func(element: &Elem) -> TokenStream {
/// Create a builder pattern method for a field. /// Create a builder pattern method for a field.
fn create_with_field_method(field: &Field) -> TokenStream { fn create_with_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, with_ident, name, ty, .. } = field; let Field {
vis,
ident,
with_ident,
name,
ty,
synthesized,
default,
..
} = field;
let doc = format!("Set the [`{}`](Self::{}) field.", name, ident); let doc = format!("Set the [`{}`](Self::{}) field.", name, ident);
let set = if field.inherent() { let set = if field.inherent() || (*synthesized && default.is_some()) {
quote! { self.#ident = #ident; } quote! { self.#ident = #ident; }
} else { } else {
quote! { self.#ident = Some(#ident); } quote! { self.#ident = Some(#ident); }
@ -505,9 +528,19 @@ fn create_with_field_method(field: &Field) -> TokenStream {
/// Create a set-style method for a field. /// Create a set-style method for a field.
fn create_push_field_method(field: &Field) -> TokenStream { fn create_push_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, push_ident, name, ty, .. } = field; let Field {
vis,
ident,
push_ident,
name,
ty,
synthesized,
default,
..
} = field;
let doc = format!("Push the [`{}`](Self::{}) field.", name, ident); let doc = format!("Push the [`{}`](Self::{}) field.", name, ident);
let set = if field.inherent() && !field.synthesized { let set = if (field.inherent() && !synthesized) || (*synthesized && default.is_some())
{
quote! { self.#ident = #ident; } quote! { self.#ident = #ident; }
} else { } else {
quote! { self.#ident = Some(#ident); } quote! { self.#ident = Some(#ident); }
@ -561,10 +594,11 @@ fn create_field_in_method(element: &Elem, field: &Field) -> TokenStream {
/// Create an accessor methods for a field. /// Create an accessor methods for a field.
fn create_field_method(element: &Elem, field: &Field) -> TokenStream { fn create_field_method(element: &Elem, field: &Field) -> TokenStream {
let Field { vis, docs, ident, output, .. } = field; let Field { vis, docs, ident, output, .. } = field;
if field.inherent() && !field.synthesized { if (field.inherent() && !field.synthesized)
|| (field.synthesized && field.default.is_some())
{
quote! { quote! {
#[doc = #docs] #[doc = #docs]
#[track_caller]
#vis fn #ident(&self) -> &#output { #vis fn #ident(&self) -> &#output {
&self.#ident &self.#ident
} }
@ -618,6 +652,9 @@ fn create_style_chain_access(
(true, true, _) => quote! { get_resolve_fold }, (true, true, _) => quote! { get_resolve_fold },
}; };
let default = default
.clone()
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() });
let (init, default) = field.fold.then(|| (None, quote! { || #default })).unwrap_or_else(|| ( let (init, default) = field.fold.then(|| (None, quote! { || #default })).unwrap_or_else(|| (
Some(quote! { Some(quote! {
static DEFAULT: ::once_cell::sync::Lazy<#ty> = ::once_cell::sync::Lazy::new(|| #default); static DEFAULT: ::once_cell::sync::Lazy<#ty> = ::once_cell::sync::Lazy::new(|| #default);
@ -1141,6 +1178,9 @@ fn create_param_info(field: &Field) -> TokenStream {
let settable = field.settable(); let settable = field.settable();
let default_ty = if *fold { &output } else { &ty }; let default_ty = if *fold { &output } else { &ty };
let default = quote_option(&settable.then(|| { let default = quote_option(&settable.then(|| {
let default = default
.clone()
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() });
quote! { quote! {
|| { || {
let typed: #default_ty = #default; let typed: #default_ty = #default;

View File

@ -0,0 +1,40 @@
// Test that figure captions don't cause panics.
// Ref: false
---
// #2530
#figure(caption: [test])[].caption
---
// #2165
#figure.caption[]
---
// #2328
// Error: 4-43 footnote entry must have a location
// Hint: 4-43 try using a query or a show rule to customize the footnote instead
HI#footnote.entry(clearance: 2.5em)[There]
---
// Enum item (pre-emptive)
#enum.item(none)[Hello]
#enum.item(17)[Hello]
---
// List item (pre-emptive)
#list.item[Hello]
---
// Term item (pre-emptive)
#terms.item[Hello][World!]
---
// Outline entry (pre-emptive)
// Error: 2-48 cannot outline text
#outline.entry(1, [Hello], [World!], none, [1])
---
// Outline entry (pre-emptive, improved error)
// Error: 2-55 heading must have a location
// Hint: 2-55 try using a query or a show rule to customize the outline.entry instead
#outline.entry(1, heading[Hello], [World!], none, [1])