Refactor casts to use HintedStrResult (#4229)

This commit is contained in:
PgBiel 2024-06-04 12:24:39 -03:00 committed by GitHub
parent d360e753bc
commit 9adcd9a1f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 267 additions and 185 deletions

View File

@ -20,6 +20,7 @@ use clap::Parser;
use codespan_reporting::term; use codespan_reporting::term;
use codespan_reporting::term::termcolor::WriteColor; use codespan_reporting::term::termcolor::WriteColor;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use typst::diag::HintedStrResult;
use crate::args::{CliArguments, Command}; use crate::args::{CliArguments, Command};
use crate::timings::Timer; use crate::timings::Timer;
@ -34,25 +35,32 @@ static ARGS: Lazy<CliArguments> = Lazy::new(CliArguments::parse);
/// Entry point. /// Entry point.
fn main() -> ExitCode { fn main() -> ExitCode {
let timer = Timer::new(&ARGS); let res = dispatch();
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),
};
if let Err(msg) = res { if let Err(msg) = res {
set_failed(); set_failed();
print_error(&msg).expect("failed to print error"); print_error(msg.message()).expect("failed to print error");
} }
EXIT.with(|cell| cell.get()) 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. /// Ensure a failure exit code.
fn set_failed() { fn set_failed() {
EXIT.with(|cell| cell.set(ExitCode::FAILURE)); EXIT.with(|cell| cell.set(ExitCode::FAILURE));

View File

@ -1,7 +1,7 @@
use comemo::Track; use comemo::Track;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use serde::Serialize; use serde::Serialize;
use typst::diag::{bail, StrResult}; use typst::diag::{bail, HintedStrResult, StrResult};
use typst::eval::{eval_string, EvalMode, Tracer}; use typst::eval::{eval_string, EvalMode, Tracer};
use typst::foundations::{Content, IntoValue, LocatableSelector, Scope}; use typst::foundations::{Content, IntoValue, LocatableSelector, Scope};
use typst::model::Document; use typst::model::Document;
@ -14,7 +14,7 @@ use crate::set_failed;
use crate::world::SystemWorld; use crate::world::SystemWorld;
/// Execute a query command. /// Execute a query command.
pub fn query(command: &QueryCommand) -> StrResult<()> { pub fn query(command: &QueryCommand) -> HintedStrResult<()> {
let mut world = SystemWorld::new(&command.common)?; let mut world = SystemWorld::new(&command.common)?;
// Reset everything and ensure that the main file is present. // Reset everything and ensure that the main file is present.
@ -56,7 +56,7 @@ fn retrieve(
world: &dyn World, world: &dyn World,
command: &QueryCommand, command: &QueryCommand,
document: &Document, document: &Document,
) -> StrResult<Vec<Content>> { ) -> HintedStrResult<Vec<Content>> {
let selector = eval_string( let selector = eval_string(
world.track(), world.track(),
&command.selector, &command.selector,

View File

@ -108,7 +108,7 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
let from_value = (!input.from_value.is_empty() || input.dynamic).then(|| { let from_value = (!input.from_value.is_empty() || input.dynamic).then(|| {
quote! { quote! {
impl #foundations::FromValue for #ty { impl #foundations::FromValue for #ty {
fn from_value(value: #foundations::Value) -> ::typst::diag::StrResult<Self> { fn from_value(value: #foundations::Value) -> ::typst::diag::HintedStrResult<Self> {
#from_value_body #from_value_body
} }
} }

View File

@ -38,9 +38,14 @@ use crate::{World, WorldExt};
#[doc(hidden)] #[doc(hidden)]
macro_rules! __bail { macro_rules! __bail {
// For bail!("just a {}", "string") // For bail!("just a {}", "string")
($fmt:literal $(, $arg:expr)* $(,)?) => { (
$fmt:literal $(, $arg:expr)*
$(; hint: $hint:literal $(, $hint_arg:expr)*)*
$(,)?
) => {
return Err($crate::diag::error!( 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] #[macro_export]
#[doc(hidden)] #[doc(hidden)]
macro_rules! __error { macro_rules! __error {
// For bail!("just a {}", "string"). // For bail!("just a {}", "string").
($fmt:literal $(, $arg:expr)* $(,)?) => { ($fmt:literal $(, $arg:expr)* $(,)?) => {
$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),*) $crate::diag::eco_format!($fmt, $($arg),*)
) $(.with_hint($crate::diag::eco_format!($hint, $($hint_arg),*)))*
}; };
// For bail!(span, ...) // For bail!(span, ...)
@ -296,13 +313,48 @@ where
pub type HintedStrResult<T> = Result<T, HintedString>; pub type HintedStrResult<T> = Result<T, HintedString>;
/// A string message with hints. /// 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)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct HintedString { pub struct HintedString(EcoVec<EcoString>);
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. /// 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 /// Additional hints to the user, indicating how this error could be avoided
/// or worked around. /// or worked around.
pub hints: Vec<EcoString>, 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<EcoString>) {
self.0.push(hint.into());
}
/// Adds a single hint to the hinted string.
pub fn with_hint(mut self, hint: impl Into<EcoString>) -> Self {
self.hint(hint);
self
}
/// Adds user-facing hints to the hinted string.
pub fn with_hints(mut self, hints: impl IntoIterator<Item = EcoString>) -> Self {
self.0.extend(hints);
self
}
} }
impl<S> From<S> for HintedString impl<S> From<S> for HintedString
@ -310,14 +362,18 @@ where
S: Into<EcoString>, S: Into<EcoString>,
{ {
fn from(value: S) -> Self { fn from(value: S) -> Self {
Self { message: value.into(), hints: vec![] } Self::new(value.into())
} }
} }
impl<T> At<T> for Result<T, HintedString> { impl<T> At<T> for Result<T, HintedString> {
fn at(self, span: Span) -> SourceResult<T> { fn at(self, span: Span) -> SourceResult<T> {
self.map_err(|diags| { self.map_err(|err| {
eco_vec![SourceDiagnostic::error(span, diags.message).with_hints(diags.hints)] 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<EcoString>, S: Into<EcoString>,
{ {
fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> { fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> {
self.map_err(|message| HintedString { self.map_err(|message| HintedString::new(message.into()).with_hint(hint))
message: message.into(),
hints: vec![hint.into()],
})
} }
} }
impl<T> Hint<T> for HintedStrResult<T> { impl<T> Hint<T> for HintedStrResult<T> {
fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> { fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> {
self.map_err(|mut error| { self.map_err(|mut error| {
error.hints.push(hint.into()); error.hint(hint.into());
error error
}) })
} }

View File

@ -1,6 +1,6 @@
use ecow::{eco_vec, EcoVec}; 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::eval::{ops, CapturesVisitor, Eval, Vm};
use crate::foundations::{ use crate::foundations::{
Array, Capturer, Closure, Content, ContextElem, Dict, Func, NativeElement, Str, Value, Array, Capturer, Closure, Content, ContextElem, Dict, Func, NativeElement, Str, Value,
@ -244,9 +244,9 @@ impl Eval for ast::Dict<'_> {
ast::DictItem::Keyed(keyed) => { ast::DictItem::Keyed(keyed) => {
let raw_key = keyed.key(); let raw_key = keyed.key();
let key = raw_key.eval(vm)?; let key = raw_key.eval(vm)?;
let key = key.cast::<Str>().unwrap_or_else(|error| { let key =
let error = SourceDiagnostic::error(raw_key.span(), error); key.cast::<Str>().at(raw_key.span()).unwrap_or_else(|errors| {
invalid_keys.push(error); invalid_keys.extend(errors);
Str::default() Str::default()
}); });
map.insert(key, keyed.expr().eval(vm)?); map.insert(key, keyed.expr().eval(vm)?);

View File

@ -4,7 +4,7 @@ use std::cmp::Ordering;
use ecow::eco_format; 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::eval::{access_dict, Access, Eval, Vm};
use crate::foundations::{format_str, Datetime, IntoValue, Regex, Repr, Value}; use crate::foundations::{format_str, Datetime, IntoValue, Regex, Repr, Value};
use crate::layout::{Alignment, Length, Rel}; use crate::layout::{Alignment, Length, Rel};
@ -59,7 +59,7 @@ impl Eval for ast::Binary<'_> {
fn apply_binary( fn apply_binary(
binary: ast::Binary, binary: ast::Binary,
vm: &mut Vm, vm: &mut Vm,
op: fn(Value, Value) -> StrResult<Value>, op: fn(Value, Value) -> HintedStrResult<Value>,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
let lhs = binary.lhs().eval(vm)?; let lhs = binary.lhs().eval(vm)?;
@ -78,7 +78,7 @@ fn apply_binary(
fn apply_assignment( fn apply_assignment(
binary: ast::Binary, binary: ast::Binary,
vm: &mut Vm, vm: &mut Vm,
op: fn(Value, Value) -> StrResult<Value>, op: fn(Value, Value) -> HintedStrResult<Value>,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
let rhs = binary.rhs().eval(vm)?; let rhs = binary.rhs().eval(vm)?;
let lhs = binary.lhs(); let lhs = binary.lhs();
@ -102,7 +102,7 @@ fn apply_assignment(
/// Bail with a type mismatch error. /// Bail with a type mismatch error.
macro_rules! mismatch { macro_rules! mismatch {
($fmt:expr, $($value:expr),* $(,)?) => { ($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<Value> {
} }
/// Apply the unary plus operator to a value. /// Apply the unary plus operator to a value.
pub fn pos(value: Value) -> StrResult<Value> { pub fn pos(value: Value) -> HintedStrResult<Value> {
use Value::*; use Value::*;
Ok(match value { Ok(match value {
Int(v) => Int(v), Int(v) => Int(v),
@ -159,7 +159,7 @@ pub fn pos(value: Value) -> StrResult<Value> {
} }
/// Compute the negation of a value. /// Compute the negation of a value.
pub fn neg(value: Value) -> StrResult<Value> { pub fn neg(value: Value) -> HintedStrResult<Value> {
use Value::*; use Value::*;
Ok(match value { Ok(match value {
Int(v) => Int(v.checked_neg().ok_or_else(too_large)?), Int(v) => Int(v.checked_neg().ok_or_else(too_large)?),
@ -176,7 +176,7 @@ pub fn neg(value: Value) -> StrResult<Value> {
} }
/// Compute the sum of two values. /// Compute the sum of two values.
pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn add(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
use Value::*; use Value::*;
Ok(match (lhs, rhs) { Ok(match (lhs, rhs) {
(a, None) => a, (a, None) => a,
@ -252,7 +252,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
} }
/// Compute the difference of two values. /// Compute the difference of two values.
pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn sub(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
use Value::*; use Value::*;
Ok(match (lhs, rhs) { Ok(match (lhs, rhs) {
(Int(a), Int(b)) => Int(a.checked_sub(b).ok_or_else(too_large)?), (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<Value> {
} }
/// Compute the product of two values. /// Compute the product of two values.
pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn mul(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
use Value::*; use Value::*;
Ok(match (lhs, rhs) { Ok(match (lhs, rhs) {
(Int(a), Int(b)) => Int(a.checked_mul(b).ok_or_else(too_large)?), (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<Value> {
} }
/// Compute the quotient of two values. /// Compute the quotient of two values.
pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn div(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
use Value::*; use Value::*;
if is_zero(&rhs) { if is_zero(&rhs) {
bail!("cannot divide by zero"); bail!("cannot divide by zero");
@ -416,7 +416,7 @@ fn try_div_relative(a: Rel<Length>, b: Rel<Length>) -> StrResult<f64> {
} }
/// Compute the logical "not" of a value. /// Compute the logical "not" of a value.
pub fn not(value: Value) -> StrResult<Value> { pub fn not(value: Value) -> HintedStrResult<Value> {
match value { match value {
Value::Bool(b) => Ok(Value::Bool(!b)), Value::Bool(b) => Ok(Value::Bool(!b)),
v => mismatch!("cannot apply 'not' to {}", v), v => mismatch!("cannot apply 'not' to {}", v),
@ -424,7 +424,7 @@ pub fn not(value: Value) -> StrResult<Value> {
} }
/// Compute the logical "and" of two values. /// Compute the logical "and" of two values.
pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn and(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
match (lhs, rhs) { match (lhs, rhs) {
(Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)), (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a && b)),
(a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b), (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b),
@ -432,7 +432,7 @@ pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> {
} }
/// Compute the logical "or" of two values. /// Compute the logical "or" of two values.
pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn or(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
match (lhs, rhs) { match (lhs, rhs) {
(Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)), (Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(a || b)),
(a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b), (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b),
@ -440,19 +440,19 @@ pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> {
} }
/// Compute whether two values are equal. /// Compute whether two values are equal.
pub fn eq(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn eq(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
Ok(Value::Bool(equal(&lhs, &rhs))) Ok(Value::Bool(equal(&lhs, &rhs)))
} }
/// Compute whether two values are unequal. /// Compute whether two values are unequal.
pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn neq(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
Ok(Value::Bool(!equal(&lhs, &rhs))) Ok(Value::Bool(!equal(&lhs, &rhs)))
} }
macro_rules! comparison { macro_rules! comparison {
($name:ident, $op:tt, $($pat:tt)*) => { ($name:ident, $op:tt, $($pat:tt)*) => {
/// Compute how a value compares with another value. /// Compute how a value compares with another value.
pub fn $name(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn $name(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
let ordering = compare(&lhs, &rhs)?; let ordering = compare(&lhs, &rhs)?;
Ok(Value::Bool(matches!(ordering, $($pat)*))) Ok(Value::Bool(matches!(ordering, $($pat)*)))
} }
@ -577,7 +577,7 @@ fn try_cmp_arrays(a: &[Value], b: &[Value]) -> StrResult<Ordering> {
} }
/// Test whether one value is "in" another one. /// Test whether one value is "in" another one.
pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn in_(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
if let Some(b) = contains(&lhs, &rhs) { if let Some(b) = contains(&lhs, &rhs) {
Ok(Value::Bool(b)) Ok(Value::Bool(b))
} else { } else {
@ -586,7 +586,7 @@ pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
} }
/// Test whether one value is "not in" another one. /// Test whether one value is "not in" another one.
pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> { pub fn not_in(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
if let Some(b) = contains(&lhs, &rhs) { if let Some(b) = contains(&lhs, &rhs) {
Ok(Value::Bool(!b)) Ok(Value::Bool(!b))
} else { } else {

View File

@ -178,9 +178,9 @@ impl Args {
}; };
let span = item.value.span; let span = item.value.span;
let spanned = Spanned::new(std::mem::take(&mut item.value.v), 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), Ok(val) => list.push(val),
Err(err) => errors.push(SourceDiagnostic::error(span, err)), Err(diags) => errors.extend(diags),
} }
false false
}); });

View File

@ -8,7 +8,7 @@ use ecow::{eco_format, EcoString, EcoVec};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::SmallVec; 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::engine::Engine;
use crate::eval::ops; use crate::eval::ops;
use crate::foundations::{ use crate::foundations::{
@ -594,7 +594,7 @@ impl Array {
/// be empty. /// be empty.
#[named] #[named]
default: Option<Value>, default: Option<Value>,
) -> StrResult<Value> { ) -> HintedStrResult<Value> {
let mut iter = self.into_iter(); let mut iter = self.into_iter();
let mut acc = iter let mut acc = iter
.next() .next()
@ -615,7 +615,7 @@ impl Array {
/// be empty. /// be empty.
#[named] #[named]
default: Option<Value>, default: Option<Value>,
) -> StrResult<Value> { ) -> HintedStrResult<Value> {
let mut iter = self.into_iter(); let mut iter = self.into_iter();
let mut acc = iter let mut acc = iter
.next() .next()
@ -1095,13 +1095,13 @@ impl<T: IntoValue, const N: usize> IntoValue for SmallVec<[T; N]> {
} }
impl<T: FromValue> FromValue for Vec<T> { impl<T: FromValue> FromValue for Vec<T> {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
value.cast::<Array>()?.into_iter().map(Value::cast).collect() value.cast::<Array>()?.into_iter().map(Value::cast).collect()
} }
} }
impl<T: FromValue, const N: usize> FromValue for SmallVec<[T; N]> { impl<T: FromValue, const N: usize> FromValue for SmallVec<[T; N]> {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
value.cast::<Array>()?.into_iter().map(Value::cast).collect() value.cast::<Array>()?.into_iter().map(Value::cast).collect()
} }
} }

View File

@ -1,7 +1,7 @@
use ecow::EcoString; use ecow::EcoString;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use crate::diag::StrResult; use crate::diag::HintedStrResult;
use crate::foundations::{ use crate::foundations::{
ty, CastInfo, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain, Type, ty, CastInfo, Fold, FromValue, IntoValue, Reflect, Repr, Resolve, StyleChain, Type,
Value, Value,
@ -26,7 +26,7 @@ impl IntoValue for AutoValue {
} }
impl FromValue for AutoValue { impl FromValue for AutoValue {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
match value { match value {
Value::Auto => Ok(Self), Value::Auto => Ok(Self),
_ => Err(Self::error(&value)), _ => Err(Self::error(&value)),
@ -236,7 +236,7 @@ impl<T: IntoValue> IntoValue for Smart<T> {
} }
impl<T: FromValue> FromValue for Smart<T> { impl<T: FromValue> FromValue for Smart<T> {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
match value { match value {
Value::Auto => Ok(Self::Auto), Value::Auto => Ok(Self::Auto),
v if T::castable(&v) => Ok(Self::Custom(T::from_value(v)?)), v if T::castable(&v) => Ok(Self::Custom(T::from_value(v)?)),

View File

@ -7,7 +7,7 @@ use ecow::{eco_format, EcoString};
use smallvec::SmallVec; use smallvec::SmallVec;
use unicode_math_class::MathClass; 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::foundations::{array, repr, NativeElement, Packed, Repr, Str, Type, Value};
use crate::syntax::{Span, Spanned}; use crate::syntax::{Span, Spanned};
@ -50,8 +50,8 @@ pub trait Reflect {
/// "expected integer, found none", /// "expected integer, found none",
/// ); /// );
/// ``` /// ```
fn error(found: &Value) -> EcoString { fn error(found: &Value) -> HintedString {
Self::input().error(found) Self::input().error(found).into()
} }
} }
@ -249,17 +249,17 @@ impl<T: IntoValue> IntoValue for fn() -> T {
/// See also: [`Reflect`]. /// See also: [`Reflect`].
pub trait FromValue<V = Value>: Sized + Reflect { pub trait FromValue<V = Value>: Sized + Reflect {
/// Try to cast the value into an instance of `Self`. /// Try to cast the value into an instance of `Self`.
fn from_value(value: V) -> StrResult<Self>; fn from_value(value: V) -> HintedStrResult<Self>;
} }
impl FromValue for Value { impl FromValue for Value {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
Ok(value) Ok(value)
} }
} }
impl<T: NativeElement + FromValue> FromValue for Packed<T> { impl<T: NativeElement + FromValue> FromValue for Packed<T> {
fn from_value(mut value: Value) -> StrResult<Self> { fn from_value(mut value: Value) -> HintedStrResult<Self> {
if let Value::Content(content) = value { if let Value::Content(content) = value {
match content.into_packed::<T>() { match content.into_packed::<T>() {
Ok(packed) => return Ok(packed), Ok(packed) => return Ok(packed),
@ -272,13 +272,13 @@ impl<T: NativeElement + FromValue> FromValue for Packed<T> {
} }
impl<T: FromValue> FromValue<Spanned<Value>> for T { impl<T: FromValue> FromValue<Spanned<Value>> for T {
fn from_value(value: Spanned<Value>) -> StrResult<Self> { fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
T::from_value(value.v) T::from_value(value.v)
} }
} }
impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> { impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> {
fn from_value(value: Spanned<Value>) -> StrResult<Self> { fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
let span = value.span; let span = value.span;
T::from_value(value.v).map(|t| Spanned::new(t, span)) T::from_value(value.v).map(|t| Spanned::new(t, span))
} }
@ -432,7 +432,7 @@ impl IntoValue for Never {
} }
impl FromValue for Never { impl FromValue for Never {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
Err(Self::error(&value)) Err(Self::error(&value))
} }
} }

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter};
use ecow::EcoString; use ecow::EcoString;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use crate::diag::StrResult; use crate::diag::HintedStrResult;
use crate::foundations::{ use crate::foundations::{
cast, ty, CastInfo, FromValue, IntoValue, Reflect, Repr, Type, Value, cast, ty, CastInfo, FromValue, IntoValue, Reflect, Repr, Type, Value,
}; };
@ -45,7 +45,7 @@ impl IntoValue for NoneValue {
} }
impl FromValue for NoneValue { impl FromValue for NoneValue {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
match value { match value {
Value::None => Ok(Self), Value::None => Ok(Self),
_ => Err(Self::error(&value)), _ => Err(Self::error(&value)),
@ -104,7 +104,7 @@ impl<T: IntoValue> IntoValue for Option<T> {
} }
impl<T: FromValue> FromValue for Option<T> { impl<T: FromValue> FromValue for Option<T> {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
match value { match value {
Value::None => Ok(None), Value::None => Ok(None),
v if T::castable(&v) => Ok(Some(T::from_value(v)?)), v if T::castable(&v) => Ok(Some(T::from_value(v)?)),

View File

@ -97,17 +97,14 @@ fn cannot_mutate_constant(var: &str) -> HintedString {
/// The error message when a variable is not found. /// The error message when a variable is not found.
#[cold] #[cold]
fn unknown_variable(var: &str) -> HintedString { fn unknown_variable(var: &str) -> HintedString {
let mut res = HintedString { let mut res = HintedString::new(eco_format!("unknown variable: {}", var));
message: eco_format!("unknown variable: {}", var),
hints: vec![],
};
if matches!(var, "none" | "auto" | "false" | "true") { 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" "if you meant to use a literal, try adding a hash before it"
)); ));
} else if var.contains('-') { } 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", "if you meant to use subtraction, try adding spaces around the minus sign",
)); ));
} }

View File

@ -339,7 +339,7 @@ cast! {
} }
impl FromValue for LocatableSelector { impl FromValue for LocatableSelector {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
fn validate(selector: &Selector) -> StrResult<()> { fn validate(selector: &Selector) -> StrResult<()> {
match selector { match selector {
Selector::Elem(elem, _) => { Selector::Elem(elem, _) => {
@ -421,8 +421,8 @@ cast! {
} }
impl FromValue for ShowableSelector { impl FromValue for ShowableSelector {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
fn validate(selector: &Selector, nested: bool) -> StrResult<()> { fn validate(selector: &Selector, nested: bool) -> HintedStrResult<()> {
match selector { match selector {
Selector::Elem(_, _) => {} Selector::Elem(_, _) => {}
Selector::Label(_) => {} Selector::Label(_) => {}

View File

@ -9,7 +9,7 @@ use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer};
use serde::de::{Error, MapAccess, SeqAccess, Visitor}; use serde::de::{Error, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::diag::StrResult; use crate::diag::{HintedStrResult, HintedString, StrResult};
use crate::eval::ops; use crate::eval::ops;
use crate::foundations::{ use crate::foundations::{
fields, repr, Args, Array, AutoValue, Bytes, CastInfo, Content, Datetime, Dict, 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. /// Try to cast the value into a specific type.
pub fn cast<T: FromValue>(self) -> StrResult<T> { pub fn cast<T: FromValue>(self) -> HintedStrResult<T> {
T::from_value(self) T::from_value(self)
} }
@ -606,7 +606,7 @@ macro_rules! primitive {
} }
impl FromValue for $ty { impl FromValue for $ty {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
match value { match value {
Value::$variant(v) => Ok(v), Value::$variant(v) => Ok(v),
$(Value::$other$(($binding))? => Ok($out),)* $(Value::$other$(($binding))? => Ok($out),)*
@ -614,7 +614,7 @@ macro_rules! primitive {
"expected {}, found {}", "expected {}, found {}",
Type::of::<Self>(), Type::of::<Self>(),
v.ty(), v.ty(),
)), ).into()),
} }
} }
} }
@ -682,7 +682,7 @@ impl<T: Reflect> Reflect for Arc<T> {
T::castable(value) T::castable(value)
} }
fn error(found: &Value) -> EcoString { fn error(found: &Value) -> HintedString {
T::error(found) T::error(found)
} }
} }
@ -694,7 +694,7 @@ impl<T: Clone + IntoValue> IntoValue for Arc<T> {
} }
impl<T: FromValue> FromValue for Arc<T> { impl<T: FromValue> FromValue for Arc<T> {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
match value { match value {
v if T::castable(&v) => Ok(Arc::new(T::from_value(v)?)), v if T::castable(&v) => Ok(Arc::new(T::from_value(v)?)),
_ => Err(Self::error(&value)), _ => Err(Self::error(&value)),

View File

@ -5,7 +5,7 @@ use comemo::{Track, Tracked, TrackedMut};
use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use smallvec::{smallvec, SmallVec}; 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::engine::{Engine, Route};
use crate::eval::Tracer; use crate::eval::Tracer;
use crate::foundations::{ use crate::foundations::{
@ -709,7 +709,7 @@ cast! {
array: Array => Self(array array: Array => Self(array
.into_iter() .into_iter()
.map(Value::cast) .map(Value::cast)
.collect::<StrResult<_>>()?), .collect::<HintedStrResult<_>>()?),
} }
/// Executes an update of a counter. /// Executes an update of a counter.

View File

@ -2,7 +2,7 @@ use std::ops::Add;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, HintedStrResult, SourceResult, StrResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Packed, cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Packed,
@ -637,7 +637,7 @@ where
H: Reflect + TryFrom<Alignment, Error = EcoString>, H: Reflect + TryFrom<Alignment, Error = EcoString>,
V: Reflect + TryFrom<Alignment, Error = EcoString>, V: Reflect + TryFrom<Alignment, Error = EcoString>,
{ {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
if Alignment::castable(&value) { if Alignment::castable(&value) {
let align = Alignment::from_value(value)?; let align = Alignment::from_value(value)?;
let result = match align { let result = match align {

View File

@ -1,6 +1,6 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use crate::diag::StrResult; use crate::diag::HintedStrResult;
use crate::foundations::{ use crate::foundations::{
AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve, AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve,
StyleChain, Value, StyleChain, Value,
@ -175,7 +175,7 @@ impl<T> FromValue for Corners<Option<T>>
where where
T: FromValue + Clone, T: FromValue + Clone,
{ {
fn from_value(mut value: Value) -> StrResult<Self> { fn from_value(mut value: Value) -> HintedStrResult<Self> {
let expected_keys = [ let expected_keys = [
"top-left", "top-left",
"top-right", "top-right",
@ -224,7 +224,7 @@ where
let keys = dict.iter().map(|kv| kv.0.as_str()).collect(); let keys = dict.iter().map(|kv| kv.0.as_str()).collect();
// Do not hint at expected_keys, because T may be castable from Dict // Do not hint at expected_keys, because T may be castable from Dict
// objects with other sets of expected keys. // objects with other sets of expected keys.
Err(Dict::unexpected_keys(keys, None)) Err(Dict::unexpected_keys(keys, None).into())
} else { } else {
Err(Self::error(&value)) Err(Self::error(&value))
} }

View File

@ -6,9 +6,7 @@ use ecow::eco_format;
use super::lines::Line; use super::lines::Line;
use super::repeated::{Footer, Header, Repeatable}; use super::repeated::{Footer, Header, Repeatable};
use crate::diag::{ use crate::diag::{bail, At, Hint, HintedStrResult, HintedString, SourceResult};
bail, At, Hint, HintedStrResult, HintedString, SourceResult, StrResult,
};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
Array, CastInfo, Content, Context, Fold, FromValue, Func, IntoValue, Reflect, Array, CastInfo, Content, Context, Fold, FromValue, Func, IntoValue, Reflect,
@ -85,11 +83,11 @@ impl<T: IntoValue> IntoValue for Celled<T> {
} }
impl<T: FromValue> FromValue for Celled<T> { impl<T: FromValue> FromValue for Celled<T> {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
match value { match value {
Value::Func(v) => Ok(Self::Func(v)), Value::Func(v) => Ok(Self::Func(v)),
Value::Array(array) => Ok(Self::Array( Value::Array(array) => Ok(Self::Array(
array.into_iter().map(T::from_value).collect::<StrResult<_>>()?, array.into_iter().map(T::from_value).collect::<HintedStrResult<_>>()?,
)), )),
v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)), v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)),
v => Err(Self::error(&v)), v => Err(Self::error(&v)),

View File

@ -13,10 +13,10 @@ pub use self::lines::LinePosition;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::sync::Arc; use std::sync::Arc;
use ecow::{eco_format, EcoString}; use ecow::eco_format;
use smallvec::{smallvec, SmallVec}; 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::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, scope, Array, Content, Fold, NativeElement, Packed, Show, Smart, cast, elem, scope, Array, Content, Fold, NativeElement, Packed, Show, Smart,
@ -406,7 +406,7 @@ cast! {
self => self.0.into_value(), self => self.0.into_value(),
sizing: Sizing => Self(smallvec![sizing]), sizing: Sizing => Self(smallvec![sizing]),
count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]), count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]),
values: Array => Self(values.into_iter().map(Value::cast).collect::<StrResult<_>>()?), values: Array => Self(values.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
} }
/// Any child of a grid element. /// Any child of a grid element.
@ -430,13 +430,19 @@ cast! {
} }
impl TryFrom<Content> for GridChild { impl TryFrom<Content> for GridChild {
type Error = EcoString; type Error = HintedString;
fn try_from(value: Content) -> StrResult<Self> { fn try_from(value: Content) -> HintedStrResult<Self> {
if value.is::<TableHeader>() { if value.is::<TableHeader>() {
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::<TableFooter>() { if value.is::<TableFooter>() {
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 value
@ -506,8 +512,8 @@ cast! {
} }
impl TryFrom<Content> for GridItem { impl TryFrom<Content> for GridItem {
type Error = EcoString; type Error = HintedString;
fn try_from(value: Content) -> StrResult<Self> { fn try_from(value: Content) -> HintedStrResult<Self> {
if value.is::<GridHeader>() { if value.is::<GridHeader>() {
bail!("cannot place a grid header within another header or footer"); bail!("cannot place a grid header within another header or footer");
} }
@ -521,13 +527,22 @@ impl TryFrom<Content> for GridItem {
bail!("cannot place a table footer within another footer or header"); bail!("cannot place a table footer within another footer or header");
} }
if value.is::<TableCell>() { if value.is::<TableCell>() {
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::<TableHLine>() { if value.is::<TableHLine>() {
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::<TableVLine>() { if value.is::<TableVLine>() {
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 Ok(value

View File

@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::ops::Add; use std::ops::Add;
use crate::diag::{bail, StrResult}; use crate::diag::{bail, HintedStrResult};
use crate::foundations::{ use crate::foundations::{
cast, AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve, cast, AlternativeFold, CastInfo, Dict, Fold, FromValue, IntoValue, Reflect, Resolve,
StyleChain, Value, StyleChain, Value,
@ -209,7 +209,7 @@ impl<T> FromValue for Sides<Option<T>>
where where
T: Default + FromValue + Clone, T: Default + FromValue + Clone,
{ {
fn from_value(mut value: Value) -> StrResult<Self> { fn from_value(mut value: Value) -> HintedStrResult<Self> {
let expected_keys = ["left", "top", "right", "bottom", "x", "y", "rest"]; let expected_keys = ["left", "top", "right", "bottom", "x", "y", "rest"];
if let Value::Dict(dict) = &mut value { if let Value::Dict(dict) = &mut value {
if dict.is_empty() { if dict.is_empty() {
@ -237,7 +237,7 @@ where
let keys = dict.iter().map(|kv| kv.0.as_str()).collect(); let keys = dict.iter().map(|kv| kv.0.as_str()).collect();
// Do not hint at expected_keys, because T may be castable from Dict // Do not hint at expected_keys, because T may be castable from Dict
// objects with other sets of expected keys. // objects with other sets of expected keys.
Err(Dict::unexpected_keys(keys, None)) Err(Dict::unexpected_keys(keys, None).into())
} else { } else {
Err(Self::error(&value)) Err(Self::error(&value))
} }

View File

@ -1,7 +1,7 @@
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use unicode_math_class::MathClass; use unicode_math_class::MathClass;
use crate::diag::{bail, At, SourceResult, StrResult}; use crate::diag::{bail, At, HintedStrResult, SourceResult, StrResult};
use crate::foundations::{ use crate::foundations::{
array, cast, dict, elem, Array, Content, Dict, Fold, NoneValue, Packed, Resolve, array, cast, dict, elem, Array, Content, Dict, Fold, NoneValue, Packed, Resolve,
Smart, StyleChain, Value, Smart, StyleChain, Value,
@ -712,5 +712,5 @@ cast! {
AugmentOffsets, AugmentOffsets,
self => self.0.into_value(), self => self.0.into_value(),
v: isize => Self(smallvec![v]), v: isize => Self(smallvec![v]),
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?), v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
} }

View File

@ -19,7 +19,7 @@ use once_cell::sync::Lazy;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use typed_arena::Arena; 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::engine::Engine;
use crate::eval::{eval_string, EvalMode}; use crate::eval::{eval_string, EvalMode};
use crate::foundations::{ use crate::foundations::{
@ -150,7 +150,7 @@ cast! {
BibliographyPaths, BibliographyPaths,
self => self.0.into_value(), self => self.0.into_value(),
v: EcoString => Self(vec![v]), v: EcoString => Self(vec![v]),
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?), v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
} }
impl BibliographyElem { impl BibliographyElem {
@ -508,7 +508,7 @@ impl Reflect for CslStyle {
} }
impl FromValue for CslStyle { impl FromValue for CslStyle {
fn from_value(value: Value) -> StrResult<Self> { fn from_value(value: Value) -> HintedStrResult<Self> {
if let Value::Dyn(dynamic) = &value { if let Value::Dyn(dynamic) = &value {
if let Some(concrete) = dynamic.downcast::<Self>() { if let Some(concrete) = dynamic.downcast::<Self>() {
return Ok(concrete.clone()); return Ok(concrete.clone());

View File

@ -1,6 +1,6 @@
use ecow::EcoString; use ecow::EcoString;
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, HintedStrResult, SourceResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain, cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain,
@ -115,7 +115,7 @@ cast! {
Author, Author,
self => self.0.into_value(), self => self.0.into_value(),
v: EcoString => Self(vec![v]), v: EcoString => Self(vec![v]),
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?), v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
} }
/// A list of keywords. /// A list of keywords.
@ -126,7 +126,7 @@ cast! {
Keywords, Keywords,
self => self.0.into_value(), self => self.0.into_value(),
v: EcoString => Self(vec![v]), v: EcoString => Self(vec![v]),
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?), v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
} }
/// A finished document with metadata and page frames. /// A finished document with metadata and page frames.

View File

@ -1,9 +1,9 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::sync::Arc; 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::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, scope, Content, Fold, NativeElement, Packed, Show, Smart, StyleChain, cast, elem, scope, Content, Fold, NativeElement, Packed, Show, Smart, StyleChain,
@ -346,17 +346,19 @@ cast! {
} }
impl TryFrom<Content> for TableChild { impl TryFrom<Content> for TableChild {
type Error = EcoString; type Error = HintedString;
fn try_from(value: Content) -> StrResult<Self> { fn try_from(value: Content) -> HintedStrResult<Self> {
if value.is::<GridHeader>() { if value.is::<GridHeader>() {
bail!( 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::<GridFooter>() { if value.is::<GridFooter>() {
bail!( 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<Content> for TableItem { impl TryFrom<Content> for TableItem {
type Error = EcoString; type Error = HintedString;
fn try_from(value: Content) -> StrResult<Self> { fn try_from(value: Content) -> HintedStrResult<Self> {
if value.is::<GridHeader>() { if value.is::<GridHeader>() {
bail!("cannot place a grid header within another header or footer"); bail!("cannot place a grid header within another header or footer");
} }
@ -443,13 +445,22 @@ impl TryFrom<Content> for TableItem {
bail!("cannot place a table footer within another footer or header"); bail!("cannot place a table footer within another footer or header");
} }
if value.is::<GridCell>() { if value.is::<GridCell>() {
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::<GridHLine>() { if value.is::<GridHLine>() {
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::<GridVLine>() { if value.is::<GridVLine>() {
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 Ok(value

View File

@ -1,7 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use ecow::EcoString; use crate::diag::Hint;
use ecow::{eco_format, EcoString};
use crate::foundations::{cast, StyleChain}; use crate::foundations::{cast, StyleChain};
use crate::layout::Dir; use crate::layout::Dir;
@ -121,7 +122,22 @@ impl FromStr for Lang {
cast! { cast! {
Lang, Lang,
self => self.as_str().into_value(), 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. /// An identifier for a region somewhere in the world.

View File

@ -29,14 +29,13 @@ pub use self::smartquote::*;
pub use self::space::*; pub use self::space::*;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::str::FromStr;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use rustybuzz::{Feature, Tag}; use rustybuzz::{Feature, Tag};
use smallvec::SmallVec; use smallvec::SmallVec;
use ttf_parser::Rect; 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::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, category, dict, elem, Args, Array, Cast, Category, Construct, Content, Dict, cast, category, dict, elem, Args, Array, Cast, Category, Construct, Content, Dict,
@ -394,7 +393,6 @@ pub struct TextElem {
/// = Einleitung /// = Einleitung
/// In diesem Dokument, ... /// In diesem Dokument, ...
/// ``` /// ```
#[parse(parse_lang(args)?)]
#[default(Lang::ENGLISH)] #[default(Lang::ENGLISH)]
#[ghost] #[ghost]
pub lang: Lang, pub lang: Lang,
@ -812,7 +810,7 @@ cast! {
self.0.into_value() self.0.into_value()
}, },
family: FontFamily => Self(vec![family]), family: FontFamily => Self(vec![family]),
values: Array => Self(values.into_iter().map(|v| v.cast()).collect::<StrResult<_>>()?), values: Array => Self(values.into_iter().map(|v| v.cast()).collect::<HintedStrResult<_>>()?),
} }
/// Resolve a prioritized iterator over the font families. /// Resolve a prioritized iterator over the font families.
@ -1131,7 +1129,7 @@ cast! {
let tag = v.cast::<EcoString>()?; let tag = v.cast::<EcoString>()?;
Ok((Tag::from_bytes_lossy(tag.as_bytes()), 1)) Ok((Tag::from_bytes_lossy(tag.as_bytes()), 1))
}) })
.collect::<StrResult<_>>()?), .collect::<HintedStrResult<_>>()?),
values: Dict => Self(values values: Dict => Self(values
.into_iter() .into_iter()
.map(|(k, v)| { .map(|(k, v)| {
@ -1139,7 +1137,7 @@ cast! {
let tag = Tag::from_bytes_lossy(k.as_bytes()); let tag = Tag::from_bytes_lossy(k.as_bytes());
Ok((tag, num)) Ok((tag, num))
}) })
.collect::<StrResult<_>>()?), .collect::<HintedStrResult<_>>()?),
} }
impl Fold for FontFeatures { impl Fold for FontFeatures {
@ -1300,27 +1298,3 @@ cast! {
ret 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<Option<Lang>> {
let Some(Spanned { v: iso, span }) = args.named::<Spanned<EcoString>>("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)
}

View File

@ -10,7 +10,7 @@ use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::Lang; use super::Lang;
use crate::diag::{At, FileError, SourceResult, StrResult}; use crate::diag::{At, FileError, HintedStrResult, SourceResult, StrResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, scope, Args, Array, Bytes, Content, Fold, NativeElement, Packed, cast, elem, scope, Args, Array, Bytes, Content, Fold, NativeElement, Packed,
@ -728,7 +728,7 @@ cast! {
SyntaxPaths, SyntaxPaths,
self => self.0.into_value(), self => self.0.into_value(),
v: EcoString => Self(vec![v]), v: EcoString => Self(vec![v]),
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?), v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
} }
impl Fold for SyntaxPaths { impl Fold for SyntaxPaths {

View File

@ -1,7 +1,7 @@
use ecow::EcoString; use ecow::EcoString;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{bail, StrResult}; use crate::diag::{bail, HintedStrResult, StrResult};
use crate::foundations::{ use crate::foundations::{
array, cast, dict, elem, Array, Dict, FromValue, Packed, PlainText, Smart, Str, 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(); let value = value.as_slice();
if value.len() != 2 { if value.len() != 2 {
bail!( bail!(

View File

@ -1,6 +1,6 @@
use ecow::EcoString; use ecow::EcoString;
use crate::diag::{SourceResult, StrResult}; use crate::diag::{HintedStrResult, SourceResult};
use crate::foundations::{ use crate::foundations::{
cast, dict, func, scope, ty, Args, Cast, Dict, Fold, FromValue, NoneValue, Repr, cast, dict, func, scope, ty, Args, Cast, Dict, Fold, FromValue, NoneValue, Repr,
Resolve, Smart, StyleChain, Value, Resolve, Smart, StyleChain, Value,
@ -378,7 +378,7 @@ cast! {
}, },
mut dict: Dict => { mut dict: Dict => {
// Get a value by key, accepting either Auto or something convertible to type T. // Get a value by key, accepting either Auto or something convertible to type T.
fn take<T: FromValue>(dict: &mut Dict, key: &str) -> StrResult<Smart<T>> { fn take<T: FromValue>(dict: &mut Dict, key: &str) -> HintedStrResult<Smart<T>> {
Ok(dict.take(key).ok().map(Smart::<T>::from_value) Ok(dict.take(key).ok().map(Smart::<T>::from_value)
.transpose()?.unwrap_or(Smart::Auto)) .transpose()?.unwrap_or(Smart::Auto))
} }

View File

@ -128,5 +128,6 @@
} }
--- table-cell-in-grid --- --- 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[]) #grid(table.cell[])

View File

@ -131,14 +131,16 @@
) )
--- table-footer-in-grid --- --- 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( #grid(
[a], [a],
table.footer([a]), table.footer([a]),
) )
--- grid-footer-in-table --- --- 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( #table(
[a], [a],
grid.footer([a]), grid.footer([a]),

View File

@ -133,14 +133,16 @@
) )
--- table-header-in-grid --- --- 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( #grid(
table.header([a]), table.header([a]),
[a], [a],
) )
--- grid-header-in-table --- --- 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( #table(
grid.header([a]), grid.header([a]),
[a], [a],

View File

@ -385,19 +385,23 @@
) )
--- table-hline-in-grid --- --- 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()) #grid(table.hline())
--- table-vline-in-grid --- --- 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(table.vline())
--- grid-hline-in-table --- --- 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()) #table(grid.hline())
--- grid-vline-in-table --- --- 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()) #table(grid.vline())
--- grid-hline-end-before-start-1 --- --- grid-hline-end-before-start-1 ---

View File

@ -262,7 +262,8 @@
} }
--- grid-cell-in-table --- --- 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[]) #table(grid.cell[])
--- issue-183-table-lines --- --- issue-183-table-lines ---