From fe9f020ff59046a898dd2f9602d6bd95b641b0e9 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Fri, 12 Dec 2025 15:32:54 +0000 Subject: [PATCH] Use an Unrepresentable type to store parse-time errors for later display --- core/src/error.rs | 22 +++++++++++++++++++++- core/src/language/mod.rs | 9 +++++++++ core/src/language/python.rs | 1 + core/src/language/scala.rs | 1 + core/src/reconcile.rs | 1 + core/src/rust_types.rs | 33 ++++++++++++++++++++++++++++++--- core/src/topsort.rs | 1 + 7 files changed, 64 insertions(+), 4 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index fc203661..fcd423a9 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -87,7 +87,7 @@ pub enum GenerationError { PostGeneration(String), } -#[derive(Debug, Error)] +#[derive(Debug, Error, Clone)] #[allow(missing_docs)] pub enum RustTypeParseError { #[error("Unsupported type: \"{}\"", .0.iter().join(","))] @@ -99,3 +99,23 @@ pub enum RustTypeParseError { #[error("Could not parse numeric literal")] NumericLiteral(syn::parse::Error), } + +impl PartialEq for RustTypeParseError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (RustTypeParseError::UnsupportedType(a), RustTypeParseError::UnsupportedType(b)) => { + a == b + } + (RustTypeParseError::UnexpectedToken(a), RustTypeParseError::UnexpectedToken(b)) => { + a == b + } + ( + RustTypeParseError::UnexpectedParameterizedTuple, + RustTypeParseError::UnexpectedParameterizedTuple, + ) => true, + (RustTypeParseError::NumericLiteral(_), RustTypeParseError::NumericLiteral(_)) => true, + _ => false, + } + } +} +impl Eq for RustTypeParseError {} diff --git a/core/src/language/mod.rs b/core/src/language/mod.rs index e92dfc05..1708c43b 100644 --- a/core/src/language/mod.rs +++ b/core/src/language/mod.rs @@ -215,6 +215,15 @@ pub trait Language { self.format_generic_type(id, parameters.as_slice(), generic_types) } RustType::Special(special) => self.format_special_type(special, generic_types), + RustType::Unrepresentable { + error, + line, + column, + } => Err(RustTypeFormatError::UnrepresentableType { + error: error.clone(), + line: *line, + column: *column, + }), } } diff --git a/core/src/language/python.rs b/core/src/language/python.rs index c03bb302..2cec2871 100644 --- a/core/src/language/python.rs +++ b/core/src/language/python.rs @@ -56,6 +56,7 @@ fn collect_generics_for_variant(variant_type: &RustType, generics: &[String]) -> } _ => {} }, + RustType::Unrepresentable { .. } => {} } // Remove any duplicates // E.g. Foo(HashMap) should only produce a single type var diff --git a/core/src/language/scala.rs b/core/src/language/scala.rs index 6a1b1bd6..fdd4a304 100644 --- a/core/src/language/scala.rs +++ b/core/src/language/scala.rs @@ -493,6 +493,7 @@ impl Scala { } RustType::Special(_) => vec![ty.clone()], RustType::Simple { .. } => vec![], + RustType::Unrepresentable { .. } => vec![], }) .any(|ty| { matches!( diff --git a/core/src/reconcile.rs b/core/src/reconcile.rs index a3ccc4cc..9b5dbf57 100644 --- a/core/src/reconcile.rs +++ b/core/src/reconcile.rs @@ -163,6 +163,7 @@ fn check_type( *id = renamed.to_owned(); } } + RustType::Unrepresentable { .. } => {} } } diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index 801f5b88..1edfc77e 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -212,6 +212,14 @@ pub enum RustType { #[allow(missing_docs)] id: String, }, + /// Unrepresentable type due to parsing error. Types may be overridden when emitted, so + /// we don't fail immediately but store the error here. We have to separate the span into + /// line and column because spans aren't Send and so can't be converted into io::Error. + Unrepresentable { + error: RustTypeParseError, + line: usize, + column: usize, + }, } impl Display for RustType { @@ -233,6 +241,13 @@ impl Display for RustType { } } RustType::Special(ty) => &ty.to_string(), + RustType::Unrepresentable { + error, + line, + column, + } => { + return write!(f, "{error:?} at line {line}, column {column}"); + } }; write!(f, "{rust_type}") } @@ -331,9 +346,11 @@ impl TryFrom<&syn::Type> for RustType { syn::Type::Tuple(tuple) if tuple.elems.iter().count() == 0 => { Self::Special(SpecialRustType::Unit) } - syn::Type::Tuple(tt) => { - return Err(RustTypeParseError::UnexpectedParameterizedTuple.with_span(tt.span())) - } + syn::Type::Tuple(tt) => Self::Unrepresentable { + error: RustTypeParseError::UnexpectedParameterizedTuple, + line: tt.span().start().line, + column: tt.span().start().column, + }, syn::Type::Reference(reference) => Self::try_from(reference.elem.as_ref())?, syn::Type::Path(path) => { let segment = path.path.segments.iter().next_back().unwrap(); @@ -441,6 +458,7 @@ impl RustType { id == ty || parameters.iter().any(|p| p.contains_type(ty)) } Self::Special(special) => special.contains_type(ty), + Self::Unrepresentable { .. } => false, } } @@ -449,6 +467,7 @@ impl RustType { match &self { Self::Simple { id } | Self::Generic { id, .. } => id.as_str(), Self::Special(special) => special.id(), + Self::Unrepresentable { .. } => "Unrepresentable", } } /// Check if the type is `Option` @@ -482,6 +501,7 @@ impl RustType { Self::Simple { .. } => Box::new(std::iter::empty()), Self::Generic { parameters, .. } => Box::new(parameters.iter()), Self::Special(special) => special.parameters(), + Self::Unrepresentable { .. } => Box::new(std::iter::empty()), } } @@ -540,6 +560,13 @@ pub enum RustTypeFormatError { GenericKeyForbiddenInTS(String), #[error("The special type `{0}` is not supported in this language")] UnsupportedSpecialType(String), + // An error which occurred at parsing time which makes this type unrepresentable. + #[error("{error} at line {line}, column {column}")] + UnrepresentableType { + error: RustTypeParseError, + line: usize, + column: usize, + }, } impl SpecialRustType { diff --git a/core/src/topsort.rs b/core/src/topsort.rs index fe99b72f..0b85fe24 100644 --- a/core/src/topsort.rs +++ b/core/src/topsort.rs @@ -53,6 +53,7 @@ fn get_dependencies_from_type( } _ => {} }, + RustType::Unrepresentable { .. } => {} }; seen.remove(&tp.id().to_string()); }