diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs index c8bd39144..bc1c30a68 100644 --- a/crates/typst-cli/src/main.rs +++ b/crates/typst-cli/src/main.rs @@ -20,6 +20,7 @@ use clap::Parser; use codespan_reporting::term; use codespan_reporting::term::termcolor::WriteColor; use once_cell::sync::Lazy; +use typst::diag::HintedStrResult; use crate::args::{CliArguments, Command}; use crate::timings::Timer; @@ -34,25 +35,32 @@ static ARGS: Lazy = Lazy::new(CliArguments::parse); /// Entry point. fn main() -> ExitCode { - let timer = Timer::new(&ARGS); - - let res = match &ARGS.command { - Command::Compile(command) => crate::compile::compile(timer, command.clone()), - Command::Watch(command) => crate::watch::watch(timer, command.clone()), - Command::Init(command) => crate::init::init(command), - Command::Query(command) => crate::query::query(command), - Command::Fonts(command) => crate::fonts::fonts(command), - Command::Update(command) => crate::update::update(command), - }; + let res = dispatch(); if let Err(msg) = res { set_failed(); - print_error(&msg).expect("failed to print error"); + print_error(msg.message()).expect("failed to print error"); } EXIT.with(|cell| cell.get()) } +/// Execute the requested command. +fn dispatch() -> HintedStrResult<()> { + let timer = Timer::new(&ARGS); + + match &ARGS.command { + Command::Compile(command) => crate::compile::compile(timer, command.clone())?, + Command::Watch(command) => crate::watch::watch(timer, command.clone())?, + Command::Init(command) => crate::init::init(command)?, + Command::Query(command) => crate::query::query(command)?, + Command::Fonts(command) => crate::fonts::fonts(command)?, + Command::Update(command) => crate::update::update(command)?, + } + + Ok(()) +} + /// Ensure a failure exit code. fn set_failed() { EXIT.with(|cell| cell.set(ExitCode::FAILURE)); diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs index 6422af949..2e8bf7f82 100644 --- a/crates/typst-cli/src/query.rs +++ b/crates/typst-cli/src/query.rs @@ -1,7 +1,7 @@ use comemo::Track; use ecow::{eco_format, EcoString}; use serde::Serialize; -use typst::diag::{bail, StrResult}; +use typst::diag::{bail, HintedStrResult, StrResult}; use typst::eval::{eval_string, EvalMode, Tracer}; use typst::foundations::{Content, IntoValue, LocatableSelector, Scope}; use typst::model::Document; @@ -14,7 +14,7 @@ use crate::set_failed; use crate::world::SystemWorld; /// Execute a query command. -pub fn query(command: &QueryCommand) -> StrResult<()> { +pub fn query(command: &QueryCommand) -> HintedStrResult<()> { let mut world = SystemWorld::new(&command.common)?; // Reset everything and ensure that the main file is present. @@ -56,7 +56,7 @@ fn retrieve( world: &dyn World, command: &QueryCommand, document: &Document, -) -> StrResult> { +) -> HintedStrResult> { let selector = eval_string( world.track(), &command.selector, diff --git a/crates/typst-macros/src/cast.rs b/crates/typst-macros/src/cast.rs index f2115aa97..c732ce6b7 100644 --- a/crates/typst-macros/src/cast.rs +++ b/crates/typst-macros/src/cast.rs @@ -108,7 +108,7 @@ pub fn cast(stream: TokenStream) -> Result { let from_value = (!input.from_value.is_empty() || input.dynamic).then(|| { quote! { impl #foundations::FromValue for #ty { - fn from_value(value: #foundations::Value) -> ::typst::diag::StrResult { + fn from_value(value: #foundations::Value) -> ::typst::diag::HintedStrResult { #from_value_body } } diff --git a/crates/typst/src/diag.rs b/crates/typst/src/diag.rs index 6e119591a..15e6a49fa 100644 --- a/crates/typst/src/diag.rs +++ b/crates/typst/src/diag.rs @@ -38,9 +38,14 @@ use crate::{World, WorldExt}; #[doc(hidden)] macro_rules! __bail { // For bail!("just a {}", "string") - ($fmt:literal $(, $arg:expr)* $(,)?) => { + ( + $fmt:literal $(, $arg:expr)* + $(; hint: $hint:literal $(, $hint_arg:expr)*)* + $(,)? + ) => { return Err($crate::diag::error!( - $fmt, $($arg),* + $fmt $(, $arg)* + $(; hint: $hint $(, $hint_arg)*)* )) }; @@ -55,13 +60,25 @@ macro_rules! __bail { }; } -/// Construct an [`EcoString`] or [`SourceDiagnostic`] with severity `Error`. +/// Construct an [`EcoString`], [`HintedString`] 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),*) + $crate::diag::eco_format!($fmt, $($arg),*).into() + }; + + // For bail!("a hinted {}", "string"; hint: "some hint"; hint: "...") + ( + $fmt:literal $(, $arg:expr)* + $(; hint: $hint:literal $(, $hint_arg:expr)*)* + $(,)? + ) => { + $crate::diag::HintedString::new( + $crate::diag::eco_format!($fmt, $($arg),*) + ) $(.with_hint($crate::diag::eco_format!($hint, $($hint_arg),*)))* }; // For bail!(span, ...) @@ -296,13 +313,48 @@ where pub type HintedStrResult = Result; /// A string message with hints. +/// +/// This is internally represented by a vector of strings. +/// The first element of the vector contains the message. +/// The remaining elements are the hints. +/// This is done to reduce the size of a HintedString. +/// The vector is guaranteed to not be empty. #[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct HintedString { +pub struct HintedString(EcoVec); + +impl HintedString { + /// Creates a new hinted string with the given message. + pub fn new(message: EcoString) -> Self { + Self(eco_vec![message]) + } + /// A diagnostic message describing the problem. - pub message: EcoString, + pub fn message(&self) -> &EcoString { + self.0.first().unwrap() + } + /// Additional hints to the user, indicating how this error could be avoided /// or worked around. - pub hints: Vec, + pub fn hints(&self) -> &[EcoString] { + self.0.get(1..).unwrap_or(&[]) + } + + /// Adds a single hint to the hinted string. + pub fn hint(&mut self, hint: impl Into) { + self.0.push(hint.into()); + } + + /// Adds a single hint to the hinted string. + pub fn with_hint(mut self, hint: impl Into) -> Self { + self.hint(hint); + self + } + + /// Adds user-facing hints to the hinted string. + pub fn with_hints(mut self, hints: impl IntoIterator) -> Self { + self.0.extend(hints); + self + } } impl From for HintedString @@ -310,14 +362,18 @@ where S: Into, { fn from(value: S) -> Self { - Self { message: value.into(), hints: vec![] } + Self::new(value.into()) } } impl At for Result { fn at(self, span: Span) -> SourceResult { - self.map_err(|diags| { - eco_vec![SourceDiagnostic::error(span, diags.message).with_hints(diags.hints)] + self.map_err(|err| { + let mut components = err.0.into_iter(); + let message = components.next().unwrap(); + let diag = SourceDiagnostic::error(span, message).with_hints(components); + + eco_vec![diag] }) } } @@ -333,17 +389,14 @@ where S: Into, { fn hint(self, hint: impl Into) -> HintedStrResult { - self.map_err(|message| HintedString { - message: message.into(), - hints: vec![hint.into()], - }) + self.map_err(|message| HintedString::new(message.into()).with_hint(hint)) } } impl Hint for HintedStrResult { fn hint(self, hint: impl Into) -> HintedStrResult { self.map_err(|mut error| { - error.hints.push(hint.into()); + error.hint(hint.into()); error }) } diff --git a/crates/typst/src/eval/code.rs b/crates/typst/src/eval/code.rs index 30f5fb720..8542c846e 100644 --- a/crates/typst/src/eval/code.rs +++ b/crates/typst/src/eval/code.rs @@ -1,6 +1,6 @@ use ecow::{eco_vec, EcoVec}; -use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult}; +use crate::diag::{bail, error, At, SourceResult}; use crate::eval::{ops, CapturesVisitor, Eval, Vm}; use crate::foundations::{ Array, Capturer, Closure, Content, ContextElem, Dict, Func, NativeElement, Str, Value, @@ -244,11 +244,11 @@ impl Eval for ast::Dict<'_> { ast::DictItem::Keyed(keyed) => { let raw_key = keyed.key(); let key = raw_key.eval(vm)?; - let key = key.cast::().unwrap_or_else(|error| { - let error = SourceDiagnostic::error(raw_key.span(), error); - invalid_keys.push(error); - Str::default() - }); + let key = + key.cast::().at(raw_key.span()).unwrap_or_else(|errors| { + invalid_keys.extend(errors); + Str::default() + }); map.insert(key, keyed.expr().eval(vm)?); } ast::DictItem::Spread(spread) => match spread.expr().eval(vm)? { diff --git a/crates/typst/src/eval/ops.rs b/crates/typst/src/eval/ops.rs index 13e641206..c9c8a24e6 100644 --- a/crates/typst/src/eval/ops.rs +++ b/crates/typst/src/eval/ops.rs @@ -4,7 +4,7 @@ use std::cmp::Ordering; use ecow::eco_format; -use crate::diag::{bail, At, SourceResult, StrResult}; +use crate::diag::{bail, At, HintedStrResult, SourceResult, StrResult}; use crate::eval::{access_dict, Access, Eval, Vm}; use crate::foundations::{format_str, Datetime, IntoValue, Regex, Repr, Value}; use crate::layout::{Alignment, Length, Rel}; @@ -59,7 +59,7 @@ impl Eval for ast::Binary<'_> { fn apply_binary( binary: ast::Binary, vm: &mut Vm, - op: fn(Value, Value) -> StrResult, + op: fn(Value, Value) -> HintedStrResult, ) -> SourceResult { let lhs = binary.lhs().eval(vm)?; @@ -78,7 +78,7 @@ fn apply_binary( fn apply_assignment( binary: ast::Binary, vm: &mut Vm, - op: fn(Value, Value) -> StrResult, + op: fn(Value, Value) -> HintedStrResult, ) -> SourceResult { let rhs = binary.rhs().eval(vm)?; let lhs = binary.lhs(); @@ -102,7 +102,7 @@ fn apply_assignment( /// Bail with a type mismatch error. macro_rules! mismatch { ($fmt:expr, $($value:expr),* $(,)?) => { - return Err(eco_format!($fmt, $($value.ty()),*)) + return Err(eco_format!($fmt, $($value.ty()),*).into()) }; } @@ -134,7 +134,7 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult { } /// Apply the unary plus operator to a value. -pub fn pos(value: Value) -> StrResult { +pub fn pos(value: Value) -> HintedStrResult { use Value::*; Ok(match value { Int(v) => Int(v), @@ -159,7 +159,7 @@ pub fn pos(value: Value) -> StrResult { } /// Compute the negation of a value. -pub fn neg(value: Value) -> StrResult { +pub fn neg(value: Value) -> HintedStrResult { use Value::*; Ok(match value { Int(v) => Int(v.checked_neg().ok_or_else(too_large)?), @@ -176,7 +176,7 @@ pub fn neg(value: Value) -> StrResult { } /// Compute the sum of two values. -pub fn add(lhs: Value, rhs: Value) -> StrResult { +pub fn add(lhs: Value, rhs: Value) -> HintedStrResult { use Value::*; Ok(match (lhs, rhs) { (a, None) => a, @@ -252,7 +252,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { } /// Compute the difference of two values. -pub fn sub(lhs: Value, rhs: Value) -> StrResult { +pub fn sub(lhs: Value, rhs: Value) -> HintedStrResult { use Value::*; Ok(match (lhs, rhs) { (Int(a), Int(b)) => Int(a.checked_sub(b).ok_or_else(too_large)?), @@ -285,7 +285,7 @@ pub fn sub(lhs: Value, rhs: Value) -> StrResult { } /// Compute the product of two values. -pub fn mul(lhs: Value, rhs: Value) -> StrResult { +pub fn mul(lhs: Value, rhs: Value) -> HintedStrResult { use Value::*; Ok(match (lhs, rhs) { (Int(a), Int(b)) => Int(a.checked_mul(b).ok_or_else(too_large)?), @@ -344,7 +344,7 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult { } /// Compute the quotient of two values. -pub fn div(lhs: Value, rhs: Value) -> StrResult { +pub fn div(lhs: Value, rhs: Value) -> HintedStrResult { use Value::*; if is_zero(&rhs) { bail!("cannot divide by zero"); @@ -416,7 +416,7 @@ fn try_div_relative(a: Rel, b: Rel) -> StrResult { } /// Compute the logical "not" of a value. -pub fn not(value: Value) -> StrResult { +pub fn not(value: Value) -> HintedStrResult { match value { Value::Bool(b) => Ok(Value::Bool(!b)), v => mismatch!("cannot apply 'not' to {}", v), @@ -424,7 +424,7 @@ pub fn not(value: Value) -> StrResult { } /// Compute the logical "and" of two values. -pub fn and(lhs: Value, rhs: Value) -> StrResult { +pub fn and(lhs: Value, rhs: Value) -> HintedStrResult { match (lhs, rhs) { (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)), (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b), @@ -432,7 +432,7 @@ pub fn and(lhs: Value, rhs: Value) -> StrResult { } /// Compute the logical "or" of two values. -pub fn or(lhs: Value, rhs: Value) -> StrResult { +pub fn or(lhs: Value, rhs: Value) -> HintedStrResult { match (lhs, rhs) { (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)), (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b), @@ -440,19 +440,19 @@ pub fn or(lhs: Value, rhs: Value) -> StrResult { } /// Compute whether two values are equal. -pub fn eq(lhs: Value, rhs: Value) -> StrResult { +pub fn eq(lhs: Value, rhs: Value) -> HintedStrResult { Ok(Value::Bool(equal(&lhs, &rhs))) } /// Compute whether two values are unequal. -pub fn neq(lhs: Value, rhs: Value) -> StrResult { +pub fn neq(lhs: Value, rhs: Value) -> HintedStrResult { Ok(Value::Bool(!equal(&lhs, &rhs))) } macro_rules! comparison { ($name:ident, $op:tt, $($pat:tt)*) => { /// Compute how a value compares with another value. - pub fn $name(lhs: Value, rhs: Value) -> StrResult { + pub fn $name(lhs: Value, rhs: Value) -> HintedStrResult { let ordering = compare(&lhs, &rhs)?; Ok(Value::Bool(matches!(ordering, $($pat)*))) } @@ -577,7 +577,7 @@ fn try_cmp_arrays(a: &[Value], b: &[Value]) -> StrResult { } /// Test whether one value is "in" another one. -pub fn in_(lhs: Value, rhs: Value) -> StrResult { +pub fn in_(lhs: Value, rhs: Value) -> HintedStrResult { if let Some(b) = contains(&lhs, &rhs) { Ok(Value::Bool(b)) } else { @@ -586,7 +586,7 @@ pub fn in_(lhs: Value, rhs: Value) -> StrResult { } /// Test whether one value is "not in" another one. -pub fn not_in(lhs: Value, rhs: Value) -> StrResult { +pub fn not_in(lhs: Value, rhs: Value) -> HintedStrResult { if let Some(b) = contains(&lhs, &rhs) { Ok(Value::Bool(!b)) } else { diff --git a/crates/typst/src/foundations/args.rs b/crates/typst/src/foundations/args.rs index 4624ae02f..c59e49855 100644 --- a/crates/typst/src/foundations/args.rs +++ b/crates/typst/src/foundations/args.rs @@ -178,9 +178,9 @@ impl Args { }; let span = item.value.span; let spanned = Spanned::new(std::mem::take(&mut item.value.v), span); - match T::from_value(spanned) { + match T::from_value(spanned).at(span) { Ok(val) => list.push(val), - Err(err) => errors.push(SourceDiagnostic::error(span, err)), + Err(diags) => errors.extend(diags), } false }); diff --git a/crates/typst/src/foundations/array.rs b/crates/typst/src/foundations/array.rs index 2f65800e6..2caefc133 100644 --- a/crates/typst/src/foundations/array.rs +++ b/crates/typst/src/foundations/array.rs @@ -8,7 +8,7 @@ use ecow::{eco_format, EcoString, EcoVec}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use crate::diag::{bail, At, SourceDiagnostic, SourceResult, StrResult}; +use crate::diag::{bail, At, HintedStrResult, SourceDiagnostic, SourceResult, StrResult}; use crate::engine::Engine; use crate::eval::ops; use crate::foundations::{ @@ -594,7 +594,7 @@ impl Array { /// be empty. #[named] default: Option, - ) -> StrResult { + ) -> HintedStrResult { let mut iter = self.into_iter(); let mut acc = iter .next() @@ -615,7 +615,7 @@ impl Array { /// be empty. #[named] default: Option, - ) -> StrResult { + ) -> HintedStrResult { let mut iter = self.into_iter(); let mut acc = iter .next() @@ -1095,13 +1095,13 @@ impl IntoValue for SmallVec<[T; N]> { } impl FromValue for Vec { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { value.cast::()?.into_iter().map(Value::cast).collect() } } impl FromValue for SmallVec<[T; N]> { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { value.cast::()?.into_iter().map(Value::cast).collect() } } diff --git a/crates/typst/src/foundations/auto.rs b/crates/typst/src/foundations/auto.rs index 5711b1f6a..cb9d999ef 100644 --- a/crates/typst/src/foundations/auto.rs +++ b/crates/typst/src/foundations/auto.rs @@ -1,7 +1,7 @@ use ecow::EcoString; use std::fmt::{self, Debug, Formatter}; -use crate::diag::StrResult; +use crate::diag::HintedStrResult; use crate::foundations::{ ty, CastInfo, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain, Type, Value, @@ -26,7 +26,7 @@ impl IntoValue for AutoValue { } impl FromValue for AutoValue { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { match value { Value::Auto => Ok(Self), _ => Err(Self::error(&value)), @@ -236,7 +236,7 @@ impl IntoValue for Smart { } impl FromValue for Smart { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { match value { Value::Auto => Ok(Self::Auto), v if T::castable(&v) => Ok(Self::Custom(T::from_value(v)?)), diff --git a/crates/typst/src/foundations/cast.rs b/crates/typst/src/foundations/cast.rs index b29e44732..b53576c25 100644 --- a/crates/typst/src/foundations/cast.rs +++ b/crates/typst/src/foundations/cast.rs @@ -7,7 +7,7 @@ use ecow::{eco_format, EcoString}; use smallvec::SmallVec; use unicode_math_class::MathClass; -use crate::diag::{At, HintedStrResult, SourceResult, StrResult}; +use crate::diag::{At, HintedStrResult, HintedString, SourceResult, StrResult}; use crate::foundations::{array, repr, NativeElement, Packed, Repr, Str, Type, Value}; use crate::syntax::{Span, Spanned}; @@ -50,8 +50,8 @@ pub trait Reflect { /// "expected integer, found none", /// ); /// ``` - fn error(found: &Value) -> EcoString { - Self::input().error(found) + fn error(found: &Value) -> HintedString { + Self::input().error(found).into() } } @@ -249,17 +249,17 @@ impl IntoValue for fn() -> T { /// See also: [`Reflect`]. pub trait FromValue: Sized + Reflect { /// Try to cast the value into an instance of `Self`. - fn from_value(value: V) -> StrResult; + fn from_value(value: V) -> HintedStrResult; } impl FromValue for Value { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { Ok(value) } } impl FromValue for Packed { - fn from_value(mut value: Value) -> StrResult { + fn from_value(mut value: Value) -> HintedStrResult { if let Value::Content(content) = value { match content.into_packed::() { Ok(packed) => return Ok(packed), @@ -272,13 +272,13 @@ impl FromValue for Packed { } impl FromValue> for T { - fn from_value(value: Spanned) -> StrResult { + fn from_value(value: Spanned) -> HintedStrResult { T::from_value(value.v) } } impl FromValue> for Spanned { - fn from_value(value: Spanned) -> StrResult { + fn from_value(value: Spanned) -> HintedStrResult { let span = value.span; T::from_value(value.v).map(|t| Spanned::new(t, span)) } @@ -432,7 +432,7 @@ impl IntoValue for Never { } impl FromValue for Never { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { Err(Self::error(&value)) } } diff --git a/crates/typst/src/foundations/none.rs b/crates/typst/src/foundations/none.rs index afb055a69..d376c0c5b 100644 --- a/crates/typst/src/foundations/none.rs +++ b/crates/typst/src/foundations/none.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter}; use ecow::EcoString; use serde::{Serialize, Serializer}; -use crate::diag::StrResult; +use crate::diag::HintedStrResult; use crate::foundations::{ cast, ty, CastInfo, FromValue, IntoValue, Reflect, Repr, Type, Value, }; @@ -45,7 +45,7 @@ impl IntoValue for NoneValue { } impl FromValue for NoneValue { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { match value { Value::None => Ok(Self), _ => Err(Self::error(&value)), @@ -104,7 +104,7 @@ impl IntoValue for Option { } impl FromValue for Option { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { match value { Value::None => Ok(None), v if T::castable(&v) => Ok(Some(T::from_value(v)?)), diff --git a/crates/typst/src/foundations/scope.rs b/crates/typst/src/foundations/scope.rs index 0cbbe923f..e0ef7a728 100644 --- a/crates/typst/src/foundations/scope.rs +++ b/crates/typst/src/foundations/scope.rs @@ -97,17 +97,14 @@ fn cannot_mutate_constant(var: &str) -> HintedString { /// The error message when a variable is not found. #[cold] fn unknown_variable(var: &str) -> HintedString { - let mut res = HintedString { - message: eco_format!("unknown variable: {}", var), - hints: vec![], - }; + let mut res = HintedString::new(eco_format!("unknown variable: {}", var)); if matches!(var, "none" | "auto" | "false" | "true") { - res.hints.push(eco_format!( + res.hint(eco_format!( "if you meant to use a literal, try adding a hash before it" )); } else if var.contains('-') { - res.hints.push(eco_format!( + res.hint(eco_format!( "if you meant to use subtraction, try adding spaces around the minus sign", )); } diff --git a/crates/typst/src/foundations/selector.rs b/crates/typst/src/foundations/selector.rs index d41fd2dc8..1aee961b3 100644 --- a/crates/typst/src/foundations/selector.rs +++ b/crates/typst/src/foundations/selector.rs @@ -339,7 +339,7 @@ cast! { } impl FromValue for LocatableSelector { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { fn validate(selector: &Selector) -> StrResult<()> { match selector { Selector::Elem(elem, _) => { @@ -421,8 +421,8 @@ cast! { } impl FromValue for ShowableSelector { - fn from_value(value: Value) -> StrResult { - fn validate(selector: &Selector, nested: bool) -> StrResult<()> { + fn from_value(value: Value) -> HintedStrResult { + fn validate(selector: &Selector, nested: bool) -> HintedStrResult<()> { match selector { Selector::Elem(_, _) => {} Selector::Label(_) => {} diff --git a/crates/typst/src/foundations/value.rs b/crates/typst/src/foundations/value.rs index 5d99a586f..d2190ae76 100644 --- a/crates/typst/src/foundations/value.rs +++ b/crates/typst/src/foundations/value.rs @@ -9,7 +9,7 @@ use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer}; use serde::de::{Error, MapAccess, SeqAccess, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::diag::StrResult; +use crate::diag::{HintedStrResult, HintedString, StrResult}; use crate::eval::ops; use crate::foundations::{ fields, repr, Args, Array, AutoValue, Bytes, CastInfo, Content, Datetime, Dict, @@ -151,7 +151,7 @@ impl Value { } /// Try to cast the value into a specific type. - pub fn cast(self) -> StrResult { + pub fn cast(self) -> HintedStrResult { T::from_value(self) } @@ -606,7 +606,7 @@ macro_rules! primitive { } impl FromValue for $ty { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { match value { Value::$variant(v) => Ok(v), $(Value::$other$(($binding))? => Ok($out),)* @@ -614,7 +614,7 @@ macro_rules! primitive { "expected {}, found {}", Type::of::(), v.ty(), - )), + ).into()), } } } @@ -682,7 +682,7 @@ impl Reflect for Arc { T::castable(value) } - fn error(found: &Value) -> EcoString { + fn error(found: &Value) -> HintedString { T::error(found) } } @@ -694,7 +694,7 @@ impl IntoValue for Arc { } impl FromValue for Arc { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { match value { v if T::castable(&v) => Ok(Arc::new(T::from_value(v)?)), _ => Err(Self::error(&value)), diff --git a/crates/typst/src/introspection/counter.rs b/crates/typst/src/introspection/counter.rs index 2365b3e8c..2a7e9ea7d 100644 --- a/crates/typst/src/introspection/counter.rs +++ b/crates/typst/src/introspection/counter.rs @@ -5,7 +5,7 @@ use comemo::{Track, Tracked, TrackedMut}; use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use smallvec::{smallvec, SmallVec}; -use crate::diag::{bail, At, SourceResult, StrResult}; +use crate::diag::{bail, At, HintedStrResult, SourceResult}; use crate::engine::{Engine, Route}; use crate::eval::Tracer; use crate::foundations::{ @@ -709,7 +709,7 @@ cast! { array: Array => Self(array .into_iter() .map(Value::cast) - .collect::>()?), + .collect::>()?), } /// Executes an update of a counter. diff --git a/crates/typst/src/layout/align.rs b/crates/typst/src/layout/align.rs index 188796923..1bd9e9705 100644 --- a/crates/typst/src/layout/align.rs +++ b/crates/typst/src/layout/align.rs @@ -2,7 +2,7 @@ use std::ops::Add; use ecow::{eco_format, EcoString}; -use crate::diag::{bail, SourceResult, StrResult}; +use crate::diag::{bail, HintedStrResult, SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Packed, @@ -637,7 +637,7 @@ where H: Reflect + TryFrom, V: Reflect + TryFrom, { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { if Alignment::castable(&value) { let align = Alignment::from_value(value)?; let result = match align { diff --git a/crates/typst/src/layout/corners.rs b/crates/typst/src/layout/corners.rs index 4b90854a1..a3b6ba93e 100644 --- a/crates/typst/src/layout/corners.rs +++ b/crates/typst/src/layout/corners.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Debug, Formatter}; -use crate::diag::StrResult; +use crate::diag::HintedStrResult; use crate::foundations::{ AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve, StyleChain, Value, @@ -175,7 +175,7 @@ impl FromValue for Corners> where T: FromValue + Clone, { - fn from_value(mut value: Value) -> StrResult { + fn from_value(mut value: Value) -> HintedStrResult { let expected_keys = [ "top-left", "top-right", @@ -224,7 +224,7 @@ where let keys = dict.iter().map(|kv| kv.0.as_str()).collect(); // Do not hint at expected_keys, because T may be castable from Dict // objects with other sets of expected keys. - Err(Dict::unexpected_keys(keys, None)) + Err(Dict::unexpected_keys(keys, None).into()) } else { Err(Self::error(&value)) } diff --git a/crates/typst/src/layout/grid/cells.rs b/crates/typst/src/layout/grid/cells.rs index 8ec84bc30..efa7bb6ea 100644 --- a/crates/typst/src/layout/grid/cells.rs +++ b/crates/typst/src/layout/grid/cells.rs @@ -6,9 +6,7 @@ use ecow::eco_format; use super::lines::Line; use super::repeated::{Footer, Header, Repeatable}; -use crate::diag::{ - bail, At, Hint, HintedStrResult, HintedString, SourceResult, StrResult, -}; +use crate::diag::{bail, At, Hint, HintedStrResult, HintedString, SourceResult}; use crate::engine::Engine; use crate::foundations::{ Array, CastInfo, Content, Context, Fold, FromValue, Func, IntoValue, Reflect, @@ -85,11 +83,11 @@ impl IntoValue for Celled { } impl FromValue for Celled { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { match value { Value::Func(v) => Ok(Self::Func(v)), Value::Array(array) => Ok(Self::Array( - array.into_iter().map(T::from_value).collect::>()?, + array.into_iter().map(T::from_value).collect::>()?, )), v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)), v => Err(Self::error(&v)), diff --git a/crates/typst/src/layout/grid/mod.rs b/crates/typst/src/layout/grid/mod.rs index e1fec58e7..57e28730c 100644 --- a/crates/typst/src/layout/grid/mod.rs +++ b/crates/typst/src/layout/grid/mod.rs @@ -13,10 +13,10 @@ pub use self::lines::LinePosition; use std::num::NonZeroUsize; use std::sync::Arc; -use ecow::{eco_format, EcoString}; +use ecow::eco_format; use smallvec::{smallvec, SmallVec}; -use crate::diag::{bail, SourceResult, StrResult, Trace, Tracepoint}; +use crate::diag::{bail, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint}; use crate::engine::Engine; use crate::foundations::{ cast, elem, scope, Array, Content, Fold, NativeElement, Packed, Show, Smart, @@ -406,7 +406,7 @@ cast! { self => self.0.into_value(), sizing: Sizing => Self(smallvec![sizing]), count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]), - values: Array => Self(values.into_iter().map(Value::cast).collect::>()?), + values: Array => Self(values.into_iter().map(Value::cast).collect::>()?), } /// Any child of a grid element. @@ -430,13 +430,19 @@ cast! { } impl TryFrom for GridChild { - type Error = EcoString; - fn try_from(value: Content) -> StrResult { + type Error = HintedString; + fn try_from(value: Content) -> HintedStrResult { if value.is::() { - bail!("cannot use `table.header` as a grid header; use `grid.header` instead") + bail!( + "cannot use `table.header` as a grid header"; + hint: "use `grid.header` instead" + ) } if value.is::() { - bail!("cannot use `table.footer` as a grid footer; use `grid.footer` instead") + bail!( + "cannot use `table.footer` as a grid footer"; + hint: "use `grid.footer` instead" + ) } value @@ -506,8 +512,8 @@ cast! { } impl TryFrom for GridItem { - type Error = EcoString; - fn try_from(value: Content) -> StrResult { + type Error = HintedString; + fn try_from(value: Content) -> HintedStrResult { if value.is::() { bail!("cannot place a grid header within another header or footer"); } @@ -521,13 +527,22 @@ impl TryFrom for GridItem { bail!("cannot place a table footer within another footer or header"); } if value.is::() { - bail!("cannot use `table.cell` as a grid cell; use `grid.cell` instead"); + bail!( + "cannot use `table.cell` as a grid cell"; + hint: "use `grid.cell` instead" + ); } if value.is::() { - bail!("cannot use `table.hline` as a grid line; use `grid.hline` instead"); + bail!( + "cannot use `table.hline` as a grid line"; + hint: "use `grid.hline` instead" + ); } if value.is::() { - bail!("cannot use `table.vline` as a grid line; use `grid.vline` instead"); + bail!( + "cannot use `table.vline` as a grid line"; + hint: "use `grid.vline` instead" + ); } Ok(value diff --git a/crates/typst/src/layout/sides.rs b/crates/typst/src/layout/sides.rs index ddf58ea99..75d4e25b3 100644 --- a/crates/typst/src/layout/sides.rs +++ b/crates/typst/src/layout/sides.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter}; use std::ops::Add; -use crate::diag::{bail, StrResult}; +use crate::diag::{bail, HintedStrResult}; use crate::foundations::{ cast, AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve, StyleChain, Value, @@ -209,7 +209,7 @@ impl FromValue for Sides> where T: Default + FromValue + Clone, { - fn from_value(mut value: Value) -> StrResult { + fn from_value(mut value: Value) -> HintedStrResult { let expected_keys = ["left", "top", "right", "bottom", "x", "y", "rest"]; if let Value::Dict(dict) = &mut value { if dict.is_empty() { @@ -237,7 +237,7 @@ where let keys = dict.iter().map(|kv| kv.0.as_str()).collect(); // Do not hint at expected_keys, because T may be castable from Dict // objects with other sets of expected keys. - Err(Dict::unexpected_keys(keys, None)) + Err(Dict::unexpected_keys(keys, None).into()) } else { Err(Self::error(&value)) } diff --git a/crates/typst/src/math/matrix.rs b/crates/typst/src/math/matrix.rs index 2581cfee1..4dadd756f 100644 --- a/crates/typst/src/math/matrix.rs +++ b/crates/typst/src/math/matrix.rs @@ -1,7 +1,7 @@ use smallvec::{smallvec, SmallVec}; use unicode_math_class::MathClass; -use crate::diag::{bail, At, SourceResult, StrResult}; +use crate::diag::{bail, At, HintedStrResult, SourceResult, StrResult}; use crate::foundations::{ array, cast, dict, elem, Array, Content, Dict, Fold, NoneValue, Packed, Resolve, Smart, StyleChain, Value, @@ -712,5 +712,5 @@ cast! { AugmentOffsets, self => self.0.into_value(), v: isize => Self(smallvec![v]), - v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), + v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } diff --git a/crates/typst/src/model/bibliography.rs b/crates/typst/src/model/bibliography.rs index c820bc40f..57a33fc4d 100644 --- a/crates/typst/src/model/bibliography.rs +++ b/crates/typst/src/model/bibliography.rs @@ -19,7 +19,7 @@ use once_cell::sync::Lazy; use smallvec::{smallvec, SmallVec}; use typed_arena::Arena; -use crate::diag::{bail, error, At, FileError, SourceResult, StrResult}; +use crate::diag::{bail, error, At, FileError, HintedStrResult, SourceResult, StrResult}; use crate::engine::Engine; use crate::eval::{eval_string, EvalMode}; use crate::foundations::{ @@ -150,7 +150,7 @@ cast! { BibliographyPaths, self => self.0.into_value(), v: EcoString => Self(vec![v]), - v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), + v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } impl BibliographyElem { @@ -508,7 +508,7 @@ impl Reflect for CslStyle { } impl FromValue for CslStyle { - fn from_value(value: Value) -> StrResult { + fn from_value(value: Value) -> HintedStrResult { if let Value::Dyn(dynamic) = &value { if let Some(concrete) = dynamic.downcast::() { return Ok(concrete.clone()); diff --git a/crates/typst/src/model/document.rs b/crates/typst/src/model/document.rs index ebe21505b..7bc517ee3 100644 --- a/crates/typst/src/model/document.rs +++ b/crates/typst/src/model/document.rs @@ -1,6 +1,6 @@ use ecow::EcoString; -use crate::diag::{bail, SourceResult, StrResult}; +use crate::diag::{bail, HintedStrResult, SourceResult}; use crate::engine::Engine; use crate::foundations::{ cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain, @@ -115,7 +115,7 @@ cast! { Author, self => self.0.into_value(), v: EcoString => Self(vec![v]), - v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), + v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } /// A list of keywords. @@ -126,7 +126,7 @@ cast! { Keywords, self => self.0.into_value(), v: EcoString => Self(vec![v]), - v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), + v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } /// A finished document with metadata and page frames. diff --git a/crates/typst/src/model/table.rs b/crates/typst/src/model/table.rs index 04a785b65..0c56b7e42 100644 --- a/crates/typst/src/model/table.rs +++ b/crates/typst/src/model/table.rs @@ -1,9 +1,9 @@ use std::num::NonZeroUsize; use std::sync::Arc; -use ecow::{eco_format, EcoString}; +use ecow::eco_format; -use crate::diag::{bail, SourceResult, StrResult, Trace, Tracepoint}; +use crate::diag::{bail, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint}; use crate::engine::Engine; use crate::foundations::{ cast, elem, scope, Content, Fold, NativeElement, Packed, Show, Smart, StyleChain, @@ -346,17 +346,19 @@ cast! { } impl TryFrom for TableChild { - type Error = EcoString; + type Error = HintedString; - fn try_from(value: Content) -> StrResult { + fn try_from(value: Content) -> HintedStrResult { if value.is::() { bail!( - "cannot use `grid.header` as a table header; use `table.header` instead" + "cannot use `grid.header` as a table header"; + hint: "use `table.header` instead" ) } if value.is::() { bail!( - "cannot use `grid.footer` as a table footer; use `table.footer` instead" + "cannot use `grid.footer` as a table footer"; + hint: "use `table.footer` instead" ) } @@ -427,9 +429,9 @@ cast! { } impl TryFrom for TableItem { - type Error = EcoString; + type Error = HintedString; - fn try_from(value: Content) -> StrResult { + fn try_from(value: Content) -> HintedStrResult { if value.is::() { bail!("cannot place a grid header within another header or footer"); } @@ -443,13 +445,22 @@ impl TryFrom for TableItem { bail!("cannot place a table footer within another footer or header"); } if value.is::() { - bail!("cannot use `grid.cell` as a table cell; use `table.cell` instead"); + bail!( + "cannot use `grid.cell` as a table cell"; + hint: "use `table.cell` instead" + ); } if value.is::() { - bail!("cannot use `grid.hline` as a table line; use `table.hline` instead"); + bail!( + "cannot use `grid.hline` as a table line"; + hint: "use `table.hline` instead" + ); } if value.is::() { - bail!("cannot use `grid.vline` as a table line; use `table.vline` instead"); + bail!( + "cannot use `grid.vline` as a table line"; + hint: "use `table.vline` instead" + ); } Ok(value diff --git a/crates/typst/src/text/lang.rs b/crates/typst/src/text/lang.rs index 6809238aa..05d6104e5 100644 --- a/crates/typst/src/text/lang.rs +++ b/crates/typst/src/text/lang.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; use std::str::FromStr; -use ecow::EcoString; +use crate::diag::Hint; +use ecow::{eco_format, EcoString}; use crate::foundations::{cast, StyleChain}; use crate::layout::Dir; @@ -121,7 +122,22 @@ impl FromStr for Lang { cast! { Lang, self => self.as_str().into_value(), - string: EcoString => Self::from_str(&string)?, + string: EcoString => { + let result = Self::from_str(&string); + if result.is_err() { + if let Some((lang, region)) = string.split_once('-') { + if Lang::from_str(lang).is_ok() && Region::from_str(region).is_ok() { + return result + .hint(eco_format!( + "you should leave only \"{}\" in the `lang` parameter and specify \"{}\" in the `region` parameter", + lang, region, + )); + } + } + } + + result? + } } /// An identifier for a region somewhere in the world. diff --git a/crates/typst/src/text/mod.rs b/crates/typst/src/text/mod.rs index df140419e..4b10b807b 100644 --- a/crates/typst/src/text/mod.rs +++ b/crates/typst/src/text/mod.rs @@ -29,14 +29,13 @@ pub use self::smartquote::*; pub use self::space::*; use std::fmt::{self, Debug, Formatter}; -use std::str::FromStr; use ecow::{eco_format, EcoString}; use rustybuzz::{Feature, Tag}; use smallvec::SmallVec; use ttf_parser::Rect; -use crate::diag::{bail, warning, At, Hint, SourceResult, StrResult}; +use crate::diag::{bail, warning, HintedStrResult, SourceResult}; use crate::engine::Engine; use crate::foundations::{ cast, category, dict, elem, Args, Array, Cast, Category, Construct, Content, Dict, @@ -394,7 +393,6 @@ pub struct TextElem { /// = Einleitung /// In diesem Dokument, ... /// ``` - #[parse(parse_lang(args)?)] #[default(Lang::ENGLISH)] #[ghost] pub lang: Lang, @@ -812,7 +810,7 @@ cast! { self.0.into_value() }, family: FontFamily => Self(vec![family]), - values: Array => Self(values.into_iter().map(|v| v.cast()).collect::>()?), + values: Array => Self(values.into_iter().map(|v| v.cast()).collect::>()?), } /// Resolve a prioritized iterator over the font families. @@ -1131,7 +1129,7 @@ cast! { let tag = v.cast::()?; Ok((Tag::from_bytes_lossy(tag.as_bytes()), 1)) }) - .collect::>()?), + .collect::>()?), values: Dict => Self(values .into_iter() .map(|(k, v)| { @@ -1139,7 +1137,7 @@ cast! { let tag = Tag::from_bytes_lossy(k.as_bytes()); Ok((tag, num)) }) - .collect::>()?), + .collect::>()?), } impl Fold for FontFeatures { @@ -1300,27 +1298,3 @@ cast! { ret }, } - -/// Function to parse the language argument. -/// Provides a hint if a region is used in the language parameter. -fn parse_lang(args: &mut Args) -> SourceResult> { - let Some(Spanned { v: iso, span }) = args.named::>("lang")? else { - return Ok(None); - }; - - let result = Lang::from_str(&iso); - if result.is_err() { - if let Some((lang, region)) = iso.split_once('-') { - if Lang::from_str(lang).is_ok() && Region::from_str(region).is_ok() { - return result - .hint(eco_format!( - "you should leave only \"{}\" in the `lang` parameter and specify \"{}\" in the `region` parameter", - lang, region, - )) - .at(span) - .map(Some); - } - } - } - result.at(span).map(Some) -} diff --git a/crates/typst/src/text/raw.rs b/crates/typst/src/text/raw.rs index 6f900927c..e8cb3b997 100644 --- a/crates/typst/src/text/raw.rs +++ b/crates/typst/src/text/raw.rs @@ -10,7 +10,7 @@ use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder}; use unicode_segmentation::UnicodeSegmentation; use super::Lang; -use crate::diag::{At, FileError, SourceResult, StrResult}; +use crate::diag::{At, FileError, HintedStrResult, SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ cast, elem, scope, Args, Array, Bytes, Content, Fold, NativeElement, Packed, @@ -728,7 +728,7 @@ cast! { SyntaxPaths, self => self.0.into_value(), v: EcoString => Self(vec![v]), - v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), + v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } impl Fold for SyntaxPaths { diff --git a/crates/typst/src/text/smartquote.rs b/crates/typst/src/text/smartquote.rs index 4bb5ca019..236d06363 100644 --- a/crates/typst/src/text/smartquote.rs +++ b/crates/typst/src/text/smartquote.rs @@ -1,7 +1,7 @@ use ecow::EcoString; use unicode_segmentation::UnicodeSegmentation; -use crate::diag::{bail, StrResult}; +use crate::diag::{bail, HintedStrResult, StrResult}; use crate::foundations::{ array, cast, dict, elem, Array, Dict, FromValue, Packed, PlainText, Smart, Str, }; @@ -332,7 +332,7 @@ fn str_to_set(value: &str) -> StrResult<[EcoString; 2]> { } } -fn array_to_set(value: Array) -> StrResult<[EcoString; 2]> { +fn array_to_set(value: Array) -> HintedStrResult<[EcoString; 2]> { let value = value.as_slice(); if value.len() != 2 { bail!( diff --git a/crates/typst/src/visualize/stroke.rs b/crates/typst/src/visualize/stroke.rs index 7d6909a58..234ad1da0 100644 --- a/crates/typst/src/visualize/stroke.rs +++ b/crates/typst/src/visualize/stroke.rs @@ -1,6 +1,6 @@ use ecow::EcoString; -use crate::diag::{SourceResult, StrResult}; +use crate::diag::{HintedStrResult, SourceResult}; use crate::foundations::{ cast, dict, func, scope, ty, Args, Cast, Dict, Fold, FromValue, NoneValue, Repr, Resolve, Smart, StyleChain, Value, @@ -378,7 +378,7 @@ cast! { }, mut dict: Dict => { // Get a value by key, accepting either Auto or something convertible to type T. - fn take(dict: &mut Dict, key: &str) -> StrResult> { + fn take(dict: &mut Dict, key: &str) -> HintedStrResult> { Ok(dict.take(key).ok().map(Smart::::from_value) .transpose()?.unwrap_or(Smart::Auto)) } diff --git a/tests/suite/layout/grid/cell.typ b/tests/suite/layout/grid/cell.typ index 3b08c752a..cc526adb1 100644 --- a/tests/suite/layout/grid/cell.typ +++ b/tests/suite/layout/grid/cell.typ @@ -128,5 +128,6 @@ } --- table-cell-in-grid --- -// Error: 7-19 cannot use `table.cell` as a grid cell; use `grid.cell` instead +// Error: 7-19 cannot use `table.cell` as a grid cell +// Hint: 7-19 use `grid.cell` instead #grid(table.cell[]) diff --git a/tests/suite/layout/grid/footers.typ b/tests/suite/layout/grid/footers.typ index c73bcb393..7cd09003c 100644 --- a/tests/suite/layout/grid/footers.typ +++ b/tests/suite/layout/grid/footers.typ @@ -131,14 +131,16 @@ ) --- table-footer-in-grid --- -// Error: 3:3-3:20 cannot use `table.footer` as a grid footer; use `grid.footer` instead +// Error: 3:3-3:20 cannot use `table.footer` as a grid footer +// Hint: 3:3-3:20 use `grid.footer` instead #grid( [a], table.footer([a]), ) --- grid-footer-in-table --- -// Error: 3:3-3:19 cannot use `grid.footer` as a table footer; use `table.footer` instead +// Error: 3:3-3:19 cannot use `grid.footer` as a table footer +// Hint: 3:3-3:19 use `table.footer` instead #table( [a], grid.footer([a]), diff --git a/tests/suite/layout/grid/headers.typ b/tests/suite/layout/grid/headers.typ index b9a90461e..78a2540f4 100644 --- a/tests/suite/layout/grid/headers.typ +++ b/tests/suite/layout/grid/headers.typ @@ -133,14 +133,16 @@ ) --- table-header-in-grid --- -// Error: 2:3-2:20 cannot use `table.header` as a grid header; use `grid.header` instead +// Error: 2:3-2:20 cannot use `table.header` as a grid header +// Hint: 2:3-2:20 use `grid.header` instead #grid( table.header([a]), [a], ) --- grid-header-in-table --- -// Error: 2:3-2:19 cannot use `grid.header` as a table header; use `table.header` instead +// Error: 2:3-2:19 cannot use `grid.header` as a table header +// Hint: 2:3-2:19 use `table.header` instead #table( grid.header([a]), [a], diff --git a/tests/suite/layout/grid/stroke.typ b/tests/suite/layout/grid/stroke.typ index 9c1c3482d..3a26b69af 100644 --- a/tests/suite/layout/grid/stroke.typ +++ b/tests/suite/layout/grid/stroke.typ @@ -385,19 +385,23 @@ ) --- table-hline-in-grid --- -// Error: 7-20 cannot use `table.hline` as a grid line; use `grid.hline` instead +// Error: 7-20 cannot use `table.hline` as a grid line +// Hint: 7-20 use `grid.hline` instead #grid(table.hline()) --- table-vline-in-grid --- -// Error: 7-20 cannot use `table.vline` as a grid line; use `grid.vline` instead +// Error: 7-20 cannot use `table.vline` as a grid line +// Hint: 7-20 use `grid.vline` instead #grid(table.vline()) --- grid-hline-in-table --- -// Error: 8-20 cannot use `grid.hline` as a table line; use `table.hline` instead +// Error: 8-20 cannot use `grid.hline` as a table line +// Hint: 8-20 use `table.hline` instead #table(grid.hline()) --- grid-vline-in-table --- -// Error: 8-20 cannot use `grid.vline` as a table line; use `table.vline` instead +// Error: 8-20 cannot use `grid.vline` as a table line +// Hint: 8-20 use `table.vline` instead #table(grid.vline()) --- grid-hline-end-before-start-1 --- diff --git a/tests/suite/layout/table.typ b/tests/suite/layout/table.typ index 7eec46a17..4090ef95a 100644 --- a/tests/suite/layout/table.typ +++ b/tests/suite/layout/table.typ @@ -262,7 +262,8 @@ } --- grid-cell-in-table --- -// Error: 8-19 cannot use `grid.cell` as a table cell; use `table.cell` instead +// Error: 8-19 cannot use `grid.cell` as a table cell +// Hint: 8-19 use `table.cell` instead #table(grid.cell[]) --- issue-183-table-lines ---