diff --git a/compiler/src/constraints/expression.rs b/compiler/src/constraints/expression.rs index 05e5b6805c..564660ec70 100644 --- a/compiler/src/constraints/expression.rs +++ b/compiler/src/constraints/expression.rs @@ -47,7 +47,7 @@ impl> ConstrainedProgram { // Check global scope (function and circuit names) value.clone() } else { - return Err(ExpressionError::UndefinedIdentifier(unresolved_identifier.to_string())); + return Err(ExpressionError::undefined_identifier(unresolved_identifier)); }; result_value.resolve_type(expected_types)?; diff --git a/compiler/src/errors/constraints/expression.rs b/compiler/src/errors/constraints/expression.rs index 671eb21839..b9a3ccbab0 100644 --- a/compiler/src/errors/constraints/expression.rs +++ b/compiler/src/errors/constraints/expression.rs @@ -1,14 +1,13 @@ -use crate::errors::{BooleanError, FieldError, FunctionError, GroupError, ValueError}; -use leo_types::IntegerError; +use crate::errors::{BooleanError, Error, FieldError, FunctionError, GroupError, ValueError}; +use leo_types::{Identifier, IntegerError, Span}; use snarkos_errors::gadgets::SynthesisError; use std::num::ParseIntError; #[derive(Debug, Error)] pub enum ExpressionError { - // Identifiers - #[error("Identifier \"{}\" not found", _0)] - UndefinedIdentifier(String), + #[error("{}", _0)] + Error(#[from] Error), // Types #[error("{}", _0)] @@ -87,3 +86,15 @@ pub enum ExpressionError { #[error("{}", _0)] SynthesisError(#[from] SynthesisError), } + +impl ExpressionError { + fn new_from_span(message: String, span: Span) -> Self { + ExpressionError::Error(Error::new_from_span(message, span)) + } + + pub fn undefined_identifier(identifier: Identifier) -> Self { + let message = format!("cannot find value `{}` in this scope", identifier.name); + + Self::new_from_span(message, identifier.span) + } +} diff --git a/compiler/src/errors/error.rs b/compiler/src/errors/error.rs index 91a623ebbd..5d246501be 100644 --- a/compiler/src/errors/error.rs +++ b/compiler/src/errors/error.rs @@ -86,6 +86,12 @@ impl fmt::Display for Error { } } +impl std::error::Error for Error { + fn description(&self) -> &str { + &self.message + } +} + #[test] fn test_error() { let err = Error { @@ -110,26 +116,3 @@ fn test_error() { .join("\n") ); } - -#[test] -fn test_from_span() { - use pest::Span as AstSpan; - - let text = "aaaa"; - let ast_span = AstSpan::new(text, 0, text.len()).unwrap(); - let span = Span::from(ast_span); - let err = Error::new_from_span("test message".to_string(), span); - - assert_eq!( - format!("{}", err), - vec![ - " --> 1:0", - " |", - " 1 | aaaa", - " | ^^^^", - " |", - " = test message", - ] - .join("\n") - ); -} diff --git a/compiler/tests/syntax/mod.rs b/compiler/tests/syntax/mod.rs index d583cd7bb5..c434690bb5 100644 --- a/compiler/tests/syntax/mod.rs +++ b/compiler/tests/syntax/mod.rs @@ -1,6 +1,6 @@ use crate::{get_error, parse_program}; use leo_ast::ParserError; -use leo_compiler::errors::CompilerError; +use leo_compiler::errors::{CompilerError, ExpressionError, FunctionError, StatementError}; use leo_inputs::InputParserError; #[test] @@ -21,7 +21,25 @@ fn test_undefined() { let error = get_error(program); - println!("{}", error); + match error { + CompilerError::FunctionError(FunctionError::StatementError(StatementError::ExpressionError( + ExpressionError::Error(error), + ))) => { + assert_eq!( + format!("{}", error), + vec![ + " --> 2:10", + " |", + " 2 | return a", + " | ^", + " |", + " = cannot find value `a` in this scope", + ] + .join("\n") + ); + } + _ => panic!("expected an undefined identifier error"), + } } // #[test] diff --git a/types/src/common/identifier.rs b/types/src/common/identifier.rs index 6247d026a5..62ece81746 100644 --- a/types/src/common/identifier.rs +++ b/types/src/common/identifier.rs @@ -1,3 +1,4 @@ +use crate::Span; use leo_ast::common::Identifier as AstIdentifier; use std::fmt; @@ -6,6 +7,7 @@ use std::fmt; #[derive(Clone, PartialEq, Eq, Hash)] pub struct Identifier { pub name: String, + pub span: Span, } impl Identifier { @@ -16,7 +18,10 @@ impl Identifier { impl<'ast> From> for Identifier { fn from(identifier: AstIdentifier<'ast>) -> Self { - Self { name: identifier.value } + Self { + name: identifier.value, + span: Span::from(identifier.span), + } } } diff --git a/types/src/common/span.rs b/types/src/common/span.rs index 78f565939f..95a762aec3 100644 --- a/types/src/common/span.rs +++ b/types/src/common/span.rs @@ -1,4 +1,4 @@ -use pest::Span as AstSpan; +use pest::{Position, Span as AstSpan}; #[derive(Clone, PartialEq, Eq, Hash)] pub struct Span { @@ -14,13 +14,50 @@ pub struct Span { impl<'ast> From> for Span { fn from(span: AstSpan<'ast>) -> Self { - let line_col = span.start_pos().line_col(); - + let mut text = " ".to_string(); + text.push_str(span.start_pos().line_of().trim_end()); Self { - text: span.as_str().to_string(), - line: line_col.0, - start: span.start(), - end: span.end(), + text, + line: span.start_pos().line_col().0, + start: find_line_start(&span.start_pos()), + end: find_line_end(&span.end_pos()), + } + } +} + +pub fn find_line_start(pos: &Position) -> usize { + let input = pos.line_of(); + if input.is_empty() { + return 0; + }; + + // Position's pos is always a UTF-8 border. + let start = input + .char_indices() + .rev() + .skip_while(|&(i, _)| i >= pos.pos()) + .find(|&(_, c)| c == '\n'); + match start { + Some((i, _)) => i, + None => 0, + } +} + +pub fn find_line_end(pos: &Position) -> usize { + let input = pos.line_of(); + if input.is_empty() { + 0 + } else if pos.pos() == input.len() - 1 { + input.len() + } else { + // Position's pos is always a UTF-8 border. + let end = input + .char_indices() + .skip_while(|&(i, _)| i < pos.pos()) + .find(|&(_, c)| c == '\n'); + match end { + Some((i, _)) => i, + None => input.len(), } } }