Delay errors for all show rules (#3323)

This commit is contained in:
Laurenz 2024-02-05 10:56:09 +01:00 committed by GitHub
parent 6a9866dc80
commit 302b870321
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 190 additions and 194 deletions

View File

@ -635,37 +635,35 @@ struct DisplayElem {
impl Show for Packed<DisplayElem> { impl Show for Packed<DisplayElem> {
#[typst_macros::time(name = "counter.display", span = self.span())] #[typst_macros::time(name = "counter.display", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(engine.delayed(|engine| { let location = self.location().unwrap();
let location = self.location().unwrap(); let counter = self.counter();
let counter = self.counter(); let numbering = self
let numbering = self .numbering()
.numbering() .clone()
.clone() .or_else(|| {
.or_else(|| { let CounterKey::Selector(Selector::Elem(func, _)) = counter.0 else {
let CounterKey::Selector(Selector::Elem(func, _)) = counter.0 else { return None;
return None; };
};
if func == HeadingElem::elem() { if func == HeadingElem::elem() {
HeadingElem::numbering_in(styles).clone() HeadingElem::numbering_in(styles).clone()
} else if func == FigureElem::elem() { } else if func == FigureElem::elem() {
FigureElem::numbering_in(styles).clone() FigureElem::numbering_in(styles).clone()
} else if func == EquationElem::elem() { } else if func == EquationElem::elem() {
EquationElem::numbering_in(styles).clone() EquationElem::numbering_in(styles).clone()
} else { } else {
None None
} }
}) })
.unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into()); .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into());
let state = if *self.both() { let state = if *self.both() {
counter.both(engine, location)? counter.both(engine, location)?
} else { } else {
counter.at(engine, location)? counter.at(engine, location)?
}; };
state.display(engine, &numbering) state.display(engine, &numbering)
}))
} }
} }

View File

@ -44,9 +44,7 @@ struct LocateElem {
impl Show for Packed<LocateElem> { impl Show for Packed<LocateElem> {
#[typst_macros::time(name = "locate", span = self.span())] #[typst_macros::time(name = "locate", span = self.span())]
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(engine.delayed(|engine| { let location = self.location().unwrap();
let location = self.location().unwrap(); Ok(self.func().call(engine, [location])?.display())
Ok(self.func().call(engine, [location])?.display())
}))
} }
} }

View File

@ -388,14 +388,12 @@ struct DisplayElem {
impl Show for Packed<DisplayElem> { impl Show for Packed<DisplayElem> {
#[typst_macros::time(name = "state.display", span = self.span())] #[typst_macros::time(name = "state.display", span = self.span())]
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(engine.delayed(|engine| { let location = self.location().unwrap();
let location = self.location().unwrap(); let value = self.state().at(engine, location)?;
let value = self.state().at(engine, location)?; Ok(match self.func() {
Ok(match self.func() { Some(func) => func.call(engine, [value])?.display(),
Some(func) => func.call(engine, [value])?.display(), None => value.display(),
None => value.display(), })
})
}))
} }
} }

View File

