Ensure synthesized field access never panics (#3310)

This commit is contained in:
Laurenz 2024-01-31 14:56:57 +01:00 committed by GitHub
parent 6999be9ab0
commit 426445edfc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 61 additions and 64 deletions

View File

@ -82,13 +82,13 @@ impl Elem {
/// Fields that can be configured with set rules. /// Fields that can be configured with set rules.
fn set_fields(&self) -> impl Iterator<Item = &Field> + Clone { fn set_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.construct_fields().filter(|field| !field.inherent()) self.construct_fields().filter(|field| !field.required)
} }
/// Fields that can be accessed from the style chain. /// Fields that can be accessed from the style chain.
fn style_fields(&self) -> impl Iterator<Item = &Field> + Clone { fn style_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.real_fields() self.real_fields()
.filter(|field| !field.inherent() && !field.synthesized) .filter(|field| !field.required && !field.synthesized)
} }
/// Fields that are visible to the user. /// Fields that are visible to the user.
@ -124,13 +124,6 @@ struct Field {
default: Option<syn::Expr>, default: Option<syn::Expr>,
} }
impl Field {
/// Whether the field is present on every instance of the element.
fn inherent(&self) -> bool {
self.required || (self.synthesized && self.default.is_some())
}
}
/// The `..` in `#[elem(..)]`. /// The `..` in `#[elem(..)]`.
struct Meta { struct Meta {
scope: bool, scope: bool,
@ -174,7 +167,10 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> {
if fields.iter().any(|field| field.ghost && !field.internal) if fields.iter().any(|field| field.ghost && !field.internal)
&& meta.capabilities.iter().all(|capability| capability != "Construct") && meta.capabilities.iter().all(|capability| capability != "Construct")
{ {
bail!(body.ident, "cannot have ghost fields and have `Construct` auto generated"); bail!(
body.ident,
"cannot have public ghost fields and an auto-generated constructor"
);
} }
Ok(Elem { Ok(Elem {
@ -231,16 +227,18 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
default: parse_attr::<syn::Expr>(&mut attrs, "default")?.flatten(), default: parse_attr::<syn::Expr>(&mut attrs, "default")?.flatten(),
}; };
if field.required || field.variadic { if field.required && field.synthesized {
if !field.positional { bail!(ident, "required fields cannot be synthesized");
bail!(ident, "inherent fields must be positional"); }
} else if field.fold || field.resolve || field.ghost || field.synthesized {
if (field.required || field.synthesized)
&& (field.default.is_some() || field.fold || field.resolve || field.ghost)
{
bail!( bail!(
ident, ident,
"inherent fields cannot be fold, resolve, ghost, or synthesized" "required and synthesized fields cannot be default, fold, resolve, or ghost"
); );
} }
}
if field.resolve { if field.resolve {
let ty = &field.ty; let ty = &field.ty;
@ -322,7 +320,7 @@ fn create_struct(element: &Elem) -> TokenStream {
/// Create a field declaration for the struct. /// Create a field declaration for the struct.
fn create_field(field: &Field) -> TokenStream { fn create_field(field: &Field) -> TokenStream {
let Field { ident, ty, .. } = field; let Field { ident, ty, .. } = field;
if field.inherent() { if field.required {
quote! { #ident: #ty } quote! { #ident: #ty }
} else { } else {
quote! { #ident: ::std::option::Option<#ty> } quote! { #ident: ::std::option::Option<#ty> }
@ -425,18 +423,12 @@ fn create_inherent_impl(element: &Elem) -> TokenStream {
fn create_new_func(element: &Elem) -> TokenStream { fn create_new_func(element: &Elem) -> TokenStream {
let params = element let params = element
.struct_fields() .struct_fields()
.filter(|field| field.inherent() && !field.synthesized) .filter(|field| field.required)
.map(|Field { ident, ty, .. }| quote! { #ident: #ty }); .map(|Field { ident, ty, .. }| quote! { #ident: #ty });
let fields = element.struct_fields().map(|field| { let fields = element.struct_fields().map(|field| {
let Field { ident, default, .. } = field; let ident = &field.ident;
if field.synthesized { if field.required {
if let Some(expr) = default {
quote! { #ident: #expr }
} else {
quote! { #ident: None }
}
} else if field.inherent() {
quote! { #ident } quote! { #ident }
} else { } else {
quote! { #ident: None } quote! { #ident: None }
@ -469,7 +461,7 @@ 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, .. } = field;
let doc = format!("Setter for the [`{name}`](Self::{ident}) field."); let doc = format!("Setter for the [`{name}`](Self::{ident}) field.");
let expr = if field.inherent() { let expr = if field.required {
quote! { #ident } quote! { #ident }
} else { } else {
quote! { Some(#ident) } quote! { Some(#ident) }
@ -487,7 +479,7 @@ fn create_push_field_method(field: &Field) -> TokenStream {
fn create_field_method(field: &Field) -> TokenStream { fn create_field_method(field: &Field) -> TokenStream {
let Field { vis, docs, ident, output, .. } = field; let Field { vis, docs, ident, output, .. } = field;
if field.inherent() { if field.required {
quote! { quote! {
#[doc = #docs] #[doc = #docs]
#vis fn #ident(&self) -> &#output { #vis fn #ident(&self) -> &#output {
@ -498,8 +490,8 @@ fn create_field_method(field: &Field) -> TokenStream {
quote! { quote! {
#[doc = #docs] #[doc = #docs]
#[track_caller] #[track_caller]
#vis fn #ident(&self) -> &#output { #vis fn #ident(&self) -> ::std::option::Option<&#output> {
self.#ident.as_ref().unwrap() self.#ident.as_ref()
} }
} }
} else { } else {
@ -655,7 +647,7 @@ fn create_param_info(field: &Field) -> TokenStream {
} = field; } = field;
let named = !positional; let named = !positional;
let settable = !field.inherent(); let settable = !field.required;
let default = if settable { let default = if settable {
let default = default let default = default
@ -718,13 +710,9 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
}); });
let fields = element.struct_fields().map(|field| { let fields = element.struct_fields().map(|field| {
let Field { ident, default, .. } = field; let ident = &field.ident;
if field.synthesized { if field.synthesized {
if let Some(expr) = default {
quote! { #ident: #expr }
} else {
quote! { #ident: None } quote! { #ident: None }
}
} else { } else {
quote! { #ident } quote! { #ident }
} }
@ -835,7 +823,7 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
let has_arms = visible_non_ghost().map(|field| { let has_arms = visible_non_ghost().map(|field| {
let Field { enum_ident, ident, .. } = field; let Field { enum_ident, ident, .. } = field;
let expr = if field.inherent() { let expr = if field.required {
quote! { true } quote! { true }
} else { } else {
quote! { self.#ident.is_some() } quote! { self.#ident.is_some() }
@ -845,10 +833,10 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
}); });
// Fields that can be accessed using the `field` method. // Fields that can be accessed using the `field` method.
let field_arms = visible_non_ghost().filter(|field| !field.ghost).map(|field| { let field_arms = visible_non_ghost().map(|field| {
let Field { enum_ident, ident, .. } = field; let Field { enum_ident, ident, .. } = field;
let expr = if field.inherent() { let expr = if field.required {
quote! { Some(#into_value(self.#ident.clone())) } quote! { Some(#into_value(self.#ident.clone())) }
} else { } else {
quote! { self.#ident.clone().map(#into_value) } quote! { self.#ident.clone().map(#into_value) }
@ -861,9 +849,9 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
let field_with_styles_arms = element.visible_fields().map(|field| { let field_with_styles_arms = element.visible_fields().map(|field| {
let Field { enum_ident, ident, .. } = field; let Field { enum_ident, ident, .. } = field;
let expr = if field.inherent() { let expr = if field.required {
quote! { Some(#into_value(self.#ident.clone())) } quote! { Some(#into_value(self.#ident.clone())) }
} else if field.synthesized && field.default.is_none() { } else if field.synthesized {
quote! { self.#ident.clone().map(#into_value) } quote! { self.#ident.clone().map(#into_value) }
} else { } else {
let value = create_style_chain_access( let value = create_style_chain_access(
@ -883,7 +871,7 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
let Field { ident, name, .. } = field; let Field { ident, name, .. } = field;
let string = quote! { #name.into() }; let string = quote! { #name.into() };
if field.inherent() { if field.required {
quote! { quote! {
fields.insert(#string, #into_value(self.#ident.clone())); fields.insert(#string, #into_value(self.#ident.clone()));
} }

View File

@ -331,7 +331,6 @@ pub struct PageElem {
/// Whether the page should be aligned to an even or odd page. /// Whether the page should be aligned to an even or odd page.
#[internal] #[internal]
#[synthesized] #[synthesized]
#[default(None)]
pub clear_to: Option<Parity>, pub clear_to: Option<Parity>,
} }

View File

@ -741,13 +741,19 @@ impl<'a> Generator<'a> {
driver.citation(CitationRequest::new( driver.citation(CitationRequest::new(
items, items,
style, style,
Some(locale(*first.lang(), *first.region())), Some(locale(
first.lang().copied().unwrap_or(Lang::ENGLISH),
first.region().copied().flatten(),
)),
&LOCALES, &LOCALES,
None, None,
)); ));
} }
let locale = locale(*self.bibliography.lang(), *self.bibliography.region()); let locale = locale(
self.bibliography.lang().copied().unwrap_or(Lang::ENGLISH),
self.bibliography.region().copied().flatten(),
);
// Add hidden items for everything if we should print the whole // Add hidden items for everything if we should print the whole
// bibliography. // bibliography.

View File

@ -93,7 +93,7 @@ impl LayoutRoot for Packed<DocumentElem> {
.to_styled() .to_styled()
.map_or(next, |(elem, _)| elem) .map_or(next, |(elem, _)| elem)
.to_packed::<PageElem>()? .to_packed::<PageElem>()?
.clear_to() .clear_to()?
}); });
let run = page.layout(engine, styles, &mut page_counter, extend_to)?; let run = page.layout(engine, styles, &mut page_counter, extend_to)?;
pages.extend(run); pages.extend(run);

View File

@ -371,7 +371,8 @@ impl Refable for Packed<FigureElem> {
fn counter(&self) -> Counter { fn counter(&self) -> Counter {
(**self) (**self)
.counter() .counter()
.clone() .cloned()
.flatten()
.unwrap_or_else(|| Counter::of(FigureElem::elem())) .unwrap_or_else(|| Counter::of(FigureElem::elem()))
} }
@ -393,7 +394,7 @@ impl Outlinable for Packed<FigureElem> {
let mut realized = caption.body().clone(); let mut realized = caption.body().clone();
if let ( if let (
Smart::Custom(Some(Supplement::Content(mut supplement))), Smart::Custom(Some(Supplement::Content(mut supplement))),
Some(counter), Some(Some(counter)),
Some(numbering), Some(numbering),
) = ( ) = (
(**self).supplement(StyleChain::default()).clone(), (**self).supplement(StyleChain::default()).clone(),
@ -512,23 +513,19 @@ 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>,
} }
@ -568,8 +565,13 @@ impl Show for Packed<FigureCaption> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let mut realized = self.body().clone(); let mut realized = self.body().clone();
if let (Some(mut supplement), Some(numbering), Some(counter), Some(location)) = ( if let (
self.supplement().clone(), Some(Some(mut supplement)),
Some(Some(numbering)),
Some(Some(counter)),
Some(Some(location)),
) = (
self.supplement().cloned(),
self.numbering(), self.numbering(),
self.counter(), self.counter(),
self.figure_location(), self.figure_location(),

View File

@ -398,16 +398,18 @@ impl Synthesize for Packed<RawElem> {
impl Show for Packed<RawElem> { impl Show for Packed<RawElem> {
#[typst_macros::time(name = "raw", span = self.span())] #[typst_macros::time(name = "raw", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let mut lines = EcoVec::with_capacity((2 * self.lines().len()).saturating_sub(1)); let lines = self.lines().map(|v| v.as_slice()).unwrap_or_default();
for (i, line) in self.lines().iter().enumerate() {
let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
for (i, line) in lines.iter().enumerate() {
if i != 0 { if i != 0 {
lines.push(LinebreakElem::new().pack()); seq.push(LinebreakElem::new().pack());
} }
lines.push(line.clone().pack()); seq.push(line.clone().pack());
} }
let mut realized = Content::sequence(lines); let mut realized = Content::sequence(seq);
if self.block(styles) { if self.block(styles) {
// Align the text before inserting it into the block. // Align the text before inserting it into the block.
realized = realized.aligned(self.align(styles).into()); realized = realized.aligned(self.align(styles).into());