better errors for unmatched variants

This commit is contained in:
Robin Appelman 2023-12-18 17:08:00 +01:00
commit 23bc240d4f
5 changed files with 84 additions and 8 deletions

View file

@ -36,6 +36,10 @@ pub enum VdfError {
ParseString(#[from] ParseStringError),
#[error(transparent)]
#[diagnostic(transparent)]
/// Failed to find an enum variant that matches the found tag
UnknownVariant(#[from] UnknownVariantError),
#[error(transparent)]
#[diagnostic(transparent)]
/// Failed to parse serde string
SerdeParse(#[from] SerdeParseError),
#[error("{0}")]
@ -73,14 +77,20 @@ impl VdfError {
..e
}
.into(),
VdfError::UnknownVariant(e) => UnknownVariantError {
src: source.into(),
err_span: span.into(),
..e
}
.into(),
_ => self,
}
}
}
struct ExpectedTokens<'a>(&'a [Token]);
struct CommaSeperated<'a, T>(&'a [T]);
impl Display for ExpectedTokens<'_> {
impl<T: Display> Display for CommaSeperated<'_, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut tokens = self.0.iter();
if let Some(token) = tokens.next() {
@ -101,7 +111,7 @@ impl Display for ExpectedTokens<'_> {
#[derive(Debug, Clone, Diagnostic)]
#[diagnostic(code(vmt_reader::unexpected_token))]
pub struct UnexpectedTokenError {
#[label("Expected {}", ExpectedTokens(self.expected))]
#[label("Expected {}", CommaSeperated(self.expected))]
err_span: SourceSpan,
pub expected: &'static [Token],
pub found: Option<Token>,
@ -132,12 +142,12 @@ impl Display for UnexpectedTokenError {
f,
"Unexpected token, found {} expected one of {}",
token,
ExpectedTokens(self.expected)
CommaSeperated(self.expected)
),
None => write!(
f,
"Unexpected end of input expected one of {}",
ExpectedTokens(self.expected)
CommaSeperated(self.expected)
),
}
}
@ -148,9 +158,9 @@ impl Error for UnexpectedTokenError {}
/// A token that wasn't expected was found while parsing
#[derive(Debug, Clone, Diagnostic, Error)]
#[diagnostic(code(vmt_reader::no_valid_token))]
#[error("No valid token found, expected one of {}", ExpectedTokens(self.expected))]
#[error("No valid token found, expected one of {}", CommaSeperated(self.expected))]
pub struct NoValidTokenError {
#[label("Expected {}", ExpectedTokens(self.expected))]
#[label("Expected {}", CommaSeperated(self.expected))]
err_span: SourceSpan,
pub expected: &'static [Token],
#[source_code]
@ -282,6 +292,41 @@ impl SerdeParseError {
}
}
#[derive(Debug, Clone, Error, Diagnostic)]
#[error("Unknown variant {variant:?} expected on of {}", ExpectedVariants(self.expected))]
#[diagnostic(code(vmt_parser::unknown_variant))]
pub struct UnknownVariantError {
variant: String,
expected: &'static [&'static str],
#[label("{}", ExpectedVariants(self.expected))]
err_span: SourceSpan,
#[source_code]
src: String,
}
struct ExpectedVariants(&'static [&'static str]);
impl Display for ExpectedVariants {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.0.is_empty() {
write!(f, "there are no variants")
} else {
write!(f, "expected on of {}", CommaSeperated(self.0))
}
}
}
impl UnknownVariantError {
pub fn new(variant: &str, expected: &'static [&'static str], span: Span, src: &str) -> Self {
UnknownVariantError {
variant: variant.into(),
expected,
err_span: span.into(),
src: src.into(),
}
}
}
pub trait ExpectToken<'source> {
fn expect_token(
self,
@ -342,4 +387,8 @@ impl serde::de::Error for VdfError {
{
VdfError::Other(msg.to_string())
}
fn unknown_variant(variant: &str, expected: &'static [&'static str]) -> Self {
UnknownVariantError::new(variant, expected, 0..0, "").into()
}
}

View file

@ -374,7 +374,15 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
where
V: Visitor<'de>,
{
visitor.visit_enum(Enum::new(self))
let variant_token = self.peek().map(|r| r.ok()).flatten();
visitor
.visit_enum(Enum::new(self))
.map_err(|e| match (variant_token, &e) {
(Some(variant_token), VdfError::UnknownVariant(_)) => {
e.with_source_span(variant_token.span, self.source())
}
_ => e,
})
}
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>

View file

@ -0,0 +1,4 @@
"Resource"
{
foo
}

View file

@ -158,6 +158,7 @@ struct GameList {
#[test_case("tests/data/messy.vdf")]
#[test_case("tests/data/DialogConfigOverlay_1280x720.vdf")]
#[test_case("tests/data/serde_array_type.vdf")]
#[test_case("tests/errors/unmatched.vdf")]
#[test_case("tests/errors/concrete.vmt")]
#[test_case("tests/errors/novalue.vdf")]
#[test_case("tests/errors/serde_array_type.vdf")]

View file

@ -0,0 +1,14 @@
---
source: tests/serde.rs
expression: out
---
vmt_parser::unknown_variant
× Unknown variant "Resource" expected on of expected on of Types, LightmappedGeneric, Resource/specificPanel.res, UserConfigData
╭─[1:1]
1 │ "Resource"
· ─────┬────
· ╰── expected on of Types, LightmappedGeneric, Resource/specificPanel.res, UserConfigData
2 │ {
╰────