@ -225,51 +225,47 @@ impl Show for Packed<BibliographyElem> {
); );
} }
Ok(engine.delayed(|engine| { let span = self.span();
let span = self.span(); let works = Works::generate(engine.world, engine.introspector).at(span)?;
let works = Works::generate(engine.world, engine.introspector).at(span)?; let references = works
let references = works .references
.references .as_ref()
.as_ref() .ok_or("CSL style is not suitable for bibliographies")
.ok_or("CSL style is not suitable for bibliographies") .at(span)?;
.at(span)?;
let row_gutter = *BlockElem::below_in(styles).amount(); let row_gutter = *BlockElem::below_in(styles).amount();
if references.iter().any(|(prefix, _)| prefix.is_some()) { if references.iter().any(|(prefix, _)| prefix.is_some()) {
let mut cells = vec![]; let mut cells = vec![];
for (prefix, reference) in references { for (prefix, reference) in references {
cells.push( cells.push(
Packed::new(GridCell::new(prefix.clone().unwrap_or_default())) Packed::new(GridCell::new(prefix.clone().unwrap_or_default()))
.spanned(span), .spanned(span),
);
cells.push(
Packed::new(GridCell::new(reference.clone())).spanned(span),
);
}
seq.push(VElem::new(row_gutter).with_weakness(3).pack());
seq.push(
GridElem::new(cells)
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
.with_row_gutter(TrackSizings(smallvec![(row_gutter).into()]))
.pack()
.spanned(self.span()),
); );
} else { cells.push(Packed::new(GridCell::new(reference.clone())).spanned(span));
for (_, reference) in references {
seq.push(VElem::new(row_gutter).with_weakness(3).pack());
seq.push(reference.clone());
}
} }
let mut content = Content::sequence(seq); seq.push(VElem::new(row_gutter).with_weakness(3).pack());
if works.hanging_indent { seq.push(
content = content.styled(ParElem::set_hanging_indent(INDENT.into())); GridElem::new(cells)
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
.with_row_gutter(TrackSizings(smallvec![(row_gutter).into()]))
.pack()
.spanned(self.span()),
);
} else {
for (_, reference) in references {
seq.push(VElem::new(row_gutter).with_weakness(3).pack());
seq.push(reference.clone());
} }
}
Ok(content) let mut content = Content::sequence(seq);
})) if works.hanging_indent {
content = content.styled(ParElem::set_hanging_indent(INDENT.into()));
}
Ok(content)
} }
} }

View File

@ -152,17 +152,13 @@ pub struct CiteGroup {
impl Show for Packed<CiteGroup> { impl Show for Packed<CiteGroup> {
#[typst_macros::time(name = "cite", span = self.span())] #[typst_macros::time(name = "cite", span = self.span())]
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(engine.delayed(|engine| { let location = self.location().unwrap();
let location = self.location().unwrap(); let span = self.span();
let span = self.span(); Works::generate(engine.world, engine.introspector)
Works::generate(engine.world, engine.introspector) .at(span)?
.at(span)? .citations
.citations .get(&location)
.get(&location) .cloned()
.cloned() .unwrap_or_else(|| bail!(span, "failed to format citation (this is a bug)"))
.unwrap_or_else(|| {
bail!(span, "failed to format citation (this is a bug)")
})
}))
} }
} }

View File

@ -126,16 +126,14 @@ impl Packed<FootnoteElem> {
impl Show for Packed<FootnoteElem> { impl Show for Packed<FootnoteElem> {
#[typst_macros::time(name = "footnote", span = self.span())] #[typst_macros::time(name = "footnote", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(engine.delayed(|engine| { let loc = self.declaration_location(engine).at(self.span())?;
let loc = self.declaration_location(engine).at(self.span())?; let numbering = self.numbering(styles);
let numbering = self.numbering(styles); let counter = Counter::of(FootnoteElem::elem());
let counter = Counter::of(FootnoteElem::elem()); let num = counter.at(engine, loc)?.display(engine, numbering)?;
let num = counter.at(engine, loc)?.display(engine, numbering)?; let sup = SuperElem::new(num).pack().spanned(self.span());
let sup = SuperElem::new(num).pack().spanned(self.span()); let loc = loc.variant(1);
let loc = loc.variant(1); // Add zero-width weak spacing to make the footnote "sticky".
// Add zero-width weak spacing to make the footnote "sticky". Ok(HElem::hole().pack() + sup.linked(Destination::Location(loc)))
Ok(HElem::hole().pack() + sup.linked(Destination::Location(loc)))
}))
} }
} }

View File

@ -95,13 +95,11 @@ impl Show for Packed<LinkElem> {
let body = self.body().clone(); let body = self.body().clone();
let linked = match self.dest() { let linked = match self.dest() {
LinkTarget::Dest(dest) => body.linked(dest.clone()), LinkTarget::Dest(dest) => body.linked(dest.clone()),
LinkTarget::Label(label) => engine LinkTarget::Label(label) => {
.delayed(|engine| { let elem = engine.introspector.query_label(*label).at(self.span())?;
let elem = engine.introspector.query_label(*label).at(self.span())?; let dest = Destination::Location(elem.location().unwrap());
let dest = Destination::Location(elem.location().unwrap()); body.clone().linked(dest)
Ok(Some(body.clone().linked(dest))) }
})
.unwrap_or(body),
}; };
Ok(linked.styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false))))) Ok(linked.styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))))

