use std::fmt;
use std::ops::Range;
use std::path::PathBuf;
use std::sync::Arc;
use chumsky::error::Error as ChumskyError;
use chumsky::input::ValueInput;
use chumsky::label::LabelError;
use chumsky::text::Char;
use chumsky::util::MaybeRef;
use chumsky::DefaultExpected;
use itertools::Itertools;
use crate::driver::CRATE_STR;
use crate::lexer::Token;
use crate::parse::MatchPattern;
use crate::source::SourceFile;
use crate::str::{AliasName, FunctionName, Identifier, JetName, ModuleName, WitnessName};
use crate::types::{ResolvedType, UIntType};
use crate::unstable::UnstableFeature;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
#[cfg(feature = "arbitrary")]
pub(crate) const DUMMY: Self = Self::new(0, 0);
pub const fn new(start: usize, end: usize) -> Self {
assert!(start <= end, "Start cannot come after end");
Self { start, end }
}
pub fn to_slice<'a>(&self, file: &'a str) -> Option<&'a str> {
file.get(self.start..self.end)
}
}
impl chumsky::span::Span for Span {
type Context = ();
type Offset = usize;
fn new((): Self::Context, range: Range<Self::Offset>) -> Self {
Self {
start: range.start,
end: range.end,
}
}
fn context(&self) -> Self::Context {}
fn start(&self) -> Self::Offset {
self.start
}
fn end(&self) -> Self::Offset {
self.end
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)?;
Ok(())
}
}
impl From<chumsky::span::SimpleSpan> for Span {
fn from(span: chumsky::span::SimpleSpan) -> Self {
Self {
start: span.start,
end: span.end,
}
}
}
impl From<Range<usize>> for Span {
fn from(range: Range<usize>) -> Self {
Self::new(range.start, range.end)
}
}
impl From<&str> for Span {
fn from(s: &str) -> Self {
Span::new(0, s.len())
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Span {
fn arbitrary(_: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self::DUMMY)
}
}
pub trait WithSpan<T> {
fn with_span<S: Into<Span>>(self, span: S) -> Result<T, RichError>;
}
impl<T, E: Into<Error>> WithSpan<T> for Result<T, E> {
fn with_span<S: Into<Span>>(self, span: S) -> Result<T, RichError> {
self.map_err(|e| e.into().with_span(span.into()))
}
}
pub trait WithContent<T> {
fn with_content<C: Into<Arc<str>>>(self, content: C) -> Result<T, RichError>;
}
impl<T> WithContent<T> for Result<T, RichError> {
fn with_content<C: Into<Arc<str>>>(self, content: C) -> Result<T, RichError> {
self.map_err(|e| e.with_content(content.into()))
}
}
pub trait WithSource<T> {
fn with_source<S: Into<SourceFile>>(self, source: S) -> Result<T, RichError>;
}
impl<T> WithSource<T> for Result<T, RichError> {
fn with_source<S: Into<SourceFile>>(self, source: S) -> Result<T, RichError> {
self.map_err(|e| e.with_source(source.into()))
}
}
#[derive(Debug, Clone)]
pub struct RichError {
error: Box<Error>,
span: Span,
source: Option<SourceFile>,
}
impl RichError {
pub fn new(error: Error, span: Span) -> RichError {
RichError {
error: Box::new(error),
span,
source: None,
}
}
pub fn with_content(self, program_content: Arc<str>) -> Self {
Self {
error: self.error,
span: self.span,
source: Some(SourceFile::anonymous(program_content)),
}
}
pub fn with_source(self, source: impl Into<SourceFile>) -> Self {
Self {
error: self.error,
span: self.span,
source: Some(source.into()),
}
}
pub fn parsing_error(reason: &str) -> Self {
Self {
error: Box::new(Error::CannotParse {
msg: reason.to_string(),
}),
span: Span::new(0, 0),
source: None,
}
}
pub fn source(&self) -> &Option<SourceFile> {
&self.source
}
pub fn error(&self) -> &Error {
&self.error
}
pub fn span(&self) -> &Span {
&self.span
}
}
impl fmt::Display for RichError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn get_line_col(file: &str, offset: usize) -> (usize, usize) {
let mut line = 1;
let mut col = 0;
let slice = file.get(0..offset).unwrap_or_default();
for char in slice.chars() {
if char.is_newline() {
line += 1;
col = 0;
} else {
col += char.len_utf16();
}
}
(line, col + 1)
}
let Some(source) = &self.source else {
return write!(f, "{}", self.error);
};
let content = source.content();
if content.is_empty() {
return write!(f, "{}", self.error);
}
let (start_line, start_col) = get_line_col(&content, self.span.start);
let (end_line, end_col) = get_line_col(&content, self.span.end);
let start_line_index = start_line - 1;
let n_spanned_lines = end_line - start_line_index;
let line_num_width = end_line.to_string().len();
if let Some(name) = source.name() {
writeln!(
f,
"{:>width$}--> {}:{}:{}",
"",
name.display(),
start_line,
start_col,
width = line_num_width
)?;
}
writeln!(f, "{:width$} |", " ", width = line_num_width)?;
let mut lines = content
.split(|c: char| c.is_newline())
.skip(start_line_index)
.peekable();
let start_line_len = lines
.peek()
.map_or(0, |l| l.chars().map(char::len_utf16).sum());
for (relative_line_index, line_str) in lines.take(n_spanned_lines).enumerate() {
let line_num = start_line_index + relative_line_index + 1;
writeln!(f, "{line_num:line_num_width$} | {line_str}")?;
}
let is_multiline = end_line > start_line;
let (underline_start, underline_length) = match is_multiline {
true => (0, start_line_len),
false => (start_col, (end_col - start_col).max(1)),
};
write!(f, "{:width$} |", " ", width = line_num_width)?;
write!(f, "{:width$}", " ", width = underline_start)?;
write!(f, "{:^<width$} ", "", width = underline_length)?;
write!(f, "{}", self.error)
}
}
impl std::error::Error for RichError {}
impl From<RichError> for Error {
fn from(error: RichError) -> Self {
*error.error
}
}
impl From<RichError> for String {
fn from(error: RichError) -> Self {
error.to_string()
}
}
impl<'tokens, 'src: 'tokens, I> ChumskyError<'tokens, I> for RichError
where
I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
{
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,
_ => other,
}
}
}
impl<'tokens, 'src: 'tokens, I> LabelError<'tokens, I, DefaultExpected<'tokens, Token<'src>>>
for RichError
where
I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
{
fn expected_found<E>(
expected: E,
found: Option<MaybeRef<'tokens, Token<'src>>>,
span: Span,
) -> Self
where
E: IntoIterator<Item = DefaultExpected<'tokens, Token<'src>>>,
{
let expected_tokens: Vec<String> = expected
.into_iter()
.map(|t| match t {
DefaultExpected::Token(maybe) => maybe.to_string(),
DefaultExpected::Any => "anything".to_string(),
DefaultExpected::SomethingElse => "something else".to_string(),
DefaultExpected::EndOfInput => "end of input".to_string(),
_ => "UNEXPECTED_TOKEN".to_string(),
})
.collect();
let found_string = found.map(|t| t.to_string());
Self {
error: Box::new(Error::Syntax {
expected: expected_tokens,
label: None,
found: found_string,
}),
span,
source: None,
}
}
}
impl<'tokens, 'src: 'tokens, I> LabelError<'tokens, I, &'tokens str> for RichError
where
I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
{
fn expected_found<E>(
expected: E,
found: Option<MaybeRef<'tokens, Token<'src>>>,
span: Span,
) -> Self
where
E: IntoIterator<Item = &'tokens str>,
{
let expected_strings: Vec<String> = expected.into_iter().map(|s| s.to_string()).collect();
let found_string = found.map(|t| t.to_string());
Self {
error: Box::new(Error::Syntax {
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()
{
*l = Some(label.to_string());
}
}
}
#[derive(Debug, Clone)]
pub struct ErrorCollector {
errors: Vec<RichError>,
}
impl Default for ErrorCollector {
fn default() -> Self {
Self::new()
}
}
impl std::error::Error for ErrorCollector {}
impl ErrorCollector {
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn push(&mut self, error: RichError) {
self.errors.push(error);
}
pub fn extend(
&mut self,
source: impl Into<SourceFile> + Clone,
errors: impl IntoIterator<Item = RichError>,
) {
let new_errors = errors
.into_iter()
.map(|err| err.with_source(source.clone()));
self.errors.extend(new_errors);
}
pub fn extend_with_handler(
&mut self,
source: impl Into<SourceFile> + Clone,
handler: &ErrorCollector,
) {
self.extend(source, handler.errors.iter().cloned());
}
pub fn get(&self) -> &[RichError] {
&self.errors
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
}
impl fmt::Display for ErrorCollector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for err in self.get() {
writeln!(f, "{err}\n")?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum Error {
UnstableFeature {
feature: UnstableFeature,
},
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<String>,
label: Option<String>,
found: Option<String>,
},
IncompatibleMatchArms {
first: MatchPattern,
second: MatchPattern,
},
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,
},
MissingCrateKeyword,
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,
},
ModuleNotFound {
name: ModuleName,
},
ModuleIsPrivate {
name: ModuleName,
},
ArgumentMissing {
name: WitnessName,
},
ArgumentTypeMismatch {
name: WitnessName,
declared: ResolvedType,
assigned: ResolvedType,
},
}
#[rustfmt::skip]
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::UnstableFeature { feature } => write!(
f,
"The '{feature}' feature is not enabled.\nEnable it with: -Z {feature}"
),
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::MissingCrateKeyword => write!(
f,
"Imports must begin with the `{CRATE_STR}` keyword in single-file programs",
),
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!(
f,
"Item `{name}` was defined multiple times"
),
Error::UnresolvedItem { name } => write!(
f,
"Item `{name}` could not be found"
),
Error::PrivateItem { name } => write!(
f,
"Item `{name}` is private"
),
Error::InvalidNumberOfArguments { expected, found } => 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<B, A>` 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}` was defined multiple times"
),
Error::ModuleNotFound { name } => write!(
f,
"Module `{name}` not found"
),
Error::ModuleIsPrivate { name } => write!(
f,
"Module `{name}` is private",
),
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}`"
),
}
}
}
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),
_ => None,
}
}
}
impl Error {
pub fn with_span(self, span: Span) -> RichError {
RichError::new(self, span)
}
}
impl From<std::num::ParseIntError> for Error {
fn from(error: std::num::ParseIntError) -> Self {
Self::ParseInt { source: error }
}
}
impl From<crate::num::ParseIntError> for Error {
fn from(error: crate::num::ParseIntError) -> Self {
Self::ParseCrateInt { source: error }
}
}
impl From<simplicity::types::Error> for Error {
fn from(error: simplicity::types::Error) -> Self {
Self::CannotCompile { source: error }
}
}
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"let a1: List<u32, 5> = None;
let x: u32 = Left(
Right(0)
);"#;
const EMPTY_FILE: &str = "";
#[test]
fn display_single_line() {
let error = Error::ListBoundPow2 { bound: 5 }
.with_span(Span::new(13, 19))
.with_content(Arc::from(CONTENT));
let expected = r#"
|
1 | let a1: List<u32, 5> = None;
| ^^^^^^ Expected a power of two greater than one (2, 4, 8, 16, 32, ...) as list bound, found 5"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_multi_line() {
let error = Error::CannotParse {
msg: "Expected value of type `u32`, got `Either<Either<_, u32>, _>`".to_string(),
}
.with_span(Span::new(41, CONTENT.len()))
.with_content(Arc::from(CONTENT));
let expected = r#"
|
2 | let x: u32 = Left(
3 | Right(0)
4 | );
| ^^^^^^^^^^^^^^^^^^ Cannot parse: Expected value of type `u32`, got `Either<Either<_, u32>, _>`"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_entire_file() {
let error = Error::CannotParse {
msg: "This span covers the entire file".to_string(),
}
.with_span(Span::from(CONTENT))
.with_content(Arc::from(CONTENT));
let expected = r#"
|
1 | let a1: List<u32, 5> = None;
2 | let x: u32 = Left(
3 | Right(0)
4 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot parse: This span covers the entire file"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_no_file() {
let error = Error::CannotParse {
msg: "This error has no file".to_string(),
}
.with_span(Span::from(EMPTY_FILE));
let expected = "Cannot parse: This error has no file";
assert_eq!(&expected, &error.to_string());
let error = Error::CannotParse {
msg: "This error has no file".to_string(),
}
.with_span(Span::new(5, 10));
assert_eq!(&expected, &error.to_string());
}
#[test]
fn display_empty_file() {
let error = Error::CannotParse {
msg: "This error has an empty file".to_string(),
}
.with_span(Span::from(EMPTY_FILE))
.with_content(Arc::from(EMPTY_FILE));
let expected = "Cannot parse: This error has an empty file";
assert_eq!(&expected, &error.to_string());
}
#[test]
fn display_with_utf16_chars() {
let file = "/*😀*/ let a: u8 = 65536;";
let error = Error::CannotParse {
msg: "number too large to fit in target type".to_string(),
}
.with_span(Span::new(21, 26))
.with_content(Arc::from(file));
let expected = r#"
|
1 | /*😀*/ let a: u8 = 65536;
| ^^^^^ Cannot parse: number too large to fit in target type"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn multiline_display_with_utf16_chars() {
let file = r#"/*😀 this symbol should not break the rendering*/
let a: u8 = 65536;
let x: u32 = Left(
Right(0)
);"#;
let error = Error::CannotParse {
msg: "This span covers the entire file".to_string(),
}
.with_span(Span::from(file))
.with_content(Arc::from(file));
let expected = r#"
|
1 | /*😀 this symbol should not break the rendering*/
2 | let a: u8 = 65536;
3 | let x: u32 = Left(
4 | Right(0)
5 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot parse: This span covers the entire file"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_with_unicode_separator() {
let file = "let a: u8 = 65536;\u{2028}let b: u8 = 0;";
let error = Error::CannotParse {
msg: "number too large to fit in target type".to_string(),
}
.with_span(Span::new(12, 17))
.with_content(Arc::from(file));
let expected = r#"
|
1 | let a: u8 = 65536;
| ^^^^^ Cannot parse: number too large to fit in target type"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_span_as_point() {
let file = "fn main()";
let error = Error::Grammar {
msg: "Error span at (0,0)".to_string(),
}
.with_span(Span::new(0, 0))
.with_content(Arc::from(file));
let expected = r#"
|
1 | fn main()
| ^ Grammar error: Error span at (0,0)"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_span_as_point_on_trailing_empty_line() {
let file = "fn main(){\n let a:\n";
let error = Error::CannotParse {
msg: "eof".to_string(),
}
.with_span(Span::new(file.len(), file.len()))
.with_content(Arc::from(file));
let expected = r#"
|
3 |
| ^ Cannot parse: eof"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_single_line_with_file() {
let source = SourceFile::new(std::path::Path::new("src/main.simf"), Arc::from(CONTENT));
let error = Error::ListBoundPow2 { bound: 5 }
.with_span(Span::new(13, 19))
.with_source(source);
let expected = r#"
--> src/main.simf:1:14
|
1 | let a1: List<u32, 5> = None;
| ^^^^^^ Expected a power of two greater than one (2, 4, 8, 16, 32, ...) as list bound, found 5"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_multi_line_with_file() {
let source = SourceFile::new(std::path::Path::new("lib/parser.simf"), Arc::from(CONTENT));
let error = Error::CannotParse {
msg: "Expected value of type `u32`, got `Either<Either<_, u32>, _>`".to_string(),
}
.with_span(Span::new(41, CONTENT.len()))
.with_source(source);
let expected = r#"
--> lib/parser.simf:2:13
|
2 | let x: u32 = Left(
3 | Right(0)
4 | );
| ^^^^^^^^^^^^^^^^^^ Cannot parse: Expected value of type `u32`, got `Either<Either<_, u32>, _>`"#;
assert_eq!(&expected[1..], &error.to_string());
}
#[test]
fn display_entire_file_with_file() {
let source = SourceFile::new(
std::path::Path::new("tests/integration.simf"),
Arc::from(CONTENT),
);
let error = Error::CannotParse {
msg: "This span covers the entire file".to_string(),
}
.with_span(Span::from(CONTENT))
.with_source(source);
let expected = r#"
--> tests/integration.simf:1:1
|
1 | let a1: List<u32, 5> = None;
2 | let x: u32 = Left(
3 | Right(0)
4 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot parse: This span covers the entire file"#;
assert_eq!(&expected[1..], &error.to_string());
}
}