mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Ensure synthesized field access never panics (#3310)
This commit is contained in:
parent
6999be9ab0
commit
426445edfc
@ -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()));
|
||||||
}
|
}
|
||||||
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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(),
|
||||||
|
@ -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());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user