View File

@ -163,78 +163,73 @@ impl Synthesize for Packed<RefElem> {
impl Show for Packed<RefElem> { impl Show for Packed<RefElem> {
#[typst_macros::time(name = "ref", span = self.span())] #[typst_macros::time(name = "ref", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(engine.delayed(|engine| { let target = *self.target();
let target = *self.target(); let elem = engine.introspector.query_label(target);
let elem = engine.introspector.query_label(target); let span = self.span();
let span = self.span();
if BibliographyElem::has(engine, target) { if BibliographyElem::has(engine, target) {
if elem.is_ok() { if elem.is_ok() {
bail!(span, "label occurs in the document and its bibliography"); bail!(span, "label occurs in the document and its bibliography");
}
return Ok(to_citation(self, engine, styles)?.pack().spanned(span));
} }
let elem = elem.at(span)?; return Ok(to_citation(self, engine, styles)?.pack().spanned(span));
}
if elem.func() == FootnoteElem::elem() { let elem = elem.at(span)?;
return Ok(FootnoteElem::with_label(target).pack().spanned(span));
}
let elem = elem.clone(); if elem.func() == FootnoteElem::elem() {
let refable = elem return Ok(FootnoteElem::with_label(target).pack().spanned(span));
.with::<dyn Refable>() }
.ok_or_else(|| {
if elem.can::<dyn Figurable>() {
eco_format!(
"cannot reference {} directly, try putting it into a figure",
elem.func().name()
)
} else {
eco_format!("cannot reference {}", elem.func().name())
}
})
.at(span)?;
let numbering = refable let elem = elem.clone();
.numbering() let refable = elem
.ok_or_else(|| { .with::<dyn Refable>()
.ok_or_else(|| {
if elem.can::<dyn Figurable>() {
eco_format!( eco_format!(
"cannot reference {} without numbering", "cannot reference {} directly, try putting it into a figure",
elem.func().name() elem.func().name()
) )
}) } else {
.hint(eco_format!( eco_format!("cannot reference {}", elem.func().name())
"you can enable {} numbering with `#set {}(numbering: \"1.\")`", }
elem.func().name(), })
if elem.func() == EquationElem::elem() { .at(span)?;
"math.equation"
} else {
elem.func().name()
}
))
.at(span)?;
let loc = elem.location().unwrap(); let numbering = refable
let numbers = refable .numbering()
.counter() .ok_or_else(|| {
.at(engine, loc)? eco_format!("cannot reference {} without numbering", elem.func().name())
.display(engine, &numbering.clone().trimmed())?; })
.hint(eco_format!(
"you can enable {} numbering with `#set {}(numbering: \"1.\")`",
elem.func().name(),
if elem.func() == EquationElem::elem() {
"math.equation"
} else {
elem.func().name()
}
))
.at(span)?;
let supplement = match self.supplement(styles).as_ref() { let loc = elem.location().unwrap();
Smart::Auto => refable.supplement(), let numbers = refable
Smart::Custom(None) => Content::empty(), .counter()
Smart::Custom(Some(supplement)) => supplement.resolve(engine, [elem])?, .at(engine, loc)?
}; .display(engine, &numbering.clone().trimmed())?;
let mut content = numbers; let supplement = match self.supplement(styles).as_ref() {
if !supplement.is_empty() { Smart::Auto => refable.supplement(),
content = supplement + TextElem::packed("\u{a0}") + content; Smart::Custom(None) => Content::empty(),
} Smart::Custom(Some(supplement)) => supplement.resolve(engine, [elem])?,
};
Ok(content.linked(Destination::Location(loc))) let mut content = numbers;
})) if !supplement.is_empty() {
content = supplement + TextElem::packed("\u{a0}") + content;
}
Ok(content.linked(Destination::Location(loc)))
} }
} }

View File

