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.
This commit is contained in:
Rob Knight 2026-02-11 11:14:23 +01:00 committed by GitHub
parent acab26e5c1
commit 09d67de989
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 612 additions and 40 deletions

View file

@ -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"]

View file

@ -88,7 +88,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
game_pk = game_pk,
);
println!("# custom predicate batch:{}", input);
let module = load_module(&input, "points_module", &params, vec![])?;
let module = load_module(&input, "points_module", &params, &[])?;
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();

View file

@ -1175,7 +1175,7 @@ pub mod tests {
"#,
"test",
&params,
vec![],
&[],
)
.unwrap();
let batch = module.batch.clone();

View file

@ -30,7 +30,7 @@ pub fn eth_dos_batch(params: &Params) -> Result<Arc<CustomPredicateBatch>> {
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]);

View file

@ -1381,7 +1381,7 @@ pub mod tests {
Equal(b, 5)
)
"#;
let module = load_module(input, "test", &params, vec![]).unwrap();
let module = load_module(input, "test", &params, &[]).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", &params, vec![]).unwrap();
let module = load_module(input, "test", &params, &[]).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", &params, vec![]).unwrap();
let module = load_module(input, "test", &params, &[]).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", &params, vec![])?;
let module = load_module(input, "test", &params, &[])?;
// Verify it was split
assert!(module.split_chains.contains_key("large_pred"));

View file

@ -763,7 +763,7 @@ mod tests {
"#,
"test",
&params,
vec![],
&[],
)
.expect("load module");
let batch = &module.batch;
@ -1492,7 +1492,7 @@ mod tests {
"#,
"test",
&params,
vec![],
&[],
)
.expect("load module");
let batch = &module.batch;
@ -1621,7 +1621,7 @@ mod tests {
"#,
"test",
&params,
vec![],
&[],
)
.expect("load module");
let batch = &module.batch;

486
src/lang/diagnostics.rs Normal file
View file

@ -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<R: std::fmt::Debug>(error: &pest::error::Error<R>) -> String {
match &error.variant {
pest::error::ErrorVariant::ParsingError {
positives,
negatives,
} => {
let mut parts = Vec::new();
if !positives.is_empty() {
let names: Vec<String> = positives.iter().map(|r| format!("{:?}", r)).collect();
parts.push(format!("expected {}", names.join(", ")));
}
if !negatives.is_empty() {
let names: Vec<String> = 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}"
);
}
}

View file

@ -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<ParseError>),
@ -27,6 +29,68 @@ pub enum LangError {
Batching(Box<BatchingError>),
}
/// 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<String>,
path: Option<String>,
}
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<String>, path: Option<String>) -> 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<ParseError> for LangError {
fn from(err: ParseError) -> Self {
LangError::Parse(Box::new(err))
LangError::new(LangErrorKind::Parse(Box::new(err)))
}
}
impl From<middleware::Error> for LangError {
fn from(err: middleware::Error) -> Self {
LangError::Middleware(Box::new(err))
LangError::new(LangErrorKind::Middleware(Box::new(err)))
}
}
impl From<ValidationError> for LangError {
fn from(err: ValidationError) -> Self {
LangError::Validation(Box::new(err))
LangError::new(LangErrorKind::Validation(Box::new(err)))
}
}
impl From<LoweringError> for LangError {
fn from(err: LoweringError) -> Self {
LangError::Lowering(Box::new(err))
LangError::new(LangErrorKind::Lowering(Box::new(err)))
}
}
impl From<BatchingError> for LangError {
fn from(err: BatchingError) -> Self {
LangError::Batching(Box::new(err))
LangError::new(LangErrorKind::Batching(Box::new(err)))
}
}

View file

@ -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<Arc<Module>>,
available_modules: &[Arc<Module>],
) -> Result<Module, LangError> {
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<Module>],
) -> Result<Module, LangError> {
let pairs = parse_podlang(source)?;
let document_pair = pairs
@ -89,6 +101,15 @@ pub fn parse_request(
source: &str,
params: &Params,
available_modules: &[Arc<Module>],
) -> Result<PodRequest, LangError> {
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<Module>],
) -> Result<PodRequest, LangError> {
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", &params, vec![])?;
let module = load_module(input, "test_module", &params, &[])?;
assert_eq!(module.batch.predicates().len(), 1);
@ -235,7 +256,7 @@ mod tests {
"#;
let params = Params::default();
let module = load_module(input, "test_module", &params, vec![])?;
let module = load_module(input, "test_module", &params, &[])?;
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", &params, vec![])?);
let module = Arc::new(load_module(module_input, "my_module", &params, &[])?);
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", &params, vec![])?);
let module = Arc::new(load_module(module_input, "some_module", &params, &[])?);
let module_hash = module.id().encode_hex::<String>();
@ -585,7 +606,7 @@ mod tests {
)
"#;
let module = load_module(input, "ethdos", &params, vec![])?;
let module = load_module(input, "ethdos", &params, &[])?;
assert_eq!(
module.batch.predicates().len(),
@ -855,7 +876,7 @@ mod tests {
);
// 3. Load as module
let module = load_module(&input, "test", &params, vec![extmod])?;
let module = load_module(&input, "test", &params, &[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", &params, vec![]);
let result = load_module(input, "test", &params, &[]);
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,

View file

@ -610,7 +610,7 @@ mod tests {
r#"is_equal(X, Y) = AND(Equal(X["val"], Y["val"]))"#,
"checks",
&params,
vec![],
&[],
)
.unwrap(),
);
@ -621,7 +621,7 @@ mod tests {
r#"is_less(X, Y) = AND(Lt(X["val"], Y["val"]))"#,
"ordering",
&params,
vec![],
&[],
)
.unwrap(),
);
@ -645,7 +645,7 @@ mod tests {
),
"combined",
&params,
vec![checks.clone(), ordering.clone()],
&[checks.clone(), ordering.clone()],
)
.unwrap();

View file

@ -391,15 +391,15 @@ mod tests {
// Step 1: Parse the input
let module =
load_module(input, "test", &params, vec![]).expect("Initial parsing should succeed");
load_module(input, "test", &params, &[]).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", &params, vec![])
.expect("Reparsing should succeed");
let reparsed_module =
load_module(&pretty_printed, "test", &params, &[]).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", &params, vec![]).expect("Parsing should succeed");
let module = load_module(input, "test", &params, &[]).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", &params, vec![])
.expect("Reparsing should succeed");
let reparsed =
load_module(&pretty_printed, "test", &params, &[]).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", &params, vec![]).expect("Should parse successfully");
load_module(&input, "test", &params, &[]).expect("Should parse successfully");
let batch = &module.batch;
let pretty_printed = batch.to_podlang_string();
let reparsed_module = load_module(&pretty_printed, "test", &params, vec![])
let reparsed_module = load_module(&pretty_printed, "test", &params, &[])
.expect("Should reparse successfully");
let reparsed_batch = &reparsed_module.batch;