diff --git a/crates/typst/src/diag.rs b/crates/typst/src/diag.rs index 4634f41ed..41339dff5 100644 --- a/crates/typst/src/diag.rs +++ b/crates/typst/src/diag.rs @@ -18,69 +18,105 @@ use crate::{World, WorldExt}; /// `StrResult`. If called with a span, a string and format args, returns /// a `SourceResult`. /// +/// You can also emit hints with the `; hint: "..."` syntax. +/// /// ``` /// bail!("bailing with a {}", "string result"); /// bail!(span, "bailing with a {}", "source result"); +/// bail!( +/// span, "bailing with a {}", "source result"; +/// hint: "hint 1" +/// ); +/// bail!( +/// span, "bailing with a {}", "source result"; +/// hint: "hint 1"; +/// hint: "hint 2"; +/// ); /// ``` #[macro_export] #[doc(hidden)] macro_rules! __bail { + // For bail!("just a {}", "string") ($fmt:literal $(, $arg:expr)* $(,)?) => { - return Err($crate::diag::eco_format!($fmt, $($arg),*)) + return Err($crate::diag::error!( + $fmt, $($arg),* + )) }; + // For bail!(error!(..)) ($error:expr) => { return Err(::ecow::eco_vec![$error]) }; - ($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => { - return Err(::ecow::eco_vec![$crate::diag::SourceDiagnostic::error( - $span, - $crate::diag::eco_format!($fmt, $($arg),*), - )]) + // For bail(span, ...) + ($($tts:tt)*) => { + return Err(::ecow::eco_vec![$crate::diag::error!($($tts)*)]) }; } -#[doc(inline)] -pub use crate::__bail as bail; -#[doc(inline)] -pub use crate::__error as error; -#[doc(inline)] -pub use crate::__warning as warning; - -#[doc(hidden)] -pub use ecow::eco_format; -#[doc(hidden)] -pub use ecow::EcoString; - /// Construct an [`EcoString`] or [`SourceDiagnostic`] with severity `Error`. #[macro_export] #[doc(hidden)] macro_rules! __error { + // For bail!("just a {}", "string"). ($fmt:literal $(, $arg:expr)* $(,)?) => { $crate::diag::eco_format!($fmt, $($arg),*) }; - ($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => { + // For bail!(span, ...) + ( + $span:expr, $fmt:literal $(, $arg:expr)* + $(; hint: $hint:literal $(, $hint_arg:expr)*)* + $(,)? + ) => { $crate::diag::SourceDiagnostic::error( $span, $crate::diag::eco_format!($fmt, $($arg),*), - ) + ) $(.with_hint($crate::diag::eco_format!($hint, $($hint_arg),*)))* }; } /// Construct a [`SourceDiagnostic`] with severity `Warning`. +/// +/// You can also emit hints with the `; hint: "..."` syntax. +/// +/// ``` +/// warning!(span, "warning with a {}", "source result"); +/// warning!( +/// span, "warning with a {}", "source result"; +/// hint: "hint 1" +/// ); +/// warning!( +/// span, "warning with a {}", "source result"; +/// hint: "hint 1"; +/// hint: "hint 2"; +/// ); +/// ``` #[macro_export] #[doc(hidden)] macro_rules! __warning { - ($span:expr, $fmt:literal $(, $arg:expr)* $(,)?) => { + ( + $span:expr, + $fmt:literal $(, $arg:expr)* + $(; hint: $hint:literal $(, $hint_arg:expr)*)* + $(,)? + ) => { $crate::diag::SourceDiagnostic::warning( $span, $crate::diag::eco_format!($fmt, $($arg),*), - ) + ) $(.with_hint($crate::diag::eco_format!($hint, $($hint_arg),*)))* }; } +#[rustfmt::skip] +#[doc(inline)] +pub use { + crate::__bail as bail, + crate::__error as error, + crate::__warning as warning, + ecow::{eco_format, EcoString}, +}; + /// A result that can carry multiple source errors. pub type SourceResult = Result>; diff --git a/crates/typst/src/eval/markup.rs b/crates/typst/src/eval/markup.rs index a40b978e0..ad9cd9c15 100644 --- a/crates/typst/src/eval/markup.rs +++ b/crates/typst/src/eval/markup.rs @@ -139,11 +139,12 @@ impl Eval for ast::Strong<'_> { fn eval(self, vm: &mut Vm) -> SourceResult { let body = self.body(); if body.exprs().next().is_none() { - vm.engine.tracer.warn( - warning!(self.span(), "no text within stars").with_hint( - "using multiple consecutive stars (e.g. **) has no additional effect", - ), - ); + vm.engine + .tracer + .warn(warning!( + self.span(), "no text within stars"; + hint: "using multiple consecutive stars (e.g. **) has no additional effect", + )); } Ok(StrongElem::new(body.eval(vm)?).pack()) @@ -159,9 +160,10 @@ impl Eval for ast::Emph<'_> { if body.exprs().next().is_none() { vm.engine .tracer - .warn(warning!(self.span(), "no text within underscores").with_hint( - "using multiple consecutive underscores (e.g. __) has no additional effect" - )); + .warn(warning!( + self.span(), "no text within underscores"; + hint: "using multiple consecutive underscores (e.g. __) has no additional effect" + )); } Ok(EmphElem::new(body.eval(vm)?).pack()) diff --git a/crates/typst/src/foundations/version.rs b/crates/typst/src/foundations/version.rs index 80a39fe48..98ad598b7 100644 --- a/crates/typst/src/foundations/version.rs +++ b/crates/typst/src/foundations/version.rs @@ -5,7 +5,7 @@ use std::iter::repeat; use ecow::{eco_format, EcoString, EcoVec}; -use crate::diag::{bail, error, StrResult}; +use crate::diag::{bail, StrResult}; use crate::foundations::{cast, func, repr, scope, ty, Repr}; /// A version with an arbitrary number of components. @@ -43,7 +43,7 @@ impl Version { .iter() .zip(Self::COMPONENTS) .find_map(|(&i, s)| (s == name).then_some(i as i64)) - .ok_or_else(|| error!("unknown version component")) + .ok_or_else(|| "unknown version component".into()) } /// Push a component to the end of this version. diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs index 1e1bcf9be..dbdadbde5 100644 --- a/crates/typst/src/layout/mod.rs +++ b/crates/typst/src/layout/mod.rs @@ -71,7 +71,7 @@ pub(crate) use self::inline::*; use comemo::{Tracked, TrackedMut}; -use crate::diag::{bail, error, SourceResult}; +use crate::diag::{bail, SourceResult}; use crate::engine::{Engine, Route}; use crate::eval::Tracer; use crate::foundations::{category, Category, Content, Scope, StyleChain}; @@ -238,8 +238,10 @@ impl Layout for Content { }; if engine.route.exceeding() { - bail!(error!(content.span(), "maximum layout depth exceeded") - .with_hint("try to reduce the amount of nesting in your layout")); + bail!( + content.span(), "maximum layout depth exceeded"; + hint: "try to reduce the amount of nesting in your layout", + ); } let scratch = Scratch::default(); diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index 52634d453..1109b9067 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -143,10 +143,10 @@ fn typeset( } if iter >= 5 { - tracer.warn( - warning!(Span::detached(), "layout did not converge within 5 attempts",) - .with_hint("check if any states or queries are updating themselves"), - ); + tracer.warn(warning!( + Span::detached(), "layout did not converge within 5 attempts"; + hint: "check if any states or queries are updating themselves" + )); break; } } diff --git a/crates/typst/src/model/footnote.rs b/crates/typst/src/model/footnote.rs index df88b58ca..a6e1e2ded 100644 --- a/crates/typst/src/model/footnote.rs +++ b/crates/typst/src/model/footnote.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use comemo::Prehashed; -use crate::diag::{bail, error, At, SourceResult, StrResult}; +use crate::diag::{bail, At, SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ cast, elem, scope, Content, Finalize, Label, NativeElement, Show, Smart, StyleChain, @@ -280,9 +280,10 @@ impl Show for FootnoteEntry { let numbering = note.numbering(default); let counter = Counter::of(FootnoteElem::elem()); 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" - )) + bail!( + self.span(), "footnote entry must have a location"; + hint: "try using a query or a show rule to customize the footnote instead" + ); }; let num = counter.at(engine, loc)?.display(engine, numbering)?; diff --git a/crates/typst/src/model/outline.rs b/crates/typst/src/model/outline.rs index 91526a9cc..06b032fa5 100644 --- a/crates/typst/src/model/outline.rs +++ b/crates/typst/src/model/outline.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; use std::str::FromStr; -use crate::diag::{bail, error, At, SourceResult}; +use crate::diag::{bail, At, SourceResult}; use crate::engine::Engine; use crate::foundations::{ cast, elem, scope, select_where, Content, Finalize, Func, LocatableSelector, @@ -492,10 +492,10 @@ impl Show for OutlineEntry { // In case a user constructs an outline entry with an arbitrary element. let Some(location) = elem.location() else { if elem.can::() && elem.can::() { - 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", - )) + bail!( + self.span(), "{} must have a location", elem.func().name(); + hint: "try using a query or a show rule to customize the outline.entry instead", + ) } else { bail!(self.span(), "cannot outline {}", elem.func().name()) } diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs index c2f193034..81dbdb667 100644 --- a/crates/typst/src/realize/mod.rs +++ b/crates/typst/src/realize/mod.rs @@ -10,7 +10,7 @@ use std::mem; use smallvec::smallvec; use typed_arena::Arena; -use crate::diag::{bail, error, SourceResult}; +use crate::diag::{bail, SourceResult}; use crate::engine::Engine; use crate::foundations::{ Content, Finalize, Guard, NativeElement, Recipe, Selector, Show, StyleChain, @@ -308,11 +308,11 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { if let Some(realized) = realize(self.engine, content, styles)? { self.engine.route.increase(); if self.engine.route.exceeding() { - bail!(error!(content.span(), "maximum show rule depth exceeded") - .with_hint("check whether the show rule matches its own output") - .with_hint( - "this is a current compiler limitation that will be resolved in the future" - )); + bail!( + content.span(), "maximum show rule depth exceeded"; + hint: "check whether the show rule matches its own output"; + hint: "this is a current compiler limitation that will be resolved in the future", + ); } let stored = self.scratch.content.alloc(realized); let v = self.accept(stored, styles); diff --git a/crates/typst/src/text/mod.rs b/crates/typst/src/text/mod.rs index da3580a56..dfbf328fc 100644 --- a/crates/typst/src/text/mod.rs +++ b/crates/typst/src/text/mod.rs @@ -34,7 +34,7 @@ use ecow::{eco_format, EcoString}; use rustybuzz::{Feature, Tag}; use ttf_parser::Rect; -use crate::diag::{bail, error, SourceResult, StrResult}; +use crate::diag::{bail, SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ cast, category, elem, Args, Array, Cast, Category, Construct, Content, Dict, Fold, @@ -228,11 +228,9 @@ pub struct TextElem { if let Some(paint) = &paint { if paint.v.relative() == Smart::Custom(RelativeTo::Self_) { bail!( - error!( - paint.span, - "gradients and patterns on text must be relative to the parent" - ) - .with_hint("make sure to set `relative: auto` on your text fill") + paint.span, + "gradients and patterns on text must be relative to the parent"; + hint: "make sure to set `relative: auto` on your text fill" ); } } diff --git a/crates/typst/src/visualize/color.rs b/crates/typst/src/visualize/color.rs index e3c858ff6..b327e92d6 100644 --- a/crates/typst/src/visualize/color.rs +++ b/crates/typst/src/visualize/color.rs @@ -10,7 +10,7 @@ use palette::{ Darken, Desaturate, FromColor, Lighten, Okhsva, OklabHue, RgbHue, Saturate, ShiftHue, }; -use crate::diag::{bail, error, At, SourceResult, StrResult}; +use crate::diag::{bail, At, SourceResult, StrResult}; use crate::foundations::{ array, cast, func, repr, scope, ty, Args, Array, IntoValue, Module, Repr, Scope, Str, Value, @@ -922,8 +922,10 @@ impl Color { ) -> SourceResult { Ok(match self { Self::Luma(_) => { - bail!(error!(span, "cannot saturate grayscale color") - .with_hint("try converting your color to RGB first")); + bail!( + span, "cannot saturate grayscale color"; + hint: "try converting your color to RGB first" + ); } Self::Oklab(_) => self.to_hsv().saturate(span, factor)?.to_oklab(), Self::Oklch(_) => self.to_hsv().saturate(span, factor)?.to_oklch(), @@ -946,8 +948,10 @@ impl Color { ) -> SourceResult { Ok(match self { Self::Luma(_) => { - bail!(error!(span, "cannot desaturate grayscale color") - .with_hint("try converting your color to RGB first")); + bail!( + span, "cannot desaturate grayscale color"; + hint: "try converting your color to RGB first" + ); } Self::Oklab(_) => self.to_hsv().desaturate(span, factor)?.to_oklab(), Self::Oklch(_) => self.to_hsv().desaturate(span, factor)?.to_oklch(), diff --git a/crates/typst/src/visualize/gradient.rs b/crates/typst/src/visualize/gradient.rs index 4e804d9ac..bd08254cc 100644 --- a/crates/typst/src/visualize/gradient.rs +++ b/crates/typst/src/visualize/gradient.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use ecow::EcoString; use kurbo::Vec2; -use crate::diag::{bail, error, SourceResult}; +use crate::diag::{bail, SourceResult}; use crate::foundations::{ array, cast, func, scope, ty, Args, Array, Cast, Func, IntoValue, Repr, Smart, }; @@ -231,8 +231,10 @@ impl Gradient { }; if stops.len() < 2 { - bail!(error!(span, "a gradient must have at least two stops") - .with_hint("try filling the shape with a single color instead")); + bail!( + span, "a gradient must have at least two stops"; + hint: "try filling the shape with a single color instead" + ); } Ok(Self::Linear(Arc::new(LinearGradient { @@ -332,24 +334,29 @@ impl Gradient { focal_radius: Spanned, ) -> SourceResult { if stops.len() < 2 { - bail!(error!(span, "a gradient must have at least two stops") - .with_hint("try filling the shape with a single color instead")); + bail!( + span, "a gradient must have at least two stops"; + hint: "try filling the shape with a single color instead" + ); } if focal_radius.v > radius.v { - bail!(error!( + bail!( focal_radius.span, - "the focal radius must be smaller than the end radius" - ) - .with_hint("try using a focal radius of `0%` instead")); + "the focal radius must be smaller than the end radius"; + hint: "try using a focal radius of `0%` instead" + ); } let focal_center = focal_center.unwrap_or(center); let d_center_sqr = (focal_center.x - center.x).get().powi(2) + (focal_center.y - center.y).get().powi(2); if d_center_sqr.sqrt() >= (radius.v - focal_radius.v).get() { - bail!(error!(span, "the focal circle must be inside of the end circle") - .with_hint("try using a focal center of `auto` instead")); + bail!( + span, + "the focal circle must be inside of the end circle"; + hint: "try using a focal center of `auto` instead" + ); } Ok(Gradient::Radial(Arc::new(RadialGradient { @@ -419,8 +426,10 @@ impl Gradient { center: Axes, ) -> SourceResult { if stops.len() < 2 { - bail!(error!(span, "a gradient must have at least two stops") - .with_hint("try filling the shape with a single color instead")); + bail!( + span, "a gradient must have at least two stops"; + hint: "try filling the shape with a single color instead" + ); } Ok(Gradient::Conic(Arc::new(ConicGradient { @@ -1154,11 +1163,10 @@ fn process_stops(stops: &[Spanned]) -> SourceResult]) -> SourceResult>>()?; if out[0].1 != Ratio::zero() { - bail!(error!(stops[0].span, "first stop must have an offset of 0%") - .with_hint("try setting this stop to `0%`")); + bail!( + stops[0].span, + "first stop must have an offset of 0"; + hint: "try setting this stop to `0%`" + ); } if out[out.len() - 1].1 != Ratio::one() { - bail!(error!(stops[0].span, "last stop must have an offset of 100%") - .with_hint("try setting this stop to `100%`")); + bail!( + stops[out.len() - 1].span, + "last stop must have an offset of 100%"; + hint: "try setting this stop to `100%`" + ); } return Ok(out); diff --git a/crates/typst/src/visualize/pattern.rs b/crates/typst/src/visualize/pattern.rs index 3014a8e00..87cae4e5f 100644 --- a/crates/typst/src/visualize/pattern.rs +++ b/crates/typst/src/visualize/pattern.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use comemo::Prehashed; use ecow::{eco_format, EcoString}; -use crate::diag::{bail, error, SourceResult}; +use crate::diag::{bail, SourceResult}; use crate::engine::Engine; use crate::foundations::{func, scope, ty, Content, Repr, Smart, StyleChain}; use crate::layout::{Abs, Axes, Em, Frame, Layout, Length, Regions, Size}; @@ -181,8 +181,10 @@ impl Pattern { // Check that the frame is non-zero. if size.is_auto() && frame.size().is_zero() { - bail!(error!(span, "pattern tile size must be non-zero") - .with_hint("try setting the size manually")); + bail!( + span, "pattern tile size must be non-zero"; + hint: "try setting the size manually" + ); } // Set the size of the frame if the size is enforced.