@ -92,18 +92,17 @@ pub fn realize(
meta = prepare(engine, &mut target, &mut map, styles)?; meta = prepare(engine, &mut target, &mut map, styles)?;
} }
// Apply the step. // Apply a step, if there is one.
let mut output = match step { let mut output = match step {
// Apply a user-defined show rule. Some(step) => {
Some(Step::Recipe(recipe, guard)) => show(engine, target, recipe, guard)?, // Errors in show rules don't terminate compilation immediately. We
// just continue with empty content for them and show all errors
// If the verdict picks this step, the `target` is guaranteed // together, if they remain by the end of the introspection loop.
// to have a built-in show rule. //
Some(Step::Builtin) => { // This way, we can ignore errors that only occur in earlier
target.with::<dyn Show>().unwrap().show(engine, styles.chain(&map))? // iterations and also show more useful errors at once.
engine.delayed(|engine| show(engine, target, step, styles.chain(&map)))
} }
// Nothing to do.
None => target, None => target,
}; };
@ -122,12 +121,12 @@ struct Verdict<'a> {
prepared: bool, prepared: bool,
/// A map of styles to apply to the element. /// A map of styles to apply to the element.
map: Styles, map: Styles,
/// An optional transformation step to apply to the element. /// An optional show rule transformation to apply to the element.
step: Option<Step<'a>>, step: Option<ShowStep<'a>>,
} }
/// An optional transformation step to apply to an element. /// An optional show rule transformation to apply to the element.
enum Step<'a> { enum ShowStep<'a> {
/// A user-defined transformational show rule. /// A user-defined transformational show rule.
Recipe(&'a Recipe, RecipeIndex), Recipe(&'a Recipe, RecipeIndex),
/// The built-in show rule. /// The built-in show rule.
@ -200,7 +199,7 @@ fn verdict<'a>(
// If we find a matching, unguarded replacement show rule, // If we find a matching, unguarded replacement show rule,
// remember it, but still continue searching for potential // remember it, but still continue searching for potential
// show-set styles that might change the verdict. // show-set styles that might change the verdict.
step = Some(Step::Recipe(recipe, index)); step = Some(ShowStep::Recipe(recipe, index));
// If we found a show rule and are already prepared, there is // If we found a show rule and are already prepared, there is
// nothing else to do, so we can just break. // nothing else to do, so we can just break.
@ -215,7 +214,7 @@ fn verdict<'a>(
// If we found no user-defined rule, also consider the built-in show rule. // If we found no user-defined rule, also consider the built-in show rule.
if step.is_none() && target.can::<dyn Show>() { if step.is_none() && target.can::<dyn Show>() {
step = Some(Step::Builtin); step = Some(ShowStep::Builtin);
} }
// If there's no nothing to do, there is also no verdict. // If there's no nothing to do, there is also no verdict.
@ -286,21 +285,30 @@ fn prepare(
Ok(None) Ok(None)
} }
/// Apply a user-defined show rule. /// Apply a step.
fn show( fn show(
engine: &mut Engine, engine: &mut Engine,
target: Content, target: Content,
recipe: &Recipe, step: ShowStep,
index: RecipeIndex, styles: StyleChain,
) -> SourceResult<Content> { ) -> SourceResult<Content> {
match &recipe.selector { match step {
Some(Selector::Regex(regex)) => { // Apply a user-defined show rule.
// If the verdict picks this rule, the `target` is guaranteed ShowStep::Recipe(recipe, guard) => match &recipe.selector {
// to be a text element. // If the selector is a regex, the `target` is guaranteed to be a
let text = target.into_packed::<TextElem>().unwrap(); // text element. This invokes special regex handling.
show_regex(engine, &text, regex, recipe, index) Some(Selector::Regex(regex)) => {
} let text = target.into_packed::<TextElem>().unwrap();
_ => recipe.apply(engine, target.guarded(index)), show_regex(engine, &text, regex, recipe, guard)
}
// Just apply the recipe.
_ => recipe.apply(engine, target.guarded(guard)),
},
// If the verdict picks this step, the `target` is guaranteed to have a
// built-in show rule.
ShowStep::Builtin => target.with::<dyn Show>().unwrap().show(engine, styles),
} }
} }

View File

@ -0,0 +1,11 @@
// Test that errors in show rules are delayed: There can be multiple at once.
---
// Error: 26-34 panicked with: "hey1"
#show heading: _ => panic("hey1")
// Error: 25-33 panicked with: "hey2"
#show strong: _ => panic("hey2")
= Hello
*strong*