From 072f68c829696687fb01cbc8764e655b3ee942ba Mon Sep 17 00:00:00 2001 From: Will Lillis Date: Mon, 7 Sep 2026 03:46:22 -0400 Subject: [PATCH] feat(generate)!: derive `PartialEq + Eq` on `GenerateError` Also use `IoError` more consistently throughout the rest of the project where its straightforward to do so. BREAKING CHANGE: Changes public error types for config, generate, and loader crates. --- crates/config/src/tree_sitter_config.rs | 10 +++++ .../src/build_tables/build_parse_table.rs | 10 ++--- crates/generate/src/generate.rs | 18 +++++++-- crates/generate/src/node_types.rs | 2 +- crates/generate/src/parse_grammar.rs | 2 +- crates/generate/src/prepare_grammar.rs | 2 +- crates/generate/src/render.rs | 2 +- crates/loader/src/loader.rs | 39 +++++++++++++------ crates/xtask/src/build_wasm.rs | 19 +++++++-- 9 files changed, 77 insertions(+), 27 deletions(-) diff --git a/crates/config/src/tree_sitter_config.rs b/crates/config/src/tree_sitter_config.rs index c6b1dc2b1..9a3fd478c 100644 --- a/crates/config/src/tree_sitter_config.rs +++ b/crates/config/src/tree_sitter_config.rs @@ -31,6 +31,16 @@ pub struct IoError { pub path: Option, } +impl PartialEq for IoError { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + && self.error.kind() == other.error.kind() + && self.error.raw_os_error() == other.error.raw_os_error() + } +} + +impl Eq for IoError {} + impl IoError { fn new(error: std::io::Error, path: Option<&Path>) -> Self { Self { diff --git a/crates/generate/src/build_tables/build_parse_table.rs b/crates/generate/src/build_tables/build_parse_table.rs index 20617627f..de2c414ee 100644 --- a/crates/generate/src/build_tables/build_parse_table.rs +++ b/crates/generate/src/build_tables/build_parse_table.rs @@ -83,7 +83,7 @@ struct ParseTableBuilder<'a> { pub type BuildTableResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum ParseTableBuilderError { #[error("Unresolved conflict for symbol sequence:\n\n{0}")] Conflict(#[from] ConflictError), @@ -97,7 +97,7 @@ pub enum ParseTableBuilderError { StateCount(usize), } -#[derive(Default, Debug, Serialize, Error, Deserialize)] +#[derive(Default, Debug, Serialize, Error, Deserialize, PartialEq, Eq)] pub struct ConflictError { pub symbol_sequence: Vec, pub conflicting_lookahead: String, @@ -105,7 +105,7 @@ pub struct ConflictError { pub possible_resolutions: Vec, } -#[derive(Default, Debug, Serialize, Error, Deserialize)] +#[derive(Default, Debug, Serialize, Error, Deserialize, PartialEq, Eq)] pub struct Interpretation { pub preceding_symbols: Vec, pub variable_name: String, @@ -118,14 +118,14 @@ pub struct Interpretation { pub requires_eof_lookahead: bool, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Resolution { Precedence { symbols: Vec }, Associativity { symbols: Vec }, AddConflict { symbols: Vec }, } -#[derive(Debug, Serialize, Deserialize, Error)] +#[derive(Debug, Serialize, Deserialize, Error, PartialEq, Eq)] pub struct AmbiguousExtraError { pub parent_symbols: Vec, } diff --git a/crates/generate/src/generate.rs b/crates/generate/src/generate.rs index c90bde449..528a23eb1 100644 --- a/crates/generate/src/generate.rs +++ b/crates/generate/src/generate.rs @@ -73,7 +73,7 @@ pub const PARSER_HEADER: &str = include_str!("parser.h.inc"); pub type GenerateResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum GenerateError { #[error("Error with specified path -- {0}")] GrammarPath(IoError), @@ -107,6 +107,16 @@ pub struct IoError { pub path: Option, } +impl PartialEq for IoError { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + && self.error.kind() == other.error.kind() + && self.error.raw_os_error() == other.error.raw_os_error() + } +} + +impl Eq for IoError {} + #[cfg(feature = "load")] impl IoError { fn new(error: std::io::Error, path: Option<&Path>) -> Self { @@ -163,7 +173,7 @@ impl<'de> Deserialize<'de> for IoError { pub type LoadGrammarFileResult = Result; #[cfg(feature = "load")] -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum LoadGrammarError { #[error("Path to a grammar file with `.js` or `.json` extension is required")] InvalidPath, @@ -176,7 +186,7 @@ pub enum LoadGrammarError { } #[cfg(feature = "load")] -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum ParseVersionError { #[error("{0}")] Version(String), @@ -190,7 +200,7 @@ pub enum ParseVersionError { pub type JSResult = Result; #[cfg(feature = "load")] -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum JSError { #[error("Failed to run `{runtime}` -- {error}")] JSRuntimeSpawn { runtime: String, error: String }, diff --git a/crates/generate/src/node_types.rs b/crates/generate/src/node_types.rs index f19c03cc7..0479880d3 100644 --- a/crates/generate/src/node_types.rs +++ b/crates/generate/src/node_types.rs @@ -590,7 +590,7 @@ pub fn get_supertype_symbol_map( #[cfg(feature = "load")] pub type SuperTypeCycleResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub struct SuperTypeCycleError { items: Vec, } diff --git a/crates/generate/src/parse_grammar.rs b/crates/generate/src/parse_grammar.rs index c89657d70..d2be5538f 100644 --- a/crates/generate/src/parse_grammar.rs +++ b/crates/generate/src/parse_grammar.rs @@ -115,7 +115,7 @@ pub struct GrammarJSON { pub type ParseGrammarResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum ParseGrammarError { #[error("{0}")] Serialization(String), diff --git a/crates/generate/src/prepare_grammar.rs b/crates/generate/src/prepare_grammar.rs index 304f8edc6..314efb5d8 100644 --- a/crates/generate/src/prepare_grammar.rs +++ b/crates/generate/src/prepare_grammar.rs @@ -48,7 +48,7 @@ use super::{ pub type PrepareGrammarResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] #[error(transparent)] pub enum PrepareGrammarError { ValidatePrecedences(#[from] ValidatePrecedenceError), diff --git a/crates/generate/src/render.rs b/crates/generate/src/render.rs index 1d4e5b099..53a8b91d6 100644 --- a/crates/generate/src/render.rs +++ b/crates/generate/src/render.rs @@ -33,7 +33,7 @@ const ABI_VERSION_WITH_RESERVED_WORDS: usize = 15; pub type RenderResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum RenderError { #[error("Parse table action count {0} exceeds maximum value of {max}", max=u16::MAX)] ParseTable(usize), diff --git a/crates/loader/src/loader.rs b/crates/loader/src/loader.rs index d29723c60..a4b0af5d2 100755 --- a/crates/loader/src/loader.rs +++ b/crates/loader/src/loader.rs @@ -89,7 +89,7 @@ pub enum LoaderError { )] LockFileTimeout(PathBuf), #[error("Failed to execute curl for {0} -- {1}")] - Curl(String, std::io::Error), + Curl(String, IoError), #[error("Failed to load language in current directory:\n{0}")] CurrentDirectoryLoad(Box), #[error("External file path {0} is outside of parser directory {1}")] @@ -120,8 +120,12 @@ pub enum LoaderError { Symbol(SymbolError), #[error(transparent)] Tags(#[from] TagsError), - #[error("Failed to execute tar for {0} -- {1}")] - Tar(String, std::io::Error), + #[error( + "Failed to execute tar for {path} -- {error}", + path = .0.path.as_deref().unwrap_or_else(|| Path::new("")).display(), + error = .0.error, + )] + Tar(IoError), #[error("Unknown scope '{0}'")] UnknownScope(String), #[error("Failed to download {tool} from {url}")] @@ -134,9 +138,9 @@ pub enum LoaderError { #[error(transparent)] Wasm(#[from] WasmError), #[error("Failed to run wasi-sdk clang -- {0}")] - WasmCompiler(std::io::Error), + WasmCompiler(IoError), #[error("Failed to run wasm-opt -- {0}")] - WasmOptimizer(std::io::Error), + WasmOptimizer(IoError), #[error("wasi-sdk clang command failed: {0}")] WasmCompilation(String), #[error("wasm-opt command failed: {0}")] @@ -145,7 +149,7 @@ pub enum LoaderError { #[derive(Debug, Error)] pub struct CompilerError { - pub error: std::io::Error, + pub error: IoError, pub command: Box, } @@ -166,6 +170,16 @@ pub struct IoError { pub path: Option, } +impl PartialEq for IoError { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + && self.error.kind() == other.error.kind() + && self.error.raw_os_error() == other.error.raw_os_error() + } +} + +impl Eq for IoError {} + impl IoError { fn new(error: std::io::Error, path: Option<&Path>) -> Self { Self { @@ -1326,9 +1340,10 @@ impl Loader { display_build_cmd(&command); } + let compiler_path = PathBuf::from(command.get_program()); let output = command.output().map_err(|e| { LoaderError::Compiler(CompilerError { - error: e, + error: IoError::new(e, Some(&compiler_path)), command: Box::new(command), }) })?; @@ -1457,7 +1472,7 @@ impl Loader { let compile_output = compile_command .output() - .map_err(LoaderError::WasmCompiler)?; + .map_err(|e| LoaderError::WasmCompiler(IoError::new(e, Some(&clang_exe))))?; if self.verbose { if !compile_output.stdout.is_empty() { info!("stdout:{}", String::from_utf8_lossy(&compile_output.stdout)); @@ -1483,7 +1498,9 @@ impl Loader { display_build_cmd(&opt_command); } - let opt_output = opt_command.output().map_err(LoaderError::WasmOptimizer)?; + let opt_output = opt_command + .output() + .map_err(|e| LoaderError::WasmOptimizer(IoError::new(e, Some(&wasm_opt_exe))))?; if self.verbose { if !opt_output.stdout.is_empty() { info!("stdout:{}", String::from_utf8_lossy(&opt_output.stdout)); @@ -1517,7 +1534,7 @@ impl Loader { .arg("-C") .arg(destination) .status() - .map_err(|e| LoaderError::Tar(archive_path.to_string_lossy().to_string(), e))?; + .map_err(|e| LoaderError::Tar(IoError::new(e, Some(archive_path))))?; if !status.success() { return Err(LoaderError::Extraction( @@ -1712,7 +1729,7 @@ impl Loader { .arg(&temp_tar_path) .arg(url) .status() - .map_err(|e| LoaderError::Curl(url.to_string(), e))?; + .map_err(|e| LoaderError::Curl(url.to_string(), IoError::new(e, None)))?; if !status.success() { Err(LoaderError::WasmToolDownload { diff --git a/crates/xtask/src/build_wasm.rs b/crates/xtask/src/build_wasm.rs index 5ebfaf5ff..a387ab8d6 100644 --- a/crates/xtask/src/build_wasm.rs +++ b/crates/xtask/src/build_wasm.rs @@ -580,7 +580,15 @@ fn download_tool( .arg(&temp_tar_path) .arg(url) .status() - .map_err(|e| LoaderError::Curl(url.to_string(), e))?; + .map_err(|e| { + LoaderError::Curl( + url.to_string(), + IoError { + error: e, + path: None, + }, + ) + })?; if !status.success() { Err(LoaderError::WasmToolDownload { @@ -620,7 +628,12 @@ fn extract_tar_gz_with_strip(archive_path: &Path, destination: &Path) -> Result< .arg("-C") .arg(destination) .status() - .map_err(|e| LoaderError::Tar(archive_path.to_string_lossy().to_string(), e))?; + .map_err(|e| { + LoaderError::Tar(IoError { + error: e, + path: Some(archive_path.to_path_buf()), + }) + })?; if !status.success() { Err(LoaderError::Extraction( @@ -684,7 +697,7 @@ fn ensure_wasi_libc_source_exists() -> Result { .arg(&archive_path) .arg(&url) .status() - .map_err(|error| LoaderError::Curl(url.clone(), error))?; + .map_err(|error| LoaderError::Curl(url.clone(), IoError { error, path: None }))?; if !status.success() { return Err(LoaderError::WasmToolDownload { tool: "wasi-libc",