diff --git a/Cargo.lock b/Cargo.lock index 5686d7ad..f884f72d 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", ] @@ -610,7 +610,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.117", ] [[package]] @@ -673,6 +673,7 @@ dependencies = [ "serde", "serde_json", "simplicity-lang", + "thiserror", ] [[package]] @@ -719,15 +720,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" @@ -773,7 +794,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -795,7 +816,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 6216826b..082289f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,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 0ab29779..8c08ab3d 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -10,7 +10,7 @@ use simplicity::jet::Elements; 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::num::{NonZeroPow2Usize, Pow2Usize}; use crate::parse::MatchPattern; use crate::pattern::Pattern; @@ -22,6 +22,115 @@ use crate::value::{UIntValue, Value}; use crate::witness::{Parameters, WitnessTypes, WitnessValues}; use crate::{driver, impl_eq_hash, parse}; +#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq, Hash)] +pub enum Error { + #[error("Main function is required")] + MainRequired, + + #[error("Function `{0}` was defined multiple times")] + FunctionRedefined(FunctionName), + + #[error("Type alias `{0}` is not defined")] + UndefinedAlias(AliasName), + + #[error("INTERNAL ERROR: {0}")] + Internal(String), + + #[error("Item `{0}` is private")] + PrivateItem(String), + + #[error("Type alias `{0}` was defined multiple times")] + RedefinedAlias(AliasName), + + #[error("Expected expression of type `{0}`, found type `{1}`")] + ExpressionTypeMismatch(ResolvedType, ResolvedType), + + #[error("Witness expressions are not allowed outside the `main` function")] + WitnessOutsideMain, + + #[error("Witness `{0}` has been used before somewhere in the program")] + WitnessReused(WitnessName), + + #[error("Function `{0}` was called but not defined")] + FunctionUndefined(FunctionName), + + #[error("Failed to compile to Simplicity: {0}")] + CannotCompile(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 `{0}`; found something else")] + ExpressionUnexpectedType(ResolvedType), + + #[error("Variable `{0}` is used twice in the pattern")] + VariableReuseInPattern(Identifier), + + #[error("Variable `{0}` is not defined")] + UndefinedVariable(Identifier), + + #[error("Value is out of bounds for type `{0}`")] + IntegerOutOfBounds(UIntType), + + #[error("Cannot parse: {0}")] + IntParseError(String), + + #[error("Expected a valid bit string length (1, 2, 4, 8, 16, 32, 64, 128, 256), found {0}")] + BitStringPow2(usize), + + #[error("{0}")] + Generic(String), + + #[error("Expected {0} arguments, found {1} arguments")] + InvalidNumberOfArguments(usize, usize), + + #[error("Cannot cast values of type `{0}` as values of type `{1}`")] + InvalidCast(ResolvedType, ResolvedType), + + #[error("Jet `{0}` does not exist")] + JetDoesNotExist(crate::str::JetName), + + #[error("Expected a signature like `fn {0}(element: E, accumulator: A) -> A` for a fold")] + FunctionNotFoldable(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 {0}(accumulator: A, context: C, counter u{{1,2,4,8,16}}) -> Either` for a for-while loop")] + FunctionNotLoopable(FunctionName), + + #[error("Witness `{0}` was declared with type `{1}` but its assigned value is of type `{2}`")] + WitnessTypeMismatch(WitnessName, ResolvedType, ResolvedType), + + #[error("Parameter `{0}` is missing an argument")] + ArgumentMissing(WitnessName), + + #[error( + "Parameter `{0}` was declared with type `{1}` but its assigned argument is of type `{2}`" + )] + ArgumentTypeMismatch(WitnessName, ResolvedType, ResolvedType), +} + +impl From for Error { + fn from(error: crate::num::ParseIntError) -> Self { + Self::IntParseError(error.to_string()) + } +} +impl From for Error { + fn from(error: std::num::ParseIntError) -> Self { + Self::IntParseError(error.to_string()) + } +} + /// A program consists of the main function. /// /// Other items such as custom functions or type aliases @@ -896,7 +1005,7 @@ impl AbstractSyntaxTree for Item { Function::analyze(function, ty, scope).map(Self::Function) } parse::Item::Use(use_decl) => Err(RichError::new( - Error::CannotCompile("The `use` keyword is not supported yet.".to_string()), + Error::CannotCompile("The `use` keyword is not supported yet.".to_string()).into(), *use_decl.span(), )), parse::Item::Module => Ok(Self::Module), diff --git a/src/compile/error.rs b/src/compile/error.rs new file mode 100644 index 00000000..e705ccb3 --- /dev/null +++ b/src/compile/error.rs @@ -0,0 +1,8 @@ +#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq, Hash)] +pub enum Error { + #[error("Variable `{0}` is not defined")] + UndefinedVariable(crate::str::Identifier), + + #[error("Simplicity compiler error: {0}")] + TypeError(String), +} diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 9f8dffb4..f65d973e 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; @@ -16,7 +19,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..312e765a --- /dev/null +++ b/src/driver/error.rs @@ -0,0 +1,45 @@ +use crate::str::{AliasName, FunctionName}; + +#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq, Hash)] +pub enum Error { + #[error("Item `{0}` could not be found")] + UnresolvedItem(String), + + #[error("Item `{0}` is private")] + PrivateItem(String), + + #[error("The alias `{0}` was defined multiple times")] + DuplicateAlias(String), + + #[error("Item `{0}` was defined multiple times")] + RedefinedItem(String), + + #[error("Main function cannot be public")] + MainCannotBePublic, + + #[error("Function `{0}` was defined multiple times")] + FunctionRedefined(FunctionName), + + #[error("Unknown module or library '{0}'")] + UnknownLibrary(String), + + #[error("Main function cannot be alias")] + MainCannotBeAlias, + + #[error("Type alias `{0}` was defined multiple times")] + RedefinedAlias(AliasName), + + #[error("Local file `{0}` not found")] + FileNotFound(String), + + #[error( + "File `{0}` is part of the local project and must be imported using the `crate::` prefix" + )] + LocalFileImportedAsExternal(String), + + #[error("File `{1}` not found in external library `{0}`")] + ExternalFileNotFound(String, String), + + #[error("INTERNAL ERROR: {0}")] + Internal(String), +} diff --git a/src/driver/mod.rs b/src/driver/mod.rs index c952b54f..a89159e3 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -27,21 +27,23 @@ //! 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; use std::collections::{HashMap, HashSet, VecDeque}; -use std::path::PathBuf; 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::{CanonPath, DependencyMap, SourceFile}; 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"; @@ -257,8 +259,11 @@ impl DependencyGraph { handler: &mut ErrorCollector, ) -> Option { let Ok(content) = std::fs::read_to_string(path.as_path()) else { - let err = RichError::new(Error::FileNotFound(PathBuf::from(path.as_path())), span) - .with_source(importer_source.clone()); + let err = RichError::new( + Error::FileNotFound(path.as_path().to_string_lossy().to_string()).into(), + span, + ) + .with_source(importer_source.clone()); handler.push(err); return None; diff --git a/src/driver/resolve_order.rs b/src/driver/resolve_order.rs index d4e90d96..d44b23ec 100644 --- a/src/driver/resolve_order.rs +++ b/src/driver/resolve_order.rs @@ -1,8 +1,9 @@ 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::{ErrorCollector, RichError, Span}; use crate::impl_eq_hash; use crate::parse::{self, AliasedSymbolName, Function, TypeAlias, Visibility}; use crate::str::{AliasName, FunctionName, SymbolName}; @@ -41,8 +42,11 @@ impl Program { for item in parsed.items() { if let parse::Item::Use(use_decl) = item { handler.push( - RichError::new(Error::UnknownLibrary(use_decl.str_path()), *use_decl.span()) - .with_content(content.clone()), + RichError::new( + Error::UnknownLibrary(use_decl.str_path()).into(), + *use_decl.span(), + ) + .with_content(content.clone()), ); continue; } @@ -259,8 +263,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 @@ -317,11 +327,14 @@ impl DependencyGraph { // 2. Verify Existence using T let visibility: &Visibility = namespace.resolutions[ind] .get(&target_name) - .ok_or_else(|| RichError::new(Error::UnresolvedItem(name.to_string()), span))?; + .ok_or_else(|| RichError::new(Error::UnresolvedItem(name.to_string()).into(), span))?; // 3. Verify Visibility if matches!(visibility, parse::Visibility::Private) { - return Err(RichError::new(Error::PrivateItem(name.to_string()), span)); + return Err(RichError::new( + Error::PrivateItem(name.to_string()).into(), + span, + )); } // 4. Determine the local name and ID up front @@ -333,7 +346,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(RichError::new(Error::MainCannotBeAlias.into(), span)); } t_alias } else { @@ -345,14 +358,14 @@ impl DependencyGraph { // 5. Check for collisions using `namespace` fields if namespace.registry.direct_targets.contains_key(&local_id) { return Err(RichError::new( - Error::DuplicateAlias(local_symbol.to_string()), + Error::DuplicateAlias(local_symbol.to_string()).into(), span, )); } if namespace.memo.contains(&local_id) { return Err(RichError::new( - Error::RedefinedItem(local_symbol.to_string()), + Error::RedefinedItem(local_symbol.to_string()).into(), span, )); } @@ -400,7 +413,7 @@ fn register_type_alias( if tracker.memo.contains(&local_id) { return Err(RichError::new( - Error::RedefinedAlias(name.clone()), + Error::RedefinedAlias(name.clone()).into(), *item.span(), )); } @@ -421,12 +434,15 @@ 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(RichError::new( + Error::MainCannotBePublic.into(), + *item.span(), + )); } if tracker.memo.contains(&local_id) { return Err(RichError::new( - Error::FunctionRedefined(name.clone()), + Error::FunctionRedefined(name.clone()).into(), *item.span(), )); } diff --git a/src/error.rs b/src/error.rs index bbd36836..095c0e2b 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,14 +9,9 @@ use chumsky::text::Char; use chumsky::util::MaybeRef; use chumsky::DefaultExpected; -use itertools::Itertools; -use simplicity::elements; - use crate::lexer::Token; -use crate::parse::MatchPattern; +use crate::parse::SyntaxErrorInfo; use crate::resolution::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)] @@ -204,7 +198,9 @@ impl RichError { /// a problem on the parsing side. pub fn parsing_error(reason: &str) -> Self { Self { - error: Box::new(Error::CannotParse(reason.to_string())), + error: Box::new(Error::ParsingError(crate::parse::Error::CannotParse( + reason.to_string(), + ))), span: Span::new(0, 0), source: None, } @@ -322,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, } } @@ -357,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, } @@ -384,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,58 +475,11 @@ impl fmt::Display for ErrorCollector { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Error { Internal(String), - UnknownLibrary(String), - ArraySizeNonZero(usize), - ListBoundPow2(usize), - BitStringPow2(usize), - CannotParse(String), - Grammar(String), - Syntax { - expected: Vec, - label: Option, - found: Option, - }, - IncompatibleMatchArms(MatchPattern, 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(String), - JetDoesNotExist(JetName), - InvalidCast(ResolvedType, ResolvedType), - FileNotFound(PathBuf), - ExternalFileNotFound(String, PathBuf), - LocalFileImportedAsExternal(PathBuf), - RedefinedItem(String), - UnresolvedItem(String), - PrivateItem(String), - MainNoInputs, - MainNoOutput, - MainRequired, - MainOutOfEntryFile, - MainCannotBePublic, - MainCannotBeAlias, - FunctionRedefined(FunctionName), - FunctionUndefined(FunctionName), - InvalidNumberOfArguments(usize, usize), - FunctionNotFoldable(FunctionName), - FunctionNotLoopable(FunctionName), - ExpressionUnexpectedType(ResolvedType), - ExpressionTypeMismatch(ResolvedType, ResolvedType), - ExpressionNotConstant, - IntegerOutOfBounds(UIntType), - UndefinedVariable(Identifier), - RedefinedAlias(AliasName), - RedefinedAliasAsBuiltin(AliasName), - UndefinedAlias(AliasName), - DuplicateAlias(String), - VariableReuseInPattern(Identifier), - WitnessReused(WitnessName), - WitnessTypeMismatch(WitnessName, ResolvedType, ResolvedType), - WitnessReassigned(WitnessName), - WitnessOutsideMain, - ModuleRedefined(ModuleName), - ArgumentMissing(WitnessName), - ArgumentTypeMismatch(WitnessName, ResolvedType, ResolvedType), + LexerError(String), + ParsingError(crate::parse::Error), + AnalyzingError(crate::ast::Error), + DriverError(crate::driver::Error), + CompileError(crate::compile::Error), } #[rustfmt::skip] @@ -537,196 +490,25 @@ impl fmt::Display for Error { f, "INTERNAL ERROR: {err}" ), - 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(description) => write!( - f, - "Cannot parse: {description}" - ), - Error::Grammar(description) => write!( - f, - "Grammar error: {description}" - ), - Error::FileNotFound(path) => write!( - f, - "Local file `{}` not found", path.to_string_lossy() - ), - Error::ExternalFileNotFound(lib, 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(pattern1, pattern2) => write!( - f, - "Match arm `{pattern1}` is incompatible with arm `{pattern2}`" - ), - Error::CannotCompile(description) => write!( - f, - "Failed to compile to Simplicity: {description}" - ), - 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" - ), - Error::FunctionUndefined(name) => write!( - f, - "Function `{name}` was called but not defined" - ), - Error::RedefinedItem(name) => write!( + Error::ParsingError(err) => write!( f, - "Item `{name}` was defined multiple times" + "{err}" ), - Error::UnresolvedItem(name) => write!( + Error::AnalyzingError(err) => write!( f, - "Item `{name}` could not be found" + "{err}" ), - Error::PrivateItem(name) => write!( + Error::DriverError(err) => write!( f, - "Item `{name}` is private" + "{err}" ), - Error::InvalidNumberOfArguments(expected, found) => write!( + Error::CompileError(err) => write!( f, - "Expected {expected} arguments, found {found} arguments" + "{err}" ), - Error::FunctionNotFoldable(name) => write!( + Error::LexerError(err) => 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(identifier) => write!( - f, - "Type alias `{identifier}` was defined multiple times" - ), - Error::RedefinedAliasAsBuiltin(identifier) => write!( - f, - "Type alias `{identifier}` is already exists as built-in alias" - ), - Error::UndefinedAlias(identifier) => write!( - f, - "Type alias `{identifier}` 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}`" + "{err}" ), } } @@ -741,33 +523,46 @@ impl Error { } } -impl From for Error { - fn from(error: elements::hex::Error) -> Self { - Self::CannotParse(error.to_string()) +impl From for Error { + fn from(error: simplicity::types::Error) -> Self { + Self::CompileError(crate::compile::Error::TypeError(error.to_string())) } } -impl From for Error { - fn from(error: std::num::ParseIntError) -> Self { - Self::CannotParse(error.to_string()) +impl From for Error { + fn from(error: crate::parse::Error) -> Self { + Self::ParsingError(error) } } -impl From for Error { - fn from(error: crate::num::ParseIntError) -> Self { - Self::CannotParse(error.to_string()) +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: simplicity::types::Error) -> Self { - Self::CannotCompile(error.to_string()) +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 94f7a8d0..a175a1ef 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, thiserror::Error, Clone, Eq, PartialEq, Hash)] +pub enum Error { + #[error("Grammar error: {0}")] + Grammar(String), + + #[error( + "Expected a power of two greater than one (2, 4, 8, 16, 32, ...) as list bound, found {0}" + )] + ListBoundPow2(usize), + + #[error("Cannot parse: {0}")] + CannotParse(String), + + #[error(transparent)] + SyntaxError(#[from] SyntaxErrorInfo), + + #[error("Incompatible match arms: {0}, {1}")] + IncompatibleMatchArms(MatchPattern, MatchPattern), + + #[error("Expected a non-negative integer as array size, found {0}")] + ArraySizeNonZero(usize), + + #[error("Type alias `{0}` is already exists as built-in alias")] + RedefinedAliasAsBuiltin(AliasName), +} + +impl Error { + /// Update the error with the affected span. + pub fn with_span(self, span: Span) -> RichError { + RichError::new(self.into(), span) + } +} + +impl From for Error { + fn from(error: simplicity::elements::hex::Error) -> Self { + Self::CannotParse(error.to_string()) + } +} + +impl From for Error { + fn from(error: std::num::ParseIntError) -> Self { + Self::CannotParse(error.to_string()) + } +} + +impl From for Error { + fn from(error: crate::num::ParseIntError) -> Self { + Self::CannotParse(error.to_string()) + } +} + /// A program is a sequence of items. #[derive(Clone, Debug)] pub struct Program { @@ -2533,7 +2616,9 @@ mod test { assert_eq!( ty.error(), - &Error::RedefinedAliasAsBuiltin(AliasName::from_str_unchecked("Ctx8")) + &crate::error::Error::ParsingError(Error::RedefinedAliasAsBuiltin( + AliasName::from_str_unchecked("Ctx8") + )) ); } diff --git a/src/pattern.rs b/src/pattern.rs index b1a1f4f1..674fb8f7 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 22ec3abb..8f8eeb73 100644 --- a/src/resolution.rs +++ b/src/resolution.rs @@ -2,8 +2,8 @@ use std::io; use std::path::Path; use std::sync::Arc; -use crate::driver::{CanonSourceFile, CRATE_STR}; -use crate::error::{Error, RichError, WithSpan as _}; +use crate::driver::{CanonSourceFile, Error, CRATE_STR}; +use crate::error::{RichError, WithSpan as _}; use crate::parse::UseDecl; /// Powers error reporting by mapping compiler diagnostics to the specific file. @@ -195,11 +195,14 @@ impl DependencyMap { let resolved = Self::build_and_verify_path(&remapping.target, &parts[1..]) .map_err(|failed_path| { let err = if drp_name == CRATE_STR { - Error::FileNotFound(failed_path) + Error::FileNotFound(failed_path.to_string_lossy().to_string()) } else { - Error::ExternalFileNotFound(drp_name.to_string(), failed_path) + Error::ExternalFileNotFound( + drp_name.to_string(), + failed_path.to_string_lossy().to_string(), + ) }; - RichError::new(err, *use_decl.span()) + RichError::new(err.into(), *use_decl.span()) })?; self.check_local_file_imported_as_external( @@ -250,7 +253,7 @@ impl DependencyMap { if let (Some(curr), Some(res)) = (current_crate, resolved_crate) { if curr.target == res.target { return Err(Error::LocalFileImportedAsExternal( - resolved.as_path().to_path_buf(), + resolved.as_path().to_string_lossy().to_string(), )) .with_span(*use_decl.span()); } @@ -352,7 +355,7 @@ pub(crate) mod tests { assert!(result.is_err()); assert!(matches!( result.unwrap_err().error(), - Error::UnknownLibrary(..) + crate::error::Error::DriverError(Error::UnknownLibrary(..)) )); } @@ -421,7 +424,7 @@ pub(crate) mod tests { assert!(matches!( result.unwrap_err().error(), - Error::LocalFileImportedAsExternal(..) + crate::error::Error::DriverError(Error::LocalFileImportedAsExternal(..)) )); } @@ -464,7 +467,7 @@ 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 3ca7fdca..0f95b9f6 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 a5e1d553..b4730816 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; use crate::parse::ParseFromStr; use crate::str::WitnessName; @@ -239,9 +240,9 @@ mod tests { &mut error_collector, ) .expect("driver works"); - match ast::Program::analyze(&driver_program).map_err(Error::from) { + match ast::Program::analyze(&driver_program).map_err(crate::error::Error::from) { Ok(_) => panic!("Witness reuse was falsely accepted"), - Err(Error::WitnessReused(..)) => {} + Err(crate::error::Error::AnalyzingError(Error::WitnessReused(_))) => {} Err(error) => panic!("Unexpected error: {error}"), } }