diff --git a/Cargo.lock b/Cargo.lock index 4bf29fdf..bf6a0fcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,9 +451,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -470,9 +470,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -589,7 +589,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.117", ] [[package]] @@ -651,6 +651,7 @@ dependencies = [ "serde", "serde_json", "simplicity-lang", + "thiserror", ] [[package]] @@ -697,15 +698,35 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "unicode-ident" version = "1.0.11" @@ -751,7 +772,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -773,7 +794,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.117", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 938f6088..0bdc0e1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ itertools = "0.13.0" arbitrary = { version = "1", optional = true, features = ["derive"] } clap = "4.5.37" chumsky = "0.11.2" +thiserror = "2.0.18" [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/src/ast.rs b/src/ast.rs index fca0947f..4947a45a 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -9,12 +9,12 @@ use simplicity::jet::{Elements, Jet}; use crate::debug::{CallTracker, DebugSymbols, TrackedCallName}; use crate::driver::{FileScoped, SymbolTable, MAIN_MODULE, MAIN_STR}; -use crate::error::{Error, RichError, Span, WithSpan}; +use crate::error::{RichError, Span, WithSpan}; use crate::jet::JetHL; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::parse::MatchPattern; use crate::pattern::Pattern; -use crate::str::{AliasName, FunctionName, Identifier, ModuleName, WitnessName}; +use crate::str::{AliasName, FunctionName, Identifier, JetName, ModuleName, WitnessName}; use crate::types::{ AliasedType, ResolvedType, StructuralType, TypeConstructible, TypeDeconstructible, UIntType, }; @@ -22,6 +22,121 @@ use crate::value::{UIntValue, Value}; use crate::witness::{Parameters, WitnessTypes}; use crate::{driver, impl_eq_hash, parse}; +#[derive(Debug, thiserror::Error, Clone)] +pub enum Error { + #[error("Main function is required")] + MainRequired, + + #[error("Function `{name}` was defined multiple times")] + FunctionRedefined { name: FunctionName }, + + #[error("Type alias `{name}` is not defined")] + UndefinedAlias { name: AliasName }, + + #[error("INTERNAL ERROR: {msg}")] + Internal { msg: String }, + + #[error("Item `{name}` is private")] + PrivateItem { name: String }, + + #[error("Type alias `{name}` was defined multiple times")] + RedefinedAlias { name: AliasName }, + + #[error("Expected expression of type `{expected}`, found type `{found}`")] + ExpressionTypeMismatch { + expected: ResolvedType, + found: ResolvedType, + }, + + #[error("Witness expressions are not allowed outside the `main` function")] + WitnessOutsideMain, + + #[error("Witness `{name}` has been used before somewhere in the program")] + WitnessReused { name: WitnessName }, + + #[error("Function `{name}` was called but not defined")] + FunctionUndefined { name: FunctionName }, + + #[error("Failed to compile to Simplicity: {msg}")] + CannotCompile { msg: String }, + + #[error("Main function takes no input parameters")] + MainNoInputs, + + #[error("Main function produces no output")] + MainNoOutput, + + #[error("The 'main' function must be defined in the entry point file")] + MainOutOfEntryFile, + + #[error("Expected expression of type `{ty}`; found something else")] + ExpressionUnexpectedType { ty: ResolvedType }, + + #[error("Variable `{identifier}` is used twice in the pattern")] + VariableReuseInPattern { identifier: Identifier }, + + #[error("Variable `{identifier}` is not defined")] + UndefinedVariable { identifier: Identifier }, + + #[error("Value is out of bounds for type `{ty}`")] + IntegerOutOfBounds { ty: UIntType }, + + #[error("Expected {expected} arguments, found {found} arguments")] + InvalidNumberOfArguments { expected: usize, found: usize }, + + #[error("Cannot cast values of type `{source_type}` as values of type `{target_type}`")] + InvalidCast { + source_type: ResolvedType, + target_type: ResolvedType, + }, + + #[error("Jet `{name}` does not exist")] + JetDoesNotExist { name: JetName }, + + #[error("Expected a signature like `fn {name}(element: E, accumulator: A) -> A` for a fold")] + FunctionNotFoldable { name: FunctionName }, + + #[error("Module `{0}` is defined twice")] + ModuleRedefined(ModuleName), + + #[error("Witness `{0}` has already been assigned a value")] + WitnessReassigned(WitnessName), + + #[error("Expected a signature like `fn {name}(accumulator: A, context: C, counter u{{1,2,4,8,16}}) -> Either` for a for-while loop")] + FunctionNotLoopable { name: FunctionName }, + + #[error("Witness `{name}` was declared with type `{declared}` but its assigned value is of type `{assigned}`")] + WitnessTypeMismatch { + name: WitnessName, + declared: ResolvedType, + assigned: ResolvedType, + }, + + #[error("Parameter `{name}` is missing an argument")] + ArgumentMissing { name: WitnessName }, + + #[error( + "Parameter `{name}` was declared with type `{declared}` but its assigned argument is of type `{assigned}`" + )] + ArgumentTypeMismatch { + name: WitnessName, + declared: ResolvedType, + assigned: ResolvedType, + }, + + #[error("The `use` keyword is not supported yet")] + UseKeywordIsNotSupported, + + #[error("Integer parsing error")] + ParseIntCrate(#[from] crate::num::ParseIntError), + + #[error("Integer parsing error")] + ParseInt(#[from] std::num::ParseIntError), + + #[error("Expected a valid bit string length (1, 2, 4, 8, 16, 32, 64, 128, 256), found {len}")] + BitStringPow2 { len: usize }, +} + /// A program consists of the main function. /// /// Other items such as custom functions or type aliases @@ -981,7 +1096,7 @@ impl AbstractSyntaxTree for Item { Function::analyze(function, ty, scope).map(Self::Function) } parse::Item::Use(use_decl) => Err(RichError::new( - Error::UseKeywordIsNotSupported, + crate::error::Error::AnalyzingError(Error::UseKeywordIsNotSupported), *use_decl.span(), )), parse::Item::Module => Ok(Self::Module), @@ -1428,8 +1543,8 @@ impl AbstractSyntaxTree for Call { CallName::TypeCast(source) => { if StructuralType::from(&source) != StructuralType::from(ty) { return Err(Error::InvalidCast { - source, - target: ty.clone(), + source_type: source, + target_type: ty.clone(), }) .with_span(from); } diff --git a/src/compile/error.rs b/src/compile/error.rs new file mode 100644 index 00000000..b89703f1 --- /dev/null +++ b/src/compile/error.rs @@ -0,0 +1,8 @@ +#[derive(Debug, thiserror::Error, Clone)] +pub enum Error { + #[error("Variable `{identifier}` is not defined")] + UndefinedVariable { identifier: crate::str::Identifier }, + + #[error("Simplicity type error")] + TypeError(#[from] simplicity::types::Error), +} diff --git a/src/compile/mod.rs b/src/compile/mod.rs index ad3a44a8..6a987010 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -1,6 +1,9 @@ //! Compile the parsed ast into a simplicity program mod builtins; +mod error; + +pub use error::Error; use std::sync::Arc; @@ -15,7 +18,7 @@ use crate::ast::{ SingleExpressionInner, Statement, }; use crate::debug::CallTracker; -use crate::error::{Error, RichError, Span, WithSpan}; +use crate::error::{RichError, Span, WithSpan}; use crate::named::{self, CoreExt, PairBuilder}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::pattern::{BasePattern, Pattern}; diff --git a/src/driver/error.rs b/src/driver/error.rs new file mode 100644 index 00000000..4a0a35ff --- /dev/null +++ b/src/driver/error.rs @@ -0,0 +1,65 @@ +use std::path::PathBuf; + +use crate::str::{AliasName, FunctionName}; + +#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq, Hash)] +pub enum Error { + #[error("Item `{name}` could not be found")] + UnresolvedItem { name: String }, + + #[error("Item `{name}` is private")] + PrivateItem { name: String }, + + #[error("The alias `{name}` was defined multiple times")] + DuplicateAlias { name: String }, + + #[error("Item `{name}` was defined multiple times")] + RedefinedItem { name: String }, + + #[error("Main function cannot be public")] + MainCannotBePublic, + + #[error("Function `{name}` was defined multiple times")] + FunctionRedefined { name: FunctionName }, + + #[error("Unknown module or library '{name}'")] + UnknownLibrary { name: String }, + + #[error("Main function cannot be alias")] + MainCannotBeAlias, + + #[error("Type alias `{name}` was defined multiple times")] + RedefinedAlias { name: AliasName }, + + #[error("Local file `{}` not found", filename.display())] + FileNotFound { filename: PathBuf }, + + #[error( + "File `{}` is part of the local project and must be imported using the `crate::` prefix", path.to_string_lossy() + + )] + LocalFileImportedAsExternal { path: PathBuf }, + + #[error("File `{}` not found in external library `{}`", lib, filename.display())] + ExternalFileNotFound { lib: String, filename: PathBuf }, + + #[error("INTERNAL ERROR: {msg}")] + Internal { msg: String }, + + #[error("Path not found: {}", path.display())] + DependencyPathNotFound { path: PathBuf }, + + #[error("Path must be a directory: {}", path.display())] + DependencyNotADirectory { path: PathBuf }, + + #[error("The '{keyword}' keyword is reserved and cannot be manually mapped. Use the builder's context definitions instead.")] + ReservedDependencyKeyword { keyword: String }, + + #[error("Duplicate dependency mapping: alias '{alias}' is defined multiple times for context '{context}'")] + DuplicateDependencyAlias { alias: String, context: String }, + + #[error( + "Invalid dependency alias '{alias}': must be a valid identifier and not a reserved keyword" + )] + InvalidDependencyIdentifier { alias: String }, +} diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 0f026f84..e2487942 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -27,6 +27,7 @@ //! resolves and parses these external files relative to the entry point during //! the dependency graph construction. +mod error; mod linearization; pub(crate) mod resolve_order; @@ -36,13 +37,15 @@ use std::sync::Arc; use chumsky::container::Container; -use crate::error::{Error, ErrorCollector, RichError, Span}; +use crate::error::{ErrorCollector, RichError, Span}; use crate::parse::{self, ParseFromStrWithErrors}; use crate::resolution::DependencyMap; use crate::source::{CanonPath, CanonSourceFile}; pub use crate::driver::resolve_order::{FileScoped, Program, SymbolTable}; +pub use crate::driver::error::Error; + /// The reserved identifier for the program's entry point. pub(crate) const MAIN_STR: &str = "main"; @@ -209,7 +212,8 @@ impl DependencyGraph { let err = RichError::new( Error::FileNotFound { filename: PathBuf::from(path.as_path()), - }, + } + .into(), span, ) .with_source(importer_source.clone()); diff --git a/src/driver/resolve_order.rs b/src/driver/resolve_order.rs index ad5463d5..6b1f8ac0 100644 --- a/src/driver/resolve_order.rs +++ b/src/driver/resolve_order.rs @@ -1,8 +1,10 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::sync::Arc; +use crate::driver::error::Error; use crate::driver::{DependencyGraph, MAIN_MODULE, MAIN_STR}; -use crate::error::{Error, ErrorCollector, RichError, Span}; +use crate::error::WithSpan; +use crate::error::{ErrorCollector, RichError, Span}; use crate::impl_eq_hash; use crate::parse::{self, AliasedSymbolName, Function, TypeAlias, Visibility}; use crate::str::{AliasName, FunctionName, SymbolName}; @@ -44,7 +46,8 @@ impl Program { RichError::new( Error::UnknownLibrary { name: use_decl.str_path(), - }, + } + .into(), *use_decl.span(), ) .with_content(content.clone()), @@ -264,8 +267,14 @@ impl DependencyGraph { (Ok(()), _) | (_, Ok(())) => Ok(()), (Err(err_alias), Err(err_func)) => { - let alias_is_missing = matches!(err_alias.error(), Error::UnresolvedItem { .. }); - let func_is_missing = matches!(err_func.error(), Error::UnresolvedItem { .. }); + let alias_is_missing = matches!( + err_alias.error(), + crate::error::Error::DriverError(Error::UnresolvedItem { .. }) + ); + let func_is_missing = matches!( + err_func.error(), + crate::error::Error::DriverError(Error::UnresolvedItem { .. }) + ); if !alias_is_missing || func_is_missing { // If it's missing everywhere, OR if the function is missing @@ -320,26 +329,19 @@ impl DependencyGraph { let orig_id = (target_name.clone(), ind); // 2. Verify Existence using T - let visibility: &Visibility = - namespace.resolutions[ind] - .get(&target_name) - .ok_or_else(|| { - RichError::new( - Error::UnresolvedItem { - name: name.to_string(), - }, - span, - ) - })?; + let visibility: &Visibility = namespace.resolutions[ind] + .get(&target_name) + .ok_or_else(|| Error::UnresolvedItem { + name: name.to_string(), + }) + .with_span(span)?; // 3. Verify Visibility if matches!(visibility, parse::Visibility::Private) { - return Err(RichError::new( - Error::PrivateItem { - name: name.to_string(), - }, - span, - )); + return Err(Error::PrivateItem { + name: name.to_string(), + }) + .with_span(span); } // 4. Determine the local name and ID up front @@ -351,7 +353,7 @@ impl DependencyGraph { let t_alias: T = alias_sym.clone().into(); if t_alias.to_string() == MAIN_STR { - return Err(RichError::new(Error::MainCannotBeAlias, span)); + return Err(Error::MainCannotBeAlias).with_span(span); } t_alias } else { @@ -362,21 +364,17 @@ impl DependencyGraph { // 5. Check for collisions using `namespace` fields if namespace.registry.direct_targets.contains_key(&local_id) { - return Err(RichError::new( - Error::DuplicateAlias { - name: local_symbol.to_string(), - }, - span, - )); + return Err(Error::DuplicateAlias { + name: local_symbol.to_string(), + }) + .with_span(span); } if namespace.memo.contains(&local_id) { - return Err(RichError::new( - Error::RedefinedItem { - name: local_symbol.to_string(), - }, - span, - )); + return Err(Error::RedefinedItem { + name: local_symbol.to_string(), + }) + .with_span(span); } namespace.memo.insert(local_id.clone()); @@ -421,10 +419,7 @@ fn register_type_alias( let local_id = (name.clone(), source_id); if tracker.memo.contains(&local_id) { - return Err(RichError::new( - Error::RedefinedAlias { name: name.clone() }, - *item.span(), - )); + return Err(Error::RedefinedAlias { name: name.clone() }).with_span(*item.span()); } tracker.memo.insert(local_id); @@ -443,14 +438,11 @@ fn register_function( let local_id = (name.clone(), source_id); if name.as_inner() == MAIN_STR && matches!(item.visibility(), Visibility::Public) { - return Err(RichError::new(Error::MainCannotBePublic, *item.span())); + return Err(Error::MainCannotBePublic).with_span(*item.span()); } if tracker.memo.contains(&local_id) { - return Err(RichError::new( - Error::FunctionRedefined { name: name.clone() }, - *item.span(), - )); + return Err(Error::FunctionRedefined { name: name.clone() }).with_span(*item.span()); } tracker.memo.insert(local_id); diff --git a/src/error.rs b/src/error.rs index 83420973..913134d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,5 @@ use std::fmt; use std::ops::Range; -use std::path::PathBuf; use std::sync::Arc; use chumsky::error::Error as ChumskyError; @@ -10,13 +9,9 @@ use chumsky::text::Char; use chumsky::util::MaybeRef; use chumsky::DefaultExpected; -use itertools::Itertools; - use crate::lexer::Token; -use crate::parse::MatchPattern; +use crate::parse::SyntaxErrorInfo; use crate::source::SourceFile; -use crate::str::{AliasName, FunctionName, Identifier, JetName, ModuleName, WitnessName}; -use crate::types::{ResolvedType, UIntType}; /// Area that an object spans inside a file. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -203,9 +198,9 @@ impl RichError { /// a problem on the parsing side. pub fn parsing_error(reason: &str) -> Self { Self { - error: Box::new(Error::CannotParse { + error: Box::new(Error::ParsingError(crate::parse::Error::CannotParse { msg: reason.to_string(), - }), + })), span: Span::new(0, 0), source: None, } @@ -323,9 +318,8 @@ where { fn merge(self, other: Self) -> Self { match (self.error.as_ref(), other.error.as_ref()) { - (Error::Grammar { .. }, Error::Grammar { .. }) => other, - (Error::Grammar { .. }, _) => other, - (_, Error::Grammar { .. }) => self, + (Error::ParsingError(crate::parse::Error::Grammar { .. }), _) => other, + (_, Error::ParsingError(crate::parse::Error::Grammar { .. })) => self, _ => other, } } @@ -358,11 +352,13 @@ where let found_string = found.map(|t| t.to_string()); Self { - error: Box::new(Error::Syntax { - expected: expected_tokens, - label: None, - found: found_string, - }), + error: Box::new(Error::ParsingError(crate::parse::Error::SyntaxError( + SyntaxErrorInfo { + expected: expected_tokens, + label: None, + found: found_string, + }, + ))), span, source: None, } @@ -385,20 +381,23 @@ where let found_string = found.map(|t| t.to_string()); Self { - error: Box::new(Error::Syntax { - expected: expected_strings, - label: None, - found: found_string, - }), + error: Box::new(Error::ParsingError(crate::parse::Error::SyntaxError( + SyntaxErrorInfo { + expected: expected_strings, + label: None, + found: found_string, + }, + ))), span, source: None, } } fn label_with(&mut self, label: &'tokens str) { - if let Error::Syntax { - label: ref mut l, .. - } = self.error.as_mut() + if let Error::ParsingError(crate::parse::Error::SyntaxError(SyntaxErrorInfo { + label: ref mut l, + .. + })) = self.error.as_mut() { *l = Some(label.to_string()); } @@ -475,390 +474,41 @@ impl fmt::Display for ErrorCollector { /// Records _what_ happened but not where. #[derive(Debug, Clone)] pub enum Error { - DependencyPathNotFound { - path: PathBuf, - }, - DependencyNotADirectory { - path: PathBuf, - }, - ReservedDependencyKeyword { - keyword: String, - }, - DuplicateDependencyAlias { - alias: String, - context: String, - }, - InvalidDependencyIdentifier { - alias: String, - }, - Internal { - msg: String, - }, - UnknownLibrary { - name: String, - }, - ArraySizeNonZero { - size: usize, - }, - ListBoundPow2 { - bound: usize, - }, - BitStringPow2 { - len: usize, - }, - CannotParse { - msg: String, - }, - Grammar { - msg: String, - }, - Syntax { - expected: Vec, - label: Option, - found: Option, - }, - IncompatibleMatchArms { - first: MatchPattern, - second: MatchPattern, - }, - // TODO: Remove CompileError once SimplicityHL has a type system - // The SimplicityHL compiler should never produce ill-typed Simplicity code - // The compiler can only be this precise if it knows a type system at least as expressive as Simplicity's - CannotCompile { - source: simplicity::types::Error, - }, - ParseInt { - source: std::num::ParseIntError, - }, - ParseCrateInt { - source: crate::num::ParseIntError, - }, - JetDoesNotExist { - name: JetName, - }, - InvalidCast { - source: ResolvedType, - target: ResolvedType, - }, - FileNotFound { - filename: PathBuf, - }, - ExternalFileNotFound { - lib: String, - filename: PathBuf, - }, - LocalFileImportedAsExternal { - path: PathBuf, - }, - RedefinedItem { - name: String, - }, - UnresolvedItem { - name: String, - }, - PrivateItem { - name: String, - }, - MainNoInputs, - MainNoOutput, - MainRequired, - MainOutOfEntryFile, - MainCannotBePublic, - MainCannotBeAlias, - FunctionRedefined { - name: FunctionName, - }, - FunctionUndefined { - name: FunctionName, - }, - InvalidNumberOfArguments { - expected: usize, - found: usize, - }, - FunctionNotFoldable { - name: FunctionName, - }, - FunctionNotLoopable { - name: FunctionName, - }, - ExpressionUnexpectedType { - ty: ResolvedType, - }, - ExpressionTypeMismatch { - expected: ResolvedType, - found: ResolvedType, - }, - ExpressionNotConstant, - IntegerOutOfBounds { - ty: UIntType, - }, - UndefinedVariable { - identifier: Identifier, - }, - RedefinedAlias { - name: AliasName, - }, - RedefinedAliasAsBuiltin { - name: AliasName, - }, - UndefinedAlias { - name: AliasName, - }, - DuplicateAlias { - name: String, - }, - VariableReuseInPattern { - identifier: Identifier, - }, - WitnessReused { - name: WitnessName, - }, - WitnessTypeMismatch { - name: WitnessName, - declared: ResolvedType, - assigned: ResolvedType, - }, - WitnessReassigned { - name: WitnessName, - }, - WitnessOutsideMain, - ModuleRedefined { - name: ModuleName, - }, - ArgumentMissing { - name: WitnessName, - }, - ArgumentTypeMismatch { - name: WitnessName, - declared: ResolvedType, - assigned: ResolvedType, - }, - UseKeywordIsNotSupported, + Internal { msg: String }, + LexerError { msg: String }, + ParsingError(crate::parse::Error), + AnalyzingError(crate::ast::Error), + DriverError(crate::driver::Error), + CompileError(crate::compile::Error), } #[rustfmt::skip] impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::DependencyPathNotFound { path } => write!( - f, - "Path not found: {}", path.display() - ), - Error::DependencyNotADirectory { path } => write!( - f, - "Path must be a directory: {}", path.display() - ), - Error::ReservedDependencyKeyword { keyword } => write!( - f, - "The '{keyword}' keyword is reserved and cannot be manually mapped. Use the builder's context definitions instead." - ), - Error::DuplicateDependencyAlias { alias, context } => write!( - f, - "Duplicate dependency mapping: alias '{alias}' is defined multiple times for context '{context}'" - ), - Error::InvalidDependencyIdentifier { alias } => write!( - f, - "Invalid dependency alias '{alias}': must be a valid identifier and not a reserved keyword" - ), Error::Internal { msg } => write!( f, - "INTERNAL ERROR: {msg}" - ), - Error::UnknownLibrary { name } => write!( - f, - "Unknown module or library '{name}'" - ), - Error::ArraySizeNonZero { size } => write!( - f, - "Expected a non-negative integer as array size, found {size}" - ), - Error::ListBoundPow2 { bound } => write!( - f, - "Expected a power of two greater than one (2, 4, 8, 16, 32, ...) as list bound, found {bound}" - ), - Error::BitStringPow2 { len } => write!( - f, - "Expected a valid bit string length (1, 2, 4, 8, 16, 32, 64, 128, 256), found {len}" - ), - Error::CannotParse{ msg } => write!( - f, - "Cannot parse: {msg}" - ), - Error::Grammar{ msg } => write!( - f, - "Grammar error: {msg}" - ), - Error::FileNotFound { filename: path } => write!( - f, - "Local file `{}` not found", path.to_string_lossy() - ), - Error::ExternalFileNotFound { lib, filename: path } => write!( - f, - "File `{}` not found in external library `{}`", path.to_string_lossy(), lib - ), - Error::LocalFileImportedAsExternal { path } => write!( - f, - "File `{}` is part of the local project and must be imported using the `crate::` prefix", path.to_string_lossy() - ), - Error::Syntax { expected, label, found } => { - let found_text = found.clone().unwrap_or("end of input".to_string()); - match (label, expected.len()) { - (Some(l), _) => write!(f, "Expected {}, found {}", l, found_text), - (None, 1) => { - let exp_text = expected.first().unwrap(); - write!(f, "Expected '{}', found '{}'", exp_text, found_text) - } - (None, 0) => write!(f, "Unexpected {}", found_text), - (None, _) => { - let exp_text = expected.iter().map(|s| format!("'{}'", s)).join(", "); - write!(f, "Expected one of {}, found '{}'", exp_text, found_text) - } - } - } - Error::IncompatibleMatchArms { first, second} => write!( - f, - "Match arm `{first}` is incompatible with arm `{second}`" - ), - Error::CannotCompile{ .. } => write!( - f, - "Failed to compile to Simplicity" - ), - Error::ParseInt { .. } | Error::ParseCrateInt { .. } => write!(f, "Integer parsing error"), - Error::JetDoesNotExist { name } => write!( - f, - "Jet `{name}` does not exist" - ), - Error::InvalidCast { source, target } => write!( - f, - "Cannot cast values of type `{source}` as values of type `{target}`" - ), - Error::MainNoInputs => write!( - f, - "Main function takes no input parameters" - ), - Error::MainNoOutput => write!( - f, - "Main function produces no output" - ), - Error::MainRequired => write!( - f, - "Main function is required" - ), - Error::MainOutOfEntryFile => write!( - f, - "The 'main' function must be defined in the entry point file" - ), - Error::MainCannotBePublic => write!( - f, - "Main function cannot be public" - ), - Error::MainCannotBeAlias => write!( - f, - "Main function cannot be alias", - ), - Error::FunctionRedefined { name } => write!( - f, - "Function `{name}` was defined multiple times" + "{msg}" ), - Error::FunctionUndefined { name } => write!( + Error::ParsingError(err) => write!( f, - "Function `{name}` was called but not defined" + "{err}" ), - Error::RedefinedItem { name } => write!( + Error::AnalyzingError(err) => write!( f, - "Item `{name}` was defined multiple times" + "{err}" ), - Error::UnresolvedItem { name } => write!( + Error::DriverError(err) => write!( f, - "Item `{name}` could not be found" + "{err}" ), - Error::PrivateItem { name } => write!( + Error::CompileError(err) => write!( f, - "Item `{name}` is private" + "{err}" ), - Error::InvalidNumberOfArguments { expected, found } => write!( + Error::LexerError { msg } => write!( f, - "Expected {expected} arguments, found {found} arguments" - ), - Error::FunctionNotFoldable { name } => write!( - f, - "Expected a signature like `fn {name}(element: E, accumulator: A) -> A` for a fold" - ), - Error::FunctionNotLoopable { name } => write!( - f, - "Expected a signature like `fn {name}(accumulator: A, context: C, counter u{{1,2,4,8,16}}) -> Either` for a for-while loop" - ), - Error::ExpressionUnexpectedType { ty } => write!( - f, - "Expected expression of type `{ty}`; found something else" - ), - Error::ExpressionTypeMismatch { expected, found } => write!( - f, - "Expected expression of type `{expected}`, found type `{found}`" - ), - Error::ExpressionNotConstant => write!( - f, - "Expression cannot be evaluated at compile time" - ), - Error::IntegerOutOfBounds { ty } => write!( - f, - "Value is out of bounds for type `{ty}`" - ), - Error::UndefinedVariable { identifier } => write!( - f, - "Variable `{identifier}` is not defined" - ), - Error::RedefinedAlias { name } => write!( - f, - "Type alias `{name}` was defined multiple times" - ), - Error::RedefinedAliasAsBuiltin { name } => write!( - f, - "Type alias `{name}` is already exists as built-in alias" - ), - Error::UndefinedAlias { name } => write!( - f, - "Type alias `{name}` is not defined" - ), - Error::DuplicateAlias { name } => write!( - f, - "The alias `{name}` was defined multiple times" - ), - Error::VariableReuseInPattern { identifier } => write!( - f, - "Variable `{identifier}` is used twice in the pattern" - ), - Error::WitnessReused { name } => write!( - f, - "Witness `{name}` has been used before somewhere in the program" - ), - Error::WitnessTypeMismatch { name, declared, assigned } => write!( - f, - "Witness `{name}` was declared with type `{declared}` but its assigned value is of type `{assigned}`" - ), - Error::WitnessReassigned { name } => write!( - f, - "Witness `{name}` has already been assigned a value" - ), - Error::WitnessOutsideMain => write!( - f, - "Witness expressions are not allowed outside the `main` function" - ), - Error::ModuleRedefined { name } => write!( - f, - "Module `{name}` is defined twice" - ), - Error::ArgumentMissing { name } => write!( - f, - "Parameter `{name}` is missing an argument" - ), - Error::ArgumentTypeMismatch { name, declared, assigned } => write!( - f, - "Parameter `{name}` was declared with type `{declared}` but its assigned argument is of type `{assigned}`" - ), - Error::UseKeywordIsNotSupported => write!( - f, - "The `use` keyword is not supported yet" + "{msg}" ), } } @@ -867,9 +517,10 @@ impl fmt::Display for Error { impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Error::ParseInt { source } => Some(source), - Error::ParseCrateInt { source } => Some(source), - Error::CannotCompile { source } => Some(source), + Error::ParsingError(err) => Some(err), + Error::AnalyzingError(err) => Some(err), + Error::DriverError(err) => Some(err), + Error::CompileError(err) => Some(err), _ => None, } } @@ -882,27 +533,46 @@ impl Error { } } -impl From for Error { - fn from(error: std::num::ParseIntError) -> Self { - Self::ParseInt { source: error } +impl From for Error { + fn from(error: simplicity::types::Error) -> Self { + Self::CompileError(crate::compile::Error::TypeError(error)) } } -impl From for Error { - fn from(error: crate::num::ParseIntError) -> Self { - Self::ParseCrateInt { source: error } +impl From for Error { + fn from(error: crate::parse::Error) -> Self { + Self::ParsingError(error) } } -impl From for Error { - fn from(error: simplicity::types::Error) -> Self { - Self::CannotCompile { source: error } +impl From for Error { + fn from(error: crate::ast::Error) -> Self { + match error { + crate::ast::Error::Internal { msg } => Self::Internal { msg }, + _ => Self::AnalyzingError(error), + } + } +} + +impl From for Error { + fn from(error: crate::driver::Error) -> Self { + match error { + crate::driver::Error::Internal { msg } => Self::Internal { msg }, + _ => Self::DriverError(error), + } + } +} + +impl From for Error { + fn from(error: crate::compile::Error) -> Self { + Self::CompileError(error) } } #[cfg(test)] mod tests { use super::*; + use crate::parse::Error; const CONTENT: &str = r#"let a1: List = None; let x: u32 = Left( diff --git a/src/lexer.rs b/src/lexer.rs index 06d63adc..229662de 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -247,7 +247,7 @@ pub fn lex<'src>(input: &'src str) -> (Option>, Vec, + pub label: Option, + pub found: Option, +} + +impl fmt::Display for SyntaxErrorInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let found_text = self + .found + .clone() + .unwrap_or_else(|| "end of input".to_string()); + + match (&self.label, self.expected.len()) { + (Some(l), _) => write!(f, "Expected {}, found {}", l, found_text), + (None, 1) => { + let exp_text = self.expected.first().unwrap(); + write!(f, "Expected '{}', found '{}'", exp_text, found_text) + } + (None, 0) => write!(f, "Unexpected {}", found_text), + (None, _) => { + let exp_text = self.expected.iter().map(|s| format!("'{}'", s)).join(", "); + write!(f, "Expected one of {}, found '{}'", exp_text, found_text) + } + } + } +} + +impl std::error::Error for SyntaxErrorInfo {} + +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + #[error("Grammar error: {msg}")] + Grammar { msg: String }, + + #[error("Cannot parse: {msg}")] + CannotParse { msg: String }, + + #[error( + "Expected a power of two greater than one (2, 4, 8, 16, 32, ...) as list bound, found {bound}" + )] + ListBoundPow2 { bound: usize }, + + #[error(transparent)] + SyntaxError(#[from] SyntaxErrorInfo), + + #[error("Incompatible match arms: {first}, {second}")] + IncompatibleMatchArms { + first: MatchPattern, + second: MatchPattern, + }, + + #[error("Expected a non-negative integer as array size, found {size}")] + ArraySizeNonZero { size: usize }, + + #[error("Type alias `{name}` is already exists as built-in alias")] + RedefinedAliasAsBuiltin { name: AliasName }, +} + +impl Error { + /// Update the error with the affected span. + pub fn with_span(self, span: Span) -> RichError { + RichError::new(self.into(), span) + } +} + /// A program is a sequence of items. #[derive(Clone, Debug)] pub struct Program { @@ -2494,6 +2562,11 @@ mod test { .error() .to_string() .contains("Type alias `Ctx8` is already exists as built-in alias")); + matches!( + ty.error(), + &crate::error::Error::ParsingError(Error::RedefinedAliasAsBuiltin { ref name }) + if name == &AliasName::from_str_unchecked("Ctx8") + ); } #[test] diff --git a/src/pattern.rs b/src/pattern.rs index b54726ad..f26c355e 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use miniscript::iter::{Tree, TreeLike}; use crate::array::BTreeSlice; -use crate::error::Error; +use crate::ast::Error; use crate::named::{CoreExt, PairBuilder, SelectorBuilder}; use crate::str::Identifier; use crate::types::{ResolvedType, TypeInner}; diff --git a/src/resolution.rs b/src/resolution.rs index eea71223..67695271 100644 --- a/src/resolution.rs +++ b/src/resolution.rs @@ -1,5 +1,5 @@ -use crate::driver::CRATE_STR; -use crate::error::{Error, RichError, WithSpan as _}; +use crate::driver::{Error, CRATE_STR}; +use crate::error::{RichError, WithSpan}; use crate::parse::UseDecl; use crate::source::CanonPath; @@ -224,25 +224,19 @@ impl DependencyMap { ) -> Result { let drp_name = use_decl.drp_name()?; - let resolved = - Self::build_and_verify_path(&remapping.target, &parts[1..]).map_err(|failed_path| { - RichError::new( - Error::ExternalFileNotFound { - lib: drp_name.to_string(), - filename: failed_path, - }, - *use_decl.span(), - ) - })?; + let resolved = Self::build_and_verify_path(&remapping.target, &parts[1..]) + .map_err(|failed_path| Error::ExternalFileNotFound { + lib: drp_name.to_string(), + filename: failed_path, + }) + .with_span(*use_decl.span())?; if !resolved.starts_with(&remapping.target) { - return Err(RichError::new( - Error::ExternalFileNotFound { - lib: drp_name.to_string(), - filename: resolved.as_path().to_path_buf(), - }, - *use_decl.span(), - )); + return Err(Error::ExternalFileNotFound { + lib: drp_name.to_string(), + filename: resolved.as_path().to_path_buf(), + }) + .with_span(*use_decl.span()); } self.check_local_file_imported_as_external(current_file, &resolved, use_decl)?; @@ -262,24 +256,19 @@ impl DependencyMap { .ok_or_else(|| Error::Internal { msg: "The 'crate' root path was not configured by the compiler.".to_string(), }) - .map_err(|e| RichError::new(e, *use_decl.span()))?; - - let resolved = Self::build_and_verify_path(root, &parts[1..]).map_err(|failed_path| { - RichError::new( - Error::FileNotFound { - filename: failed_path, - }, - *use_decl.span(), - ) - })?; + .with_span(*use_decl.span())?; + + let resolved = Self::build_and_verify_path(root, &parts[1..]) + .map_err(|failed_path| Error::FileNotFound { + filename: failed_path, + }) + .with_span(*use_decl.span())?; if !resolved.starts_with(root) { - return Err(RichError::new( - Error::FileNotFound { - filename: resolved.as_path().to_path_buf(), - }, - *use_decl.span(), - )); + return Err(Error::FileNotFound { + filename: resolved.as_path().to_path_buf(), + }) + .with_span(*use_decl.span()); } Ok(resolved) @@ -415,7 +404,7 @@ pub(crate) mod tests { assert!(result.is_err()); assert!(matches!( result.unwrap_err().error(), - Error::UnknownLibrary { .. } + crate::error::Error::DriverError(Error::UnknownLibrary { .. }) )); } @@ -473,7 +462,7 @@ pub(crate) mod tests { assert!(matches!( result.unwrap_err().error(), - Error::LocalFileImportedAsExternal { .. } + crate::error::Error::DriverError(Error::LocalFileImportedAsExternal { .. }) )); } @@ -514,7 +503,8 @@ pub(crate) mod tests { assert!(result.is_err()); assert!(matches!( result.unwrap_err().error(), - Error::Internal{msg} if msg.contains("The 'crate' root path was not configured") + crate::error::Error::Internal{ msg } + if msg.contains("The 'crate' root path was not configured") )); } diff --git a/src/value.rs b/src/value.rs index 1ccb38bd..75172d99 100644 --- a/src/value.rs +++ b/src/value.rs @@ -8,7 +8,8 @@ use simplicity::types::Final as SimType; use simplicity::{BitCollector, Value as SimValue, ValueRef}; use crate::array::{BTreeSlice, Combiner, Partition, Unfolder}; -use crate::error::{Error, RichError, WithSpan}; +use crate::ast::Error; +use crate::error::{RichError, WithSpan}; use crate::num::{NonZeroPow2Usize, Pow2Usize, U256}; use crate::parse::ParseFromStr; use crate::str::{Binary, Decimal, Hexadecimal}; diff --git a/src/witness.rs b/src/witness.rs index 3fd878d4..218095cd 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use std::fmt; use std::sync::Arc; -use crate::error::{Error, RichError, WithContent, WithSpan}; +use crate::ast::Error; +use crate::error::{RichError, WithContent, WithSpan}; use crate::parse::ParseFromStr; use crate::str::WitnessName; use crate::types::{AliasedType, ResolvedType}; @@ -234,10 +235,10 @@ mod tests { ) .expect("driver works"); match ast::Program::analyze(&driver_program, Box::new(ElementsJetHinter::new())) - .map_err(Error::from) + .map_err(crate::error::Error::from) { Ok(_) => panic!("Witness reuse was falsely accepted"), - Err(Error::WitnessReused { .. }) => {} + Err(crate::error::Error::AnalyzingError(ast::Error::WitnessReused { .. })) => {} Err(error) => panic!("Unexpected error: {error}"), } }