mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Fix an error of uncastable dict input (#3247)
This commit is contained in:
parent
a603729336
commit
a3e1c70e9e
@ -43,11 +43,11 @@ pub trait Reflect {
|
|||||||
/// dynamic checks instead of optimized machine code for each type).
|
/// dynamic checks instead of optimized machine code for each type).
|
||||||
fn castable(value: &Value) -> bool;
|
fn castable(value: &Value) -> bool;
|
||||||
|
|
||||||
/// Produce an error message for an inacceptable value.
|
/// Produce an error message for an inacceptable value type.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// <Int as Reflect>::error(Value::None),
|
/// <Int as Reflect>::error(&Value::None),
|
||||||
/// "expected integer, found none",
|
/// "expected integer, found none",
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -113,15 +113,44 @@ impl Dict {
|
|||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an "unexpected key" error if there is any remaining pair.
|
/// Check if there is any remaining pair, and if so return an
|
||||||
|
/// "unexpected key" error.
|
||||||
pub fn finish(&self, expected: &[&str]) -> StrResult<()> {
|
pub fn finish(&self, expected: &[&str]) -> StrResult<()> {
|
||||||
if let Some((key, _)) = self.iter().next() {
|
let mut iter = self.iter().peekable();
|
||||||
let parts: Vec<_> = expected.iter().map(|s| eco_format!("\"{s}\"")).collect();
|
if iter.peek().is_none() {
|
||||||
let mut msg = format!("unexpected key {}, valid keys are ", key.repr());
|
return Ok(());
|
||||||
msg.push_str(&repr::separated_list(&parts, "and"));
|
|
||||||
return Err(msg.into());
|
|
||||||
}
|
}
|
||||||
Ok(())
|
let unexpected: Vec<&str> = iter.map(|kv| kv.0.as_str()).collect();
|
||||||
|
|
||||||
|
Err(Self::unexpected_keys(unexpected, Some(expected)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an "unexpected key" error string.
|
||||||
|
pub fn unexpected_keys(
|
||||||
|
unexpected: Vec<&str>,
|
||||||
|
hint_expected: Option<&[&str]>,
|
||||||
|
) -> EcoString {
|
||||||
|
let format_as_list = |arr: &[&str]| {
|
||||||
|
repr::separated_list(
|
||||||
|
&arr.iter().map(|s| eco_format!("\"{s}\"")).collect::<Vec<_>>(),
|
||||||
|
"and",
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut msg = String::from(match unexpected.len() {
|
||||||
|
0 => unreachable!(),
|
||||||
|
1 => "unexpected key ",
|
||||||
|
_ => "unexpected keys ",
|
||||||
|
});
|
||||||
|
|
||||||
|
msg.push_str(&format_as_list(&unexpected[..]));
|
||||||
|
|
||||||
|
if let Some(expected) = hint_expected {
|
||||||
|
msg.push_str(", valid keys are ");
|
||||||
|
msg.push_str(&format_as_list(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ where
|
|||||||
T: FromValue + Clone,
|
T: FromValue + Clone,
|
||||||
{
|
{
|
||||||
fn from_value(mut value: Value) -> StrResult<Self> {
|
fn from_value(mut value: Value) -> StrResult<Self> {
|
||||||
let keys = [
|
let expected_keys = [
|
||||||
"top-left",
|
"top-left",
|
||||||
"top-right",
|
"top-right",
|
||||||
"bottom-right",
|
"bottom-right",
|
||||||
@ -177,7 +177,9 @@ where
|
|||||||
];
|
];
|
||||||
|
|
||||||
if let Value::Dict(dict) = &mut value {
|
if let Value::Dict(dict) = &mut value {
|
||||||
if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) {
|
if dict.is_empty() {
|
||||||
|
return Ok(Self::splat(None));
|
||||||
|
} else if dict.iter().any(|(key, _)| expected_keys.contains(&key.as_str())) {
|
||||||
let mut take = |key| dict.take(key).ok().map(T::from_value).transpose();
|
let mut take = |key| dict.take(key).ok().map(T::from_value).transpose();
|
||||||
let rest = take("rest")?;
|
let rest = take("rest")?;
|
||||||
let left = take("left")?.or_else(|| rest.clone());
|
let left = take("left")?.or_else(|| rest.clone());
|
||||||
@ -199,13 +201,18 @@ where
|
|||||||
.or_else(|| left.clone()),
|
.or_else(|| left.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
dict.finish(&keys)?;
|
dict.finish(&expected_keys)?;
|
||||||
return Ok(corners);
|
return Ok(corners);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if T::castable(&value) {
|
if T::castable(&value) {
|
||||||
Ok(Self::splat(Some(T::from_value(value)?)))
|
Ok(Self::splat(Some(T::from_value(value)?)))
|
||||||
|
} else if let Value::Dict(dict) = &value {
|
||||||
|
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))
|
||||||
} else {
|
} else {
|
||||||
Err(Self::error(&value))
|
Err(Self::error(&value))
|
||||||
}
|
}
|
||||||
|
@ -190,9 +190,11 @@ where
|
|||||||
T: Default + FromValue + Clone,
|
T: Default + FromValue + Clone,
|
||||||
{
|
{
|
||||||
fn from_value(mut value: Value) -> StrResult<Self> {
|
fn from_value(mut value: Value) -> StrResult<Self> {
|
||||||
let 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.iter().any(|(key, _)| keys.contains(&key.as_str())) {
|
if dict.is_empty() {
|
||||||
|
return Ok(Self::splat(None));
|
||||||
|
} else if dict.iter().any(|(key, _)| expected_keys.contains(&key.as_str())) {
|
||||||
let mut take = |key| dict.take(key).ok().map(T::from_value).transpose();
|
let mut take = |key| dict.take(key).ok().map(T::from_value).transpose();
|
||||||
let rest = take("rest")?;
|
let rest = take("rest")?;
|
||||||
let x = take("x")?.or_else(|| rest.clone());
|
let x = take("x")?.or_else(|| rest.clone());
|
||||||
@ -204,13 +206,18 @@ where
|
|||||||
bottom: take("bottom")?.or_else(|| y.clone()),
|
bottom: take("bottom")?.or_else(|| y.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
dict.finish(&keys)?;
|
dict.finish(&expected_keys)?;
|
||||||
return Ok(sides);
|
return Ok(sides);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if T::castable(&value) {
|
if T::castable(&value) {
|
||||||
Ok(Self::splat(Some(T::from_value(value)?)))
|
Ok(Self::splat(Some(T::from_value(value)?)))
|
||||||
|
} else if let Value::Dict(dict) = &value {
|
||||||
|
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))
|
||||||
} else {
|
} else {
|
||||||
Err(Self::error(&value))
|
Err(Self::error(&value))
|
||||||
}
|
}
|
||||||
|
23
tests/typ/bugs/3232-dict-wrong-keys.typ
Normal file
23
tests/typ/bugs/3232-dict-wrong-keys.typ
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Issue #3232: Confusing "expected relative length or dictionary, found dictionary"
|
||||||
|
// https://github.com/typst/typst/issues/3232
|
||||||
|
// Ref: false
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 16-58 unexpected keys "unexpected" and "unexpected-too"
|
||||||
|
#block(outset: (unexpected: 0.5em, unexpected-too: 0.2em), [Hi])
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 14-56 unexpected keys "unexpected" and "unexpected-too"
|
||||||
|
#box(radius: (unexpected: 0.5em, unexpected-too: 0.5em), [Hi])
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 16-49 unexpected key "unexpected", valid keys are "left", "top", "right", "bottom", "x", "y", and "rest"
|
||||||
|
#block(outset: (unexpected: 0.2em, right: 0.5em), [Hi]) // The 1st key is unexpected
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 14-50 unexpected key "unexpected", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"
|
||||||
|
#box(radius: (top-left: 0.5em, unexpected: 0.5em), [Hi]) // The 2nd key is unexpected
|
||||||
|
|
||||||
|
---
|
||||||
|
#block(outset: (:), [Hi]) // Ok
|
||||||
|
#box(radius: (:), [Hi]) // Ok
|
Loading…
x
Reference in New Issue
Block a user