From 09d67de989920d7afb3e2532513c2ecc8ef3cf38 Mon Sep 17 00:00:00 2001 From: Rob Knight Date: Wed, 11 Feb 2026 11:14:23 +0100 Subject: [PATCH] Add annotate_snippets for better parsing errors (#477) Adds nicer errors for Podlang code, using the `annotate_snippets` crate, the same crate used by the Rust compiler to generate contextual errors. This prints a short snippet of the code containing the error within the error message, highlighting the part that needs to be fixed. It also includes a change to the `load_module` function, changing a `Vec` function argument to a slice. --- Cargo.toml | 1 + examples/main_pod_points.rs | 2 +- src/backends/plonky2/mainpod/mod.rs | 2 +- src/examples/custom.rs | 2 +- src/frontend/mod.rs | 8 +- src/frontend/multi_pod/mod.rs | 6 +- src/lang/diagnostics.rs | 486 ++++++++++++++++++++++++++++ src/lang/error.rs | 76 ++++- src/lang/mod.rs | 47 ++- src/lang/module.rs | 6 +- src/lang/pretty_print.rs | 16 +- 11 files changed, 612 insertions(+), 40 deletions(-) create mode 100644 src/lang/diagnostics.rs diff --git a/Cargo.toml b/Cargo.toml index 810b08c..ac0295d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ serde_arrays = "0.2.0" sha2 = { version = "0.10.9" } rand_chacha = "0.3.1" good_lp = { version = "1.8", default-features = false, features = ["microlp"] } +annotate-snippets = "0.11" # Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory. # [patch."https://github.com/0xPARC/plonky2"] diff --git a/examples/main_pod_points.rs b/examples/main_pod_points.rs index 08c238f..d9d7cd8 100644 --- a/examples/main_pod_points.rs +++ b/examples/main_pod_points.rs @@ -88,7 +88,7 @@ fn main() -> Result<(), Box> { game_pk = game_pk, ); println!("# custom predicate batch:{}", input); - let module = load_module(&input, "points_module", ¶ms, vec![])?; + let module = load_module(&input, "points_module", ¶ms, &[])?; let batch = module.batch.clone(); let points_pred = batch.predicate_ref_by_name("points").unwrap(); let over_9000_pred = batch.predicate_ref_by_name("over_9000").unwrap(); diff --git a/src/backends/plonky2/mainpod/mod.rs b/src/backends/plonky2/mainpod/mod.rs index 2f25d38..341e295 100644 --- a/src/backends/plonky2/mainpod/mod.rs +++ b/src/backends/plonky2/mainpod/mod.rs @@ -1175,7 +1175,7 @@ pub mod tests { "#, "test", ¶ms, - vec![], + &[], ) .unwrap(); let batch = module.batch.clone(); diff --git a/src/examples/custom.rs b/src/examples/custom.rs index 9a68b78..48d69e7 100644 --- a/src/examples/custom.rs +++ b/src/examples/custom.rs @@ -30,7 +30,7 @@ pub fn eth_dos_batch(params: &Params) -> Result> { eth_dos_ind(src, dst, distance) ) "#; - let module = load_module(input, "eth_dos", params, vec![]).expect("lang parse"); + let module = load_module(input, "eth_dos", params, &[]).expect("lang parse"); let batch = module.batch.clone(); println!("a.0. {}", batch.predicates()[0]); println!("a.1. {}", batch.predicates()[1]); diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 46e6c80..04fe1ed 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -1381,7 +1381,7 @@ pub mod tests { Equal(b, 5) ) "#; - let module = load_module(input, "test", ¶ms, vec![]).unwrap(); + let module = load_module(input, "test", ¶ms, &[]).unwrap(); let batch = module.batch.clone(); let pred_test = batch.predicate_ref_by_name("Test").unwrap(); @@ -1430,7 +1430,7 @@ pub mod tests { c(6, 3) ) "#; - let module = load_module(input, "test", ¶ms, vec![]).unwrap(); + let module = load_module(input, "test", ¶ms, &[]).unwrap(); let batch = module.batch.clone(); let pred_test = batch.predicate_ref_by_name("Test").unwrap(); @@ -1452,7 +1452,7 @@ pub mod tests { c(6, 3) ) "#; - let module = load_module(input, "test", ¶ms, vec![]).unwrap(); + let module = load_module(input, "test", ¶ms, &[]).unwrap(); let batch = module.batch.clone(); let pred_test = batch.predicate_ref_by_name("Test").unwrap(); @@ -1491,7 +1491,7 @@ pub mod tests { "#; // Parse and batch the predicate (this handles splitting internally) - let module = load_module(input, "test", ¶ms, vec![])?; + let module = load_module(input, "test", ¶ms, &[])?; // Verify it was split assert!(module.split_chains.contains_key("large_pred")); diff --git a/src/frontend/multi_pod/mod.rs b/src/frontend/multi_pod/mod.rs index bd411ef..70d14c3 100644 --- a/src/frontend/multi_pod/mod.rs +++ b/src/frontend/multi_pod/mod.rs @@ -763,7 +763,7 @@ mod tests { "#, "test", ¶ms, - vec![], + &[], ) .expect("load module"); let batch = &module.batch; @@ -1492,7 +1492,7 @@ mod tests { "#, "test", ¶ms, - vec![], + &[], ) .expect("load module"); let batch = &module.batch; @@ -1621,7 +1621,7 @@ mod tests { "#, "test", ¶ms, - vec![], + &[], ) .expect("load module"); let batch = &module.batch; diff --git a/src/lang/diagnostics.rs b/src/lang/diagnostics.rs new file mode 100644 index 0000000..ea528ef --- /dev/null +++ b/src/lang/diagnostics.rs @@ -0,0 +1,486 @@ +//! Rich error rendering for Podlang diagnostics. +//! +//! Provides [`render_error`], which takes a source string and a [`LangError`] and produces +//! a human-readable, source-annotated error message (similar to `rustc` output). + +use annotate_snippets::{Level, Renderer, Snippet}; + +use crate::lang::{ + error::{LangError, LangErrorKind, ValidationError}, + frontend_ast::Span, + parser::ParseError, +}; + +/// Render a [`LangError`] with source context into a human-readable diagnostic string. +/// +/// Uses `Renderer::plain()` (no ANSI codes) so the output is stable for tests and log-safe. +/// +/// - `source`: the full Podlang source text that was parsed. +/// - `path`: optional file path for the `-->` origin line. +/// - `error`: the error to render. +pub fn render_error(source: &str, path: Option<&str>, error: &LangError) -> String { + let renderer = Renderer::plain(); + match &error.kind { + LangErrorKind::Validation(e) => render_validation_error(&renderer, source, path, e), + LangErrorKind::Parse(e) => render_parse_error(&renderer, source, path, e), + LangErrorKind::Lowering(_) + | LangErrorKind::Batching(_) + | LangErrorKind::Middleware(_) + | LangErrorKind::Frontend(_) => render_title_only(&renderer, &error.kind.to_string()), + } +} + +/// Render an error with only a title line and no source snippet. +fn render_title_only(renderer: &Renderer, message: &str) -> String { + let msg = Level::Error.title(message); + renderer.render(msg).to_string() +} + +/// Render an error with a single annotated span in the source. +fn render_with_span( + renderer: &Renderer, + source: &str, + path: Option<&str>, + title: &str, + span: &Span, + label: &str, +) -> String { + let annotation = Level::Error.span(span.start..span.end).label(label); + let snippet = build_snippet(source, path).annotation(annotation); + let msg = Level::Error.title(title).snippet(snippet); + renderer.render(msg).to_string() +} + +/// Render an error with an optional span — delegates to `render_with_span` or `render_title_only`. +fn render_with_optional_span( + renderer: &Renderer, + source: &str, + path: Option<&str>, + title: &str, + span: Option<&Span>, + label: &str, +) -> String { + match span { + Some(span) => render_with_span(renderer, source, path, title, span, label), + None => render_title_only(renderer, title), + } +} + +/// Render an error with two annotated spans (e.g. first definition + duplicate). +#[allow(clippy::too_many_arguments)] +fn render_dual_span( + renderer: &Renderer, + source: &str, + path: Option<&str>, + title: &str, + first_span: Option<&Span>, + first_label: &str, + second_span: Option<&Span>, + second_label: &str, +) -> String { + let mut snippet = build_snippet(source, path); + if let Some(s) = first_span { + snippet = snippet.annotation(Level::Info.span(s.start..s.end).label(first_label)); + } + if let Some(s) = second_span { + snippet = snippet.annotation(Level::Error.span(s.start..s.end).label(second_label)); + } + let msg = Level::Error.title(title).snippet(snippet); + renderer.render(msg).to_string() +} + +/// Build a `Snippet` with source and optional path, ready for annotations. +fn build_snippet<'a>(source: &'a str, path: Option<&'a str>) -> Snippet<'a> { + let mut snippet = Snippet::source(source).fold(true); + if let Some(p) = path { + snippet = snippet.origin(p); + } + snippet +} + +// --------------------------------------------------------------------------- +// Validation errors +// --------------------------------------------------------------------------- + +fn render_validation_error( + renderer: &Renderer, + source: &str, + path: Option<&str>, + error: &ValidationError, +) -> String { + match error { + ValidationError::UndefinedWildcard { + name, + pred_name, + span, + } => { + let title = format!("undefined wildcard `{}`", name); + let label = format!("not declared in predicate `{}`", pred_name); + render_with_optional_span(renderer, source, path, &title, span.as_ref(), &label) + } + + ValidationError::UndefinedPredicate { name, span } => { + let title = format!("undefined predicate: {}", name); + render_with_optional_span( + renderer, + source, + path, + &title, + span.as_ref(), + "not defined or imported", + ) + } + + ValidationError::DuplicatePredicate { + name, + first_span, + second_span, + } => { + let title = format!("duplicate predicate definition: {}", name); + render_dual_span( + renderer, + source, + path, + &title, + first_span.as_ref(), + "first definition here", + second_span.as_ref(), + "duplicate definition", + ) + } + + ValidationError::ArgumentCountMismatch { + predicate, + expected, + found, + span, + } => { + let title = format!("argument count mismatch for `{}`", predicate); + let label = format!("expected {} arguments, found {}", expected, found); + render_with_optional_span(renderer, source, path, &title, span.as_ref(), &label) + } + + ValidationError::MultipleRequestDefinitions { + first_span, + second_span, + } => render_dual_span( + renderer, + source, + path, + "multiple REQUEST definitions found", + first_span.as_ref(), + "first REQUEST here", + second_span.as_ref(), + "second REQUEST here", + ), + + ValidationError::InvalidArgumentType { predicate, span } => { + let title = format!("invalid argument type for `{}`", predicate); + render_with_optional_span( + renderer, + source, + path, + &title, + span.as_ref(), + "anchored keys not allowed here", + ) + } + + ValidationError::DuplicateWildcard { name, span } => { + let title = format!("duplicate wildcard: {}", name); + render_with_optional_span( + renderer, + source, + path, + &title, + span.as_ref(), + "already declared", + ) + } + + ValidationError::EmptyStatementList { context, span } => { + let title = format!("empty statement list in {}", context); + render_with_optional_span( + renderer, + source, + path, + &title, + span.as_ref(), + "no statements", + ) + } + + ValidationError::InvalidHash { hash, span } => { + let title = format!("invalid hash: {}", hash); + render_with_optional_span( + renderer, + source, + path, + &title, + span.as_ref(), + "invalid hash", + ) + } + + ValidationError::DuplicateImport { name, span } => { + let title = format!("duplicate import name: {}", name); + render_with_optional_span( + renderer, + source, + path, + &title, + span.as_ref(), + "already imported", + ) + } + + ValidationError::ImportArityMismatch { + expected, + found, + span, + } => { + let title = "import arity mismatch".to_string(); + let label = format!("expected {}, found {}", expected, found); + render_with_optional_span(renderer, source, path, &title, span.as_ref(), &label) + } + + ValidationError::ModuleNotFound { name, span } => { + let title = format!("module not found: {}", name); + render_with_optional_span( + renderer, + source, + path, + &title, + span.as_ref(), + "no module with this hash was provided", + ) + } + + ValidationError::WildcardPredicateNameCollision { name } => { + let title = format!("wildcard '{}' collides with a predicate name", name); + render_title_only(renderer, &title) + } + + ValidationError::PredicatesNotAllowedInRequest { span } => render_with_optional_span( + renderer, + source, + path, + "predicate definitions are not allowed in requests", + span.as_ref(), + "not allowed here", + ), + + ValidationError::RequestNotAllowedInModule { span } => render_with_optional_span( + renderer, + source, + path, + "REQUEST block is not allowed in modules", + span.as_ref(), + "not allowed here", + ), + + ValidationError::NoPredicatesInModule => render_title_only( + renderer, + "modules must contain at least one predicate definition", + ), + + ValidationError::NoRequestBlock => { + render_title_only(renderer, "requests must contain a REQUEST block") + } + } +} + +// --------------------------------------------------------------------------- +// Parse errors +// --------------------------------------------------------------------------- + +fn render_parse_error( + renderer: &Renderer, + source: &str, + path: Option<&str>, + error: &ParseError, +) -> String { + match error { + ParseError::Pest(pest_err) => { + let span = span_from_pest_location(&pest_err.location); + let label = format_pest_label(pest_err); + render_with_span(renderer, source, path, "syntax error", &span, &label) + } + _ => render_title_only(renderer, &error.to_string()), + } +} + +/// Extract a byte-offset [`Span`] from a pest `InputLocation`. +fn span_from_pest_location(location: &pest::error::InputLocation) -> Span { + match *location { + pest::error::InputLocation::Pos(pos) => Span { + start: pos, + end: pos + 1, + }, + pest::error::InputLocation::Span((start, end)) => Span { start, end }, + } +} + +/// Format the expectations from a pest error into a readable label. +fn format_pest_label(error: &pest::error::Error) -> String { + match &error.variant { + pest::error::ErrorVariant::ParsingError { + positives, + negatives, + } => { + let mut parts = Vec::new(); + if !positives.is_empty() { + let names: Vec = positives.iter().map(|r| format!("{:?}", r)).collect(); + parts.push(format!("expected {}", names.join(", "))); + } + if !negatives.is_empty() { + let names: Vec = negatives.iter().map(|r| format!("{:?}", r)).collect(); + parts.push(format!("unexpected {}", names.join(", "))); + } + if parts.is_empty() { + "unexpected input".to_string() + } else { + parts.join("; ") + } + } + pest::error::ErrorVariant::CustomError { message } => message.clone(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lang::error::BatchingError; + + /// Load a module from source and extract the error, or panic. + fn module_err(source: &str) -> LangError { + crate::lang::load_module(source, "test", &crate::middleware::Params::default(), &[]) + .unwrap_err() + } + + /// Parse a request from source and extract the error, or panic. + fn request_err(source: &str) -> LangError { + crate::lang::parse_request(source, &crate::middleware::Params::default(), &[]).unwrap_err() + } + + #[test] + fn test_undefined_wildcard() { + let source = r#" +my_pred(A, private: B) = AND( + Equal(A["key"], C["other"]) +) +"#; + let err = module_err(source); + let rendered = render_error(source, Some("test.podlang"), &err); + + assert!( + rendered.contains("undefined wildcard"), + "rendered: {rendered}" + ); + assert!(rendered.contains("C"), "rendered: {rendered}"); + assert!(rendered.contains("my_pred"), "rendered: {rendered}"); + assert!(rendered.contains("test.podlang"), "rendered: {rendered}"); + } + + #[test] + fn test_undefined_predicate() { + let source = r#" +REQUEST( + NoSuchPred(A, B) +) +"#; + let err = request_err(source); + let rendered = render_error(source, Some("test.podlang"), &err); + + assert!( + rendered.contains("undefined predicate"), + "rendered: {rendered}" + ); + assert!(rendered.contains("NoSuchPred"), "rendered: {rendered}"); + } + + #[test] + fn test_duplicate_predicate() { + let source = r#" +my_pred(A) = AND(Equal(A["x"], 1)) +my_pred(B) = AND(Equal(B["y"], 2)) +"#; + let err = module_err(source); + let rendered = render_error(source, Some("test.podlang"), &err); + + assert!( + rendered.contains("duplicate predicate"), + "rendered: {rendered}" + ); + assert!( + rendered.contains("first definition"), + "rendered: {rendered}" + ); + assert!( + rendered.contains("duplicate definition"), + "rendered: {rendered}" + ); + } + + #[test] + fn test_argument_count_mismatch() { + let source = r#" +REQUEST( + Equal(A["x"], B["y"], C["z"]) +) +"#; + let err = request_err(source); + let rendered = render_error(source, Some("test.podlang"), &err); + + assert!( + rendered.contains("argument count mismatch"), + "rendered: {rendered}" + ); + assert!(rendered.contains("Equal"), "rendered: {rendered}"); + } + + #[test] + fn test_pest_syntax_error() { + let source = "REQUEST(!!!invalid!!!"; + let err = request_err(source); + let rendered = render_error(source, Some("test.podlang"), &err); + + assert!( + rendered.contains("syntax error") || rendered.contains("error"), + "rendered: {rendered}" + ); + } + + #[test] + fn test_no_path() { + let source = r#" +REQUEST( + NoSuchPred(A, B) +) +"#; + let err = request_err(source); + let rendered = render_error(source, None, &err); + + assert!( + rendered.contains("undefined predicate"), + "rendered: {rendered}" + ); + assert!( + !rendered.contains("-->"), + "should not have path line, rendered: {rendered}" + ); + } + + #[test] + fn test_error_without_span() { + let error = LangError::from(BatchingError::Internal { + message: "something went wrong".to_string(), + }); + let rendered = render_error("", None, &error); + + assert!( + rendered.contains("something went wrong"), + "rendered: {rendered}" + ); + } +} diff --git a/src/lang/error.rs b/src/lang/error.rs index 3cf1eb6..bdd1c45 100644 --- a/src/lang/error.rs +++ b/src/lang/error.rs @@ -1,3 +1,5 @@ +use std::{fmt, ops::Deref}; + use thiserror::Error; use crate::{ @@ -7,7 +9,7 @@ use crate::{ }; #[derive(Error, Debug)] -pub enum LangError { +pub enum LangErrorKind { #[error("Parsing failed: {0}")] Parse(Box), @@ -27,6 +29,68 @@ pub enum LangError { Batching(Box), } +/// Top-level error type for the Podlang pipeline. +/// +/// When source text is attached (e.g. from [`crate::lang::parse`]), `Display` produces a rich, +/// source-annotated diagnostic. Otherwise it falls back to a plain message. +/// +/// The inner [`LangErrorKind`] variants are accessible via `Deref`, so existing match patterns +/// like `LangError::Validation(e)` continue to work. +pub struct LangError { + pub kind: LangErrorKind, + source_text: Option, + path: Option, +} + +impl LangError { + pub fn new(kind: LangErrorKind) -> Self { + Self { + kind, + source_text: None, + path: None, + } + } + + /// Attach source text (and optional path) so that `Display` produces a rich diagnostic. + pub fn with_source(mut self, source: impl Into, path: Option) -> Self { + self.source_text = Some(source.into()); + self.path = path; + self + } +} + +impl fmt::Debug for LangError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl Deref for LangError { + type Target = LangErrorKind; + fn deref(&self) -> &LangErrorKind { + &self.kind + } +} + +impl fmt::Display for LangError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.source_text { + Some(source) => { + let rendered = + crate::lang::diagnostics::render_error(source, self.path.as_deref(), self); + write!(f, "{}", rendered) + } + None => write!(f, "{}", self.kind), + } + } +} + +impl std::error::Error for LangError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.kind.source() + } +} + /// Validation errors from frontend AST validation #[derive(Debug, thiserror::Error)] pub enum ValidationError { @@ -295,30 +359,30 @@ pub enum SplittingError { impl From for LangError { fn from(err: ParseError) -> Self { - LangError::Parse(Box::new(err)) + LangError::new(LangErrorKind::Parse(Box::new(err))) } } impl From for LangError { fn from(err: middleware::Error) -> Self { - LangError::Middleware(Box::new(err)) + LangError::new(LangErrorKind::Middleware(Box::new(err))) } } impl From for LangError { fn from(err: ValidationError) -> Self { - LangError::Validation(Box::new(err)) + LangError::new(LangErrorKind::Validation(Box::new(err))) } } impl From for LangError { fn from(err: LoweringError) -> Self { - LangError::Lowering(Box::new(err)) + LangError::new(LangErrorKind::Lowering(Box::new(err))) } } impl From for LangError { fn from(err: BatchingError) -> Self { - LangError::Batching(Box::new(err)) + LangError::new(LangErrorKind::Batching(Box::new(err))) } } diff --git a/src/lang/mod.rs b/src/lang/mod.rs index d01377a..5674f53 100644 --- a/src/lang/mod.rs +++ b/src/lang/mod.rs @@ -26,6 +26,7 @@ //! Large predicates are automatically split into chains of smaller predicates; //! `apply_predicate` handles this transparently. //! +pub mod diagnostics; pub mod error; pub mod frontend_ast; pub mod frontend_ast_lower; @@ -37,7 +38,8 @@ pub mod pretty_print; use std::sync::Arc; -pub use error::LangError; +pub use diagnostics::render_error; +pub use error::{LangError, LangErrorKind}; pub use frontend_ast_split::{SplitChainInfo, SplitChainPiece, SplitResult}; pub use module::{Module, MultiOperationError}; pub use parser::{parse_podlang, Pairs, ParseError, Rule}; @@ -57,7 +59,17 @@ pub fn load_module( source: &str, name: &str, params: &Params, - available_modules: Vec>, + available_modules: &[Arc], +) -> Result { + load_module_inner(source, name, params, available_modules) + .map_err(|e| e.with_source(source.to_string(), None)) +} + +fn load_module_inner( + source: &str, + name: &str, + params: &Params, + available_modules: &[Arc], ) -> Result { let pairs = parse_podlang(source)?; let document_pair = pairs @@ -89,6 +101,15 @@ pub fn parse_request( source: &str, params: &Params, available_modules: &[Arc], +) -> Result { + parse_request_inner(source, params, available_modules) + .map_err(|e| e.with_source(source.to_string(), None)) +} + +fn parse_request_inner( + source: &str, + params: &Params, + available_modules: &[Arc], ) -> Result { let pairs = parse_podlang(source)?; let document_pair = pairs @@ -160,7 +181,7 @@ mod tests { "#; let params = Params::default(); - let module = load_module(input, "test_module", ¶ms, vec![])?; + let module = load_module(input, "test_module", ¶ms, &[])?; assert_eq!(module.batch.predicates().len(), 1); @@ -235,7 +256,7 @@ mod tests { "#; let params = Params::default(); - let module = load_module(input, "test_module", ¶ms, vec![])?; + let module = load_module(input, "test_module", ¶ms, &[])?; assert_eq!(module.batch.predicates().len(), 1); @@ -281,7 +302,7 @@ mod tests { "#; let params = Params::default(); - let module = Arc::new(load_module(module_input, "my_module", ¶ms, vec![])?); + let module = Arc::new(load_module(module_input, "my_module", ¶ms, &[])?); assert_eq!(module.batch.predicates().len(), 1); @@ -330,7 +351,7 @@ mod tests { "#; let params = Params::default(); - let module = Arc::new(load_module(module_input, "some_module", ¶ms, vec![])?); + let module = Arc::new(load_module(module_input, "some_module", ¶ms, &[])?); let module_hash = module.id().encode_hex::(); @@ -585,7 +606,7 @@ mod tests { ) "#; - let module = load_module(input, "ethdos", ¶ms, vec![])?; + let module = load_module(input, "ethdos", ¶ms, &[])?; assert_eq!( module.batch.predicates().len(), @@ -855,7 +876,7 @@ mod tests { ); // 3. Load as module - let module = load_module(&input, "test", ¶ms, vec![extmod])?; + let module = load_module(&input, "test", ¶ms, &[extmod])?; assert_eq!( module.batch.predicates().len(), @@ -1011,8 +1032,8 @@ mod tests { assert!(result.is_err()); - match result.err().unwrap() { - LangError::Validation(e) => match *e { + match result.err().unwrap().kind { + LangErrorKind::Validation(e) => match *e { frontend_ast_validate::ValidationError::ModuleNotFound { name, .. } => { // The error now carries the hex-formatted hash assert_eq!(name, fake_hash); @@ -1034,12 +1055,12 @@ mod tests { ) "#; - let result = load_module(input, "test", ¶ms, vec![]); + let result = load_module(input, "test", ¶ms, &[]); assert!(result.is_err()); - match result.err().unwrap() { - LangError::Validation(e) => match *e { + match result.err().unwrap().kind { + LangErrorKind::Validation(e) => match *e { frontend_ast_validate::ValidationError::UndefinedWildcard { name, pred_name, diff --git a/src/lang/module.rs b/src/lang/module.rs index aa4547a..3ff3d6b 100644 --- a/src/lang/module.rs +++ b/src/lang/module.rs @@ -610,7 +610,7 @@ mod tests { r#"is_equal(X, Y) = AND(Equal(X["val"], Y["val"]))"#, "checks", ¶ms, - vec![], + &[], ) .unwrap(), ); @@ -621,7 +621,7 @@ mod tests { r#"is_less(X, Y) = AND(Lt(X["val"], Y["val"]))"#, "ordering", ¶ms, - vec![], + &[], ) .unwrap(), ); @@ -645,7 +645,7 @@ mod tests { ), "combined", ¶ms, - vec![checks.clone(), ordering.clone()], + &[checks.clone(), ordering.clone()], ) .unwrap(); diff --git a/src/lang/pretty_print.rs b/src/lang/pretty_print.rs index 7d6c90f..efca5c9 100644 --- a/src/lang/pretty_print.rs +++ b/src/lang/pretty_print.rs @@ -391,15 +391,15 @@ mod tests { // Step 1: Parse the input let module = - load_module(input, "test", ¶ms, vec![]).expect("Initial parsing should succeed"); + load_module(input, "test", ¶ms, &[]).expect("Initial parsing should succeed"); // Step 2: Pretty-print the parsed batch let batch = &module.batch; let pretty_printed = batch.to_podlang_string(); // Step 3: Parse the pretty-printed result - let reparsed_module = load_module(&pretty_printed, "test", ¶ms, vec![]) - .expect("Reparsing should succeed"); + let reparsed_module = + load_module(&pretty_printed, "test", ¶ms, &[]).expect("Reparsing should succeed"); let reparsed_batch = &reparsed_module.batch; // Step 4: Verify the ASTs are equivalent @@ -555,7 +555,7 @@ mod tests { "#; let params = Params::default(); - let module = load_module(input, "test", ¶ms, vec![]).expect("Parsing should succeed"); + let module = load_module(input, "test", ¶ms, &[]).expect("Parsing should succeed"); let batch = &module.batch; let pretty_printed = batch.to_podlang_string(); @@ -563,8 +563,8 @@ mod tests { println!("Original input:\n{}", input); println!("\nPretty-printed output:\n{}", pretty_printed); - let reparsed = load_module(&pretty_printed, "test", ¶ms, vec![]) - .expect("Reparsing should succeed"); + let reparsed = + load_module(&pretty_printed, "test", ¶ms, &[]).expect("Reparsing should succeed"); let reparsed_batch = &reparsed.batch; assert_eq!(batch.predicates(), reparsed_batch.predicates()); @@ -630,12 +630,12 @@ mod tests { let params = Params::default(); let module = - load_module(&input, "test", ¶ms, vec![]).expect("Should parse successfully"); + load_module(&input, "test", ¶ms, &[]).expect("Should parse successfully"); let batch = &module.batch; let pretty_printed = batch.to_podlang_string(); - let reparsed_module = load_module(&pretty_printed, "test", ¶ms, vec![]) + let reparsed_module = load_module(&pretty_printed, "test", ¶ms, &[]) .expect("Should reparse successfully"); let reparsed_batch = &reparsed_module.batch;