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::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<CliArguments> = 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));

View File

@ -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<Vec<Content>> {
) -> HintedStrResult<Vec<Content>> {
let selector = eval_string(
world.track(),
&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(|| {
quote! {
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
}
}

View File

@ -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<T> = Result<T, HintedString>;
/// 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<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.
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<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
@ -310,14 +362,18 @@ where
S: Into<EcoString>,
{
fn from(value: S) -> Self {
Self { message: value.into(), hints: vec![] }
Self::new(value.into())
}
}
impl<T> At<T> for Result<T, HintedString> {
fn at(self, span: Span) -> SourceResult<T> {
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<EcoString>,
{
fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> {
self.map_err(|message| HintedString {
message: message.into(),
hints: vec![hint.into()],
})
self.map_err(|message| HintedString::new(message.into()).with_hint(hint))
}
}
impl<T> Hint<T> for HintedStrResult<T> {
fn hint(self, hint: impl Into<EcoString>) -> HintedStrResult<T> {
self.map_err(|mut error| {
error.hints.push(hint.into());
error.hint(hint.into());
error
})
}

View File

@ -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::<Str>().unwrap_or_else(|error| {
let error = SourceDiagnostic::error(raw_key.span(), error);
invalid_keys.push(error);
Str::default()
});
let key =
key.cast::<Str>().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)? {

View File

@ -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<Value>,
op: fn(Value, Value) -> HintedStrResult<Value>,
) -> SourceResult<Value> {
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<Value>,
op: fn(Value, Value) -> HintedStrResult<Value>,
) -> SourceResult<Value> {
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<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::*;
Ok(match value {
Int(v) => Int(v),
@ -159,7 +159,7 @@ pub fn pos(value: Value) -> StrResult<Value> {
}
/// Compute the negation of a value.
pub fn neg(value: Value) -> StrResult<Value> {
pub fn neg(value: Value) -> HintedStrResult<Value> {
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<Value> {
}
/// 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::*;
Ok(match (lhs, rhs) {
(a, None) => a,
@ -252,7 +252,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
}
/// 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::*;
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<Value> {
}
/// 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::*;
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<Value> {
}
/// 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::*;
if is_zero(&rhs) {
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.
pub fn not(value: Value) -> StrResult<Value> {
pub fn not(value: Value) -> HintedStrResult<Value> {
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<Value> {
}
/// 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) {
(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<Value> {
}
/// 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) {
(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<Value> {
}
/// 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)))
}
/// 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)))
}
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<Value> {
pub fn $name(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
let ordering = compare(&lhs, &rhs)?;
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.
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) {
Ok(Value::Bool(b))
} else {
@ -586,7 +586,7 @@ pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
}
/// 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) {
Ok(Value::Bool(!b))
} else {

View File

@ -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
});

View File

@ -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<Value>,
) -> StrResult<Value> {
) -> HintedStrResult<Value> {
let mut iter = self.into_iter();
let mut acc = iter
.next()
@ -615,7 +615,7 @@ impl Array {
/// be empty.
#[named]
default: Option<Value>,
) -> StrResult<Value> {
) -> HintedStrResult<Value> {
let mut iter = self.into_iter();
let mut acc = iter
.next()
@ -1095,13 +1095,13 @@ impl<T: IntoValue, const N: usize> IntoValue for SmallVec<[T; N]> {
}
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()
}
}
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()
}
}

View File

@ -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<Self> {
fn from_value(value: Value) -> HintedStrResult<Self> {
match value {
Value::Auto => Ok(Self),
_ => Err(Self::error(&value)),
@ -236,7 +236,7 @@ impl<T: IntoValue> IntoValue 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 {
Value::Auto => Ok(Self::Auto),
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 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<T: IntoValue> IntoValue for fn() -> T {
/// See also: [`Reflect`].
pub trait FromValue<V = Value>: Sized + Reflect {
/// 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 {
fn from_value(value: Value) -> StrResult<Self> {
fn from_value(value: Value) -> HintedStrResult<Self> {
Ok(value)
}
}
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 {
match content.into_packed::<T>() {
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 {
fn from_value(value: Spanned<Value>) -> StrResult<Self> {
fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
T::from_value(value.v)
}
}
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;
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<Self> {
fn from_value(value: Value) -> HintedStrResult<Self> {
Err(Self::error(&value))
}
}

View File

@ -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<Self> {
fn from_value(value: Value) -> HintedStrResult<Self> {
match value {
Value::None => Ok(Self),
_ => Err(Self::error(&value)),
@ -104,7 +104,7 @@ impl<T: IntoValue> IntoValue 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 {
Value::None => Ok(None),
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.
#[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",
));
}

View File

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

View File

@ -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<T: FromValue>(self) -> StrResult<T> {
pub fn cast<T: FromValue>(self) -> HintedStrResult<T> {
T::from_value(self)
}
@ -606,7 +606,7 @@ macro_rules! primitive {
}
impl FromValue for $ty {
fn from_value(value: Value) -> StrResult<Self> {
fn from_value(value: Value) -> HintedStrResult<Self> {
match value {
Value::$variant(v) => Ok(v),
$(Value::$other$(($binding))? => Ok($out),)*
@ -614,7 +614,7 @@ macro_rules! primitive {
"expected {}, found {}",
Type::of::<Self>(),
v.ty(),
)),
).into()),
}
}
}
@ -682,7 +682,7 @@ impl<T: Reflect> Reflect for Arc<T> {
T::castable(value)
}
fn error(found: &Value) -> EcoString {
fn error(found: &Value) -> HintedString {
T::error(found)
}
}
@ -694,7 +694,7 @@ impl<T: Clone + IntoValue> IntoValue 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 {
v if T::castable(&v) => Ok(Arc::new(T::from_value(v)?)),
_ => Err(Self::error(&value)),

View File

@ -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::<StrResult<_>>()?),
.collect::<HintedStrResult<_>>()?),
}
/// Executes an update of a counter.

View File

@ -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<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) {
let align = Alignment::from_value(value)?;
let result = match align {

View File

@ -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<T> FromValue for Corners<Option<T>>
where
T: FromValue + Clone,
{
fn from_value(mut value: Value) -> StrResult<Self> {
fn from_value(mut value: Value) -> HintedStrResult<Self> {
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))
}

View File

@ -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<T: IntoValue> IntoValue 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 {
Value::Func(v) => Ok(Self::Func(v)),
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 => Err(Self::error(&v)),

View File

@ -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::<StrResult<_>>()?),
values: Array => Self(values.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
}
/// Any child of a grid element.
@ -430,13 +430,19 @@ cast! {
}
impl TryFrom<Content> for GridChild {
type Error = EcoString;
fn try_from(value: Content) -> StrResult<Self> {
type Error = HintedString;
fn try_from(value: Content) -> HintedStrResult<Self> {
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>() {
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<Content> for GridItem {
type Error = EcoString;
fn try_from(value: Content) -> StrResult<Self> {
type Error = HintedString;
fn try_from(value: Content) -> HintedStrResult<Self> {
if value.is::<GridHeader>() {
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");
}
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>() {
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>() {
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

View File

@ -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<T> FromValue for Sides<Option<T>>
where
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"];
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))
}

View File

@ -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::<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 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::<StrResult<_>>()?),
v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
}
impl BibliographyElem {
@ -508,7 +508,7 @@ impl Reflect 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 Some(concrete) = dynamic.downcast::<Self>() {
return Ok(concrete.clone());

View File

@ -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::<StrResult<_>>()?),
v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
}
/// 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::<StrResult<_>>()?),
v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
}
/// A finished document with metadata and page frames.

View File

@ -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<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>() {
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>() {
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 {
type Error = EcoString;
type Error = HintedString;
fn try_from(value: Content) -> StrResult<Self> {
fn try_from(value: Content) -> HintedStrResult<Self> {
if value.is::<GridHeader>() {
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");
}
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>() {
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>() {
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

View File

@ -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.

View File

@ -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::<StrResult<_>>()?),
values: Array => Self(values.into_iter().map(|v| v.cast()).collect::<HintedStrResult<_>>()?),
}
/// Resolve a prioritized iterator over the font families.
@ -1131,7 +1129,7 @@ cast! {
let tag = v.cast::<EcoString>()?;
Ok((Tag::from_bytes_lossy(tag.as_bytes()), 1))
})
.collect::<StrResult<_>>()?),
.collect::<HintedStrResult<_>>()?),
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::<StrResult<_>>()?),
.collect::<HintedStrResult<_>>()?),
}
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<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 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::<StrResult<_>>()?),
v: Array => Self(v.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
}
impl Fold for SyntaxPaths {

View File

@ -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!(

View File

@ -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<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)
.transpose()?.unwrap_or(Smart::Auto))
}

View File

@ -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[])

View File

@ -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]),

View File

@ -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],

View File

@ -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 ---

View File

@ -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 ---