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), ParseString(#[from] ParseStringError),
#[error(transparent)] #[error(transparent)]
#[diagnostic(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 /// Failed to parse serde string
SerdeParse(#[from] SerdeParseError), SerdeParse(#[from] SerdeParseError),
#[error("{0}")] #[error("{0}")]
@ -73,14 +77,20 @@ impl VdfError {
..e ..e
} }
.into(), .into(),
VdfError::UnknownVariant(e) => UnknownVariantError {
src: source.into(),
err_span: span.into(),
..e
}
.into(),
_ => self, _ => 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 { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut tokens = self.0.iter(); let mut tokens = self.0.iter();
if let Some(token) = tokens.next() { if let Some(token) = tokens.next() {
@ -101,7 +111,7 @@ impl Display for ExpectedTokens<'_> {
#[derive(Debug, Clone, Diagnostic)] #[derive(Debug, Clone, Diagnostic)]
#[diagnostic(code(vmt_reader::unexpected_token))] #[diagnostic(code(vmt_reader::unexpected_token))]
pub struct UnexpectedTokenError { pub struct UnexpectedTokenError {
#[label("Expected {}", ExpectedTokens(self.expected))] #[label("Expected {}", CommaSeperated(self.expected))]
err_span: SourceSpan, err_span: SourceSpan,
pub expected: &'static [Token], pub expected: &'static [Token],
pub found: Option<Token>, pub found: Option<Token>,
@ -132,12 +142,12 @@ impl Display for UnexpectedTokenError {
f, f,
"Unexpected token, found {} expected one of {}", "Unexpected token, found {} expected one of {}",
token, token,
ExpectedTokens(self.expected) CommaSeperated(self.expected)
), ),
None => write!( None => write!(
f, f,
"Unexpected end of input expected one of {}", "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 /// A token that wasn't expected was found while parsing
#[derive(Debug, Clone, Diagnostic, Error)] #[derive(Debug, Clone, Diagnostic, Error)]
#[diagnostic(code(vmt_reader::no_valid_token))] #[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 { pub struct NoValidTokenError {
#[label("Expected {}", ExpectedTokens(self.expected))] #[label("Expected {}", CommaSeperated(self.expected))]
err_span: SourceSpan, err_span: SourceSpan,
pub expected: &'static [Token], pub expected: &'static [Token],
#[source_code] #[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> { pub trait ExpectToken<'source> {
fn expect_token( fn expect_token(
self, self,
@ -342,4 +387,8 @@ impl serde::de::Error for VdfError {
{ {
VdfError::Other(msg.to_string()) 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 where
V: Visitor<'de>, 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> 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/messy.vdf")]
#[test_case("tests/data/DialogConfigOverlay_1280x720.vdf")] #[test_case("tests/data/DialogConfigOverlay_1280x720.vdf")]
#[test_case("tests/data/serde_array_type.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/concrete.vmt")]
#[test_case("tests/errors/novalue.vdf")] #[test_case("tests/errors/novalue.vdf")]
#[test_case("tests/errors/serde_array_type.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 │ {
╰────