use std::{fmt, ops::Deref}; use thiserror::Error; use crate::{ frontend, lang::{frontend_ast::Span, parser::ParseError}, middleware, }; #[derive(Error, Debug)] pub enum LangErrorKind { #[error("Parsing failed: {0}")] Parse(Box), #[error("Middleware error during processing: {0}")] Middleware(Box), #[error("Frontend error: {0}")] Frontend(Box), #[error("Validation error: {0}")] Validation(Box), #[error("Lowering error: {0}")] Lowering(Box), #[error("Batching error: {0}")] 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 { #[error("Invalid hash: {hash}")] InvalidHash { hash: String, span: Option }, #[error("Duplicate predicate definition: {name}")] DuplicatePredicate { name: String, first_span: Option, second_span: Option, }, #[error("Duplicate import name: {name}")] DuplicateImport { name: String, span: Option }, #[error("Import arity mismatch: expected {expected} predicates, found {found}")] ImportArityMismatch { expected: usize, found: usize, span: Option, }, #[error("Module not found: {name}")] ModuleNotFound { name: String, span: Option }, #[error("Undefined predicate: {name}")] UndefinedPredicate { name: String, span: Option }, #[error("Undefined wildcard: {name} in predicate {pred_name}")] UndefinedWildcard { name: String, pred_name: String, span: Option, }, #[error("Argument count mismatch for {predicate}: expected {expected}, found {found}")] ArgumentCountMismatch { predicate: String, expected: usize, found: usize, span: Option, }, #[error("Duplicate wildcard in predicate arguments: {name}")] DuplicateWildcard { name: String, span: Option }, #[error("Empty statement list in {context}")] EmptyStatementList { context: String, span: Option }, #[error("Multiple REQUEST definitions found. Only one is allowed.")] MultipleRequestDefinitions { first_span: Option, second_span: Option, }, #[error("Wildcard '{name}' collides with a predicate name")] WildcardPredicateNameCollision { name: String }, #[error("Predicate definitions are not allowed in requests")] PredicatesNotAllowedInRequest { span: Option }, #[error("REQUEST block is not allowed in modules")] RequestNotAllowedInModule { span: Option }, #[error("Modules must contain at least one predicate definition")] NoPredicatesInModule, #[error("Self-referential predicate literal not allowed in requests")] SelfReferentialPredicateLiteralNotAllowedInRequests { span: Option }, #[error("Requests must contain a REQUEST block")] NoRequestBlock, #[error("Duplicate record definition: {name}")] DuplicateRecord { name: String, first_span: Option, second_span: Option, }, #[error("Record '{name}' has {count} entries, exceeding the limit of {max}")] RecordTooManyEntries { name: String, count: usize, max: usize, span: Option, }, #[error("Duplicate entry name '{entry}' in record '{record}'")] DuplicateRecordEntry { record: String, entry: String, span: Option, }, #[error("Unknown record type: {name}")] UnknownRecord { name: String, span: Option }, #[error("Record '{record}' has no entry '{entry}'")] UnknownRecordEntry { record: String, entry: String, span: Option, }, #[error("Duplicate entry '{entry}' in record literal '{record}'")] DuplicateLiteralRecordEntry { record: String, entry: String, span: Option, }, #[error("Bracket access '{wildcard}[...]' is not allowed on a wildcard typed as record '{record}'; use `{wildcard}.entry` instead")] BracketAccessOnTypedWildcard { wildcard: String, record: String, span: Option, }, } /// Lowering errors from frontend AST lowering to middleware #[derive(Debug, thiserror::Error)] pub enum LoweringError { #[error("Too many statements in predicate '{predicate}': {count} exceeds limit of {max}")] TooManyStatements { predicate: String, count: usize, max: usize, }, #[error("Too many wildcards in predicate '{predicate}': {count} exceeds limit of {max}")] TooManyWildcards { predicate: String, count: usize, max: usize, }, #[error("Too many arguments in statement template: {count} exceeds limit of {max}")] TooManyStatementArgs { count: usize, max: usize }, #[error("Predicate '{name}' not found in symbol table")] PredicateNotFound { name: String }, #[error("Invalid argument type in statement template")] InvalidArgumentType, #[error("Middleware error: {0}")] Middleware(#[from] middleware::Error), #[error("Splitting error: {0}")] Splitting(#[from] SplittingError), #[error("Batching error: {0}")] Batching(#[from] BatchingError), #[error("Cannot lower document with validation errors")] ValidationErrors, } /// Batching errors from multi-batch packing #[derive(Debug, thiserror::Error)] pub enum BatchingError { #[error("Internal batching error: {message}")] Internal { message: String }, } /// Splitting errors from predicate splitting #[derive(Debug, thiserror::Error)] pub enum SplittingError { #[error("Too many public arguments in predicate '{predicate}': {count} exceeds max of {max_allowed}. {message}")] TooManyPublicArgs { predicate: String, count: usize, max_allowed: usize, message: String, }, #[error("Could not split predicate '{predicate}' into a chain: no feasible partition exists with up to {max_links} links. \ The predicate's wildcard structure may be too dense for any chain to fit within max_statement_args ({max_statement_args}) \ and max_custom_predicate_wildcards ({max_wildcards}) per link.")] Infeasible { predicate: String, max_links: usize, max_statement_args: usize, max_wildcards: usize, }, } impl From for LangError { fn from(err: ParseError) -> Self { LangError::new(LangErrorKind::Parse(Box::new(err))) } } impl From for LangError { fn from(err: middleware::Error) -> Self { LangError::new(LangErrorKind::Middleware(Box::new(err))) } } impl From for LangError { fn from(err: ValidationError) -> Self { LangError::new(LangErrorKind::Validation(Box::new(err))) } } impl From for LangError { fn from(err: LoweringError) -> Self { LangError::new(LangErrorKind::Lowering(Box::new(err))) } } impl From for LangError { fn from(err: BatchingError) -> Self { LangError::new(LangErrorKind::Batching(Box::new(err))) } }