Add new means of reporting type check errors
This commit is contained in:
parent
d4487117eb
commit
bb53331b65
@ -1,7 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, Identifier, Map, Result, Statement, Type, TypeDefinition, Value};
|
||||
use crate::{
|
||||
AbstractTree, Error, Identifier, Map, Result, Statement, SyntaxNode, SyntaxPosition, Type,
|
||||
TypeDefinition, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Assignment {
|
||||
@ -9,6 +11,7 @@ pub struct Assignment {
|
||||
type_definition: Option<TypeDefinition>,
|
||||
operator: AssignmentOperator,
|
||||
statement: Statement,
|
||||
syntax_position: SyntaxPosition,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
@ -19,15 +22,15 @@ pub enum AssignmentOperator {
|
||||
}
|
||||
|
||||
impl AbstractTree for Assignment {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "assignment", node)?;
|
||||
fn from_syntax_node(source: &str, syntax_node: SyntaxNode, context: &Map) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "assignment", syntax_node)?;
|
||||
|
||||
let child_count = node.child_count();
|
||||
let child_count = syntax_node.child_count();
|
||||
|
||||
let identifier_node = node.child(0).unwrap();
|
||||
let identifier_node = syntax_node.child(0).unwrap();
|
||||
let identifier = Identifier::from_syntax_node(source, identifier_node, context)?;
|
||||
|
||||
let type_node = node.child(1).unwrap();
|
||||
let type_node = syntax_node.child(1).unwrap();
|
||||
let type_definition = if type_node.kind() == "type_definition" {
|
||||
Some(TypeDefinition::from_syntax_node(
|
||||
source, type_node, context,
|
||||
@ -36,7 +39,11 @@ impl AbstractTree for Assignment {
|
||||
None
|
||||
};
|
||||
|
||||
let operator_node = node.child(child_count - 2).unwrap().child(0).unwrap();
|
||||
let operator_node = syntax_node
|
||||
.child(child_count - 2)
|
||||
.unwrap()
|
||||
.child(0)
|
||||
.unwrap();
|
||||
let operator = match operator_node.kind() {
|
||||
"=" => AssignmentOperator::Equal,
|
||||
"+=" => AssignmentOperator::PlusEqual,
|
||||
@ -51,7 +58,7 @@ impl AbstractTree for Assignment {
|
||||
}
|
||||
};
|
||||
|
||||
let statement_node = node.child(child_count - 1).unwrap();
|
||||
let statement_node = syntax_node.child(child_count - 1).unwrap();
|
||||
let statement = Statement::from_syntax_node(source, statement_node, context)?;
|
||||
let statement_type = statement.expected_type(context)?;
|
||||
|
||||
@ -71,16 +78,20 @@ impl AbstractTree for Assignment {
|
||||
type_definition,
|
||||
operator,
|
||||
statement,
|
||||
syntax_position: syntax_node.range().into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn check_type(&self, context: &Map) -> Result<()> {
|
||||
fn check_type(&self, source: &str, context: &Map) -> Result<()> {
|
||||
let statement_type = self.statement.expected_type(context)?;
|
||||
|
||||
if let Some(type_definition) = &self.type_definition {
|
||||
match self.operator {
|
||||
AssignmentOperator::Equal => {
|
||||
type_definition.inner().check(&statement_type)?;
|
||||
type_definition
|
||||
.inner()
|
||||
.check(&statement_type)
|
||||
.map_err(|error| error.at_source_position(source, self.syntax_position))?;
|
||||
}
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Type::List(item_type) = type_definition.inner() {
|
||||
@ -88,7 +99,10 @@ impl AbstractTree for Assignment {
|
||||
} else {
|
||||
type_definition
|
||||
.inner()
|
||||
.check(&self.identifier.expected_type(context)?)?;
|
||||
.check(&self.identifier.expected_type(context)?)
|
||||
.map_err(|error| {
|
||||
error.at_source_position(source, self.syntax_position)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
AssignmentOperator::MinusEqual => todo!(),
|
||||
@ -98,14 +112,16 @@ impl AbstractTree for Assignment {
|
||||
AssignmentOperator::Equal => {}
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Type::List(item_type) = self.identifier.expected_type(context)? {
|
||||
item_type.check(&statement_type)?;
|
||||
item_type.check(&statement_type).map_err(|error| {
|
||||
error.at_source_position(source, self.syntax_position)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
AssignmentOperator::MinusEqual => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
self.statement.check_type(context)?;
|
||||
self.statement.check_type(source, context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -50,12 +50,12 @@ impl AbstractTree for Block {
|
||||
})
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.check_type(_context);
|
||||
return inner_statement.check_type(_source, _context);
|
||||
} else {
|
||||
statement.check_type(_context)?;
|
||||
statement.check_type(_source, _context)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,15 +65,15 @@ impl AbstractTree for Expression {
|
||||
Ok(expression)
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
|
||||
match self {
|
||||
Expression::Value(value_node) => value_node.check_type(_context),
|
||||
Expression::Identifier(identifier) => identifier.check_type(_context),
|
||||
Expression::Math(math) => math.check_type(_context),
|
||||
Expression::Logic(logic) => logic.check_type(_context),
|
||||
Expression::FunctionCall(function_call) => function_call.check_type(_context),
|
||||
Expression::Index(index) => index.check_type(_context),
|
||||
Expression::Yield(r#yield) => r#yield.check_type(_context),
|
||||
Expression::Value(value_node) => value_node.check_type(_source, _context),
|
||||
Expression::Identifier(identifier) => identifier.check_type(_source, _context),
|
||||
Expression::Math(math) => math.check_type(_source, _context),
|
||||
Expression::Logic(logic) => logic.check_type(_source, _context),
|
||||
Expression::FunctionCall(function_call) => function_call.check_type(_source, _context),
|
||||
Expression::Index(index) => index.check_type(_source, _context),
|
||||
Expression::Yield(r#yield) => r#yield.check_type(_source, _context),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ impl AbstractTree for FunctionCall {
|
||||
})
|
||||
}
|
||||
|
||||
fn check_type(&self, context: &Map) -> Result<()> {
|
||||
fn check_type(&self, _source: &str, context: &Map) -> Result<()> {
|
||||
let function_expression_type = self.function_expression.expected_type(context)?;
|
||||
|
||||
let parameter_types = match function_expression_type {
|
||||
|
@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{
|
||||
AbstractTree, Block, Error, Function, Identifier, Map, Result, Type, TypeDefinition, Value,
|
||||
AbstractTree, Block, Error, Function, Identifier, Map, Result, SyntaxPosition, Type,
|
||||
TypeDefinition, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@ -10,14 +11,21 @@ pub struct FunctionNode {
|
||||
parameters: Vec<Identifier>,
|
||||
body: Block,
|
||||
r#type: Type,
|
||||
syntax_position: SyntaxPosition,
|
||||
}
|
||||
|
||||
impl FunctionNode {
|
||||
pub fn new(parameters: Vec<Identifier>, body: Block, r#type: Type) -> Self {
|
||||
pub fn new(
|
||||
parameters: Vec<Identifier>,
|
||||
body: Block,
|
||||
r#type: Type,
|
||||
syntax_position: SyntaxPosition,
|
||||
) -> Self {
|
||||
Self {
|
||||
parameters,
|
||||
body,
|
||||
r#type,
|
||||
syntax_position,
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,13 +122,15 @@ impl AbstractTree for FunctionNode {
|
||||
let body = Block::from_syntax_node(source, body_node, &function_context)?;
|
||||
|
||||
let r#type = Type::function(parameter_types, return_type.take_inner());
|
||||
let syntax_position = node.range().into();
|
||||
|
||||
Ok(FunctionNode::new(parameters, body, r#type))
|
||||
Ok(FunctionNode::new(parameters, body, r#type, syntax_position))
|
||||
}
|
||||
|
||||
fn check_type(&self, context: &Map) -> Result<()> {
|
||||
fn check_type(&self, source: &str, context: &Map) -> Result<()> {
|
||||
self.return_type()
|
||||
.check(&self.body.expected_type(context)?)?;
|
||||
.check(&self.body.expected_type(context)?)
|
||||
.map_err(|error| error.at_source_position(source, self.syntax_position))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -35,10 +35,34 @@ pub use {
|
||||
r#match::*, r#while::*, r#yield::*, statement::*, type_definition::*, value_node::*,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{Error, Map, Result, Value};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct SyntaxPosition {
|
||||
pub start_byte: usize,
|
||||
pub end_byte: usize,
|
||||
pub start_row: usize,
|
||||
pub start_column: usize,
|
||||
pub end_row: usize,
|
||||
pub end_column: usize,
|
||||
}
|
||||
|
||||
impl From<tree_sitter::Range> for SyntaxPosition {
|
||||
fn from(range: tree_sitter::Range) -> Self {
|
||||
SyntaxPosition {
|
||||
start_byte: range.start_byte,
|
||||
end_byte: range.end_byte,
|
||||
start_row: range.start_point.row,
|
||||
start_column: range.start_point.column,
|
||||
end_row: range.end_point.row,
|
||||
end_column: range.end_point.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Root {
|
||||
statements: Vec<Statement>,
|
||||
}
|
||||
@ -60,12 +84,12 @@ impl AbstractTree for Root {
|
||||
Ok(Root { statements })
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.check_type(_context);
|
||||
return inner_statement.check_type(_source, _context);
|
||||
} else {
|
||||
statement.check_type(_context)?;
|
||||
statement.check_type(_source, _context)?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +130,7 @@ pub trait AbstractTree: Sized {
|
||||
fn from_syntax_node(source: &str, node: Node, context: &Map) -> Result<Self>;
|
||||
|
||||
/// Verify the type integrity of the node.
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -66,17 +66,19 @@ impl AbstractTree for Statement {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
|
||||
match self {
|
||||
Statement::Assignment(assignment) => assignment.check_type(_context),
|
||||
Statement::Expression(expression) => expression.check_type(_context),
|
||||
Statement::IfElse(if_else) => if_else.check_type(_context),
|
||||
Statement::Match(r#match) => r#match.check_type(_context),
|
||||
Statement::While(r#while) => r#while.check_type(_context),
|
||||
Statement::Block(block) => block.check_type(_context),
|
||||
Statement::For(r#for) => r#for.check_type(_context),
|
||||
Statement::IndexAssignment(index_assignment) => index_assignment.check_type(_context),
|
||||
Statement::Return(statement) => statement.check_type(_context),
|
||||
Statement::Assignment(assignment) => assignment.check_type(_source, _context),
|
||||
Statement::Expression(expression) => expression.check_type(_source, _context),
|
||||
Statement::IfElse(if_else) => if_else.check_type(_source, _context),
|
||||
Statement::Match(r#match) => r#match.check_type(_source, _context),
|
||||
Statement::While(r#while) => r#while.check_type(_source, _context),
|
||||
Statement::Block(block) => block.check_type(_source, _context),
|
||||
Statement::For(r#for) => r#for.check_type(_source, _context),
|
||||
Statement::IndexAssignment(index_assignment) => {
|
||||
index_assignment.check_type(_source, _context)
|
||||
}
|
||||
Statement::Return(statement) => statement.check_type(_source, _context),
|
||||
}
|
||||
}
|
||||
|
||||
|
39
src/error.rs
39
src/error.rs
@ -6,7 +6,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::{LanguageError, Node, Point};
|
||||
|
||||
use crate::{value::Value, Type};
|
||||
use crate::{value::Value, SyntaxPosition, Type};
|
||||
|
||||
use std::{
|
||||
fmt::{self, Formatter},
|
||||
@ -21,11 +21,13 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Error {
|
||||
WithContext {
|
||||
AtSourcePosition {
|
||||
error: Box<Error>,
|
||||
#[serde(skip)]
|
||||
location: Point,
|
||||
source: String,
|
||||
start_row: usize,
|
||||
start_column: usize,
|
||||
end_row: usize,
|
||||
end_column: usize,
|
||||
},
|
||||
|
||||
UnexpectedSyntaxNode {
|
||||
@ -181,11 +183,16 @@ pub enum Error {
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn at_node(self, node: Node, source: &str) -> Self {
|
||||
Error::WithContext {
|
||||
pub fn at_source_position(self, source: &str, position: SyntaxPosition) -> Self {
|
||||
let byte_range = position.start_byte..position.end_byte;
|
||||
|
||||
Error::AtSourcePosition {
|
||||
error: Box::new(self),
|
||||
location: node.start_position(),
|
||||
source: source[node.byte_range()].to_string(),
|
||||
source: source[byte_range].to_string(),
|
||||
start_row: position.start_row,
|
||||
start_column: position.start_column,
|
||||
end_row: position.end_row,
|
||||
end_column: position.end_column,
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +232,7 @@ impl Error {
|
||||
|
||||
pub fn is_type_check_error(&self, other: &Error) -> bool {
|
||||
match self {
|
||||
Error::WithContext { error, .. } => error.as_ref() == other,
|
||||
Error::AtSourcePosition { error, .. } => error.as_ref() == other,
|
||||
_ => self == other,
|
||||
}
|
||||
}
|
||||
@ -427,14 +434,18 @@ impl fmt::Display for Error {
|
||||
TypeCheckExpectedFunction { actual } => {
|
||||
write!(f, "Type check error. Expected a function but got {actual}.")
|
||||
}
|
||||
WithContext {
|
||||
AtSourcePosition {
|
||||
error,
|
||||
location,
|
||||
source,
|
||||
start_row,
|
||||
start_column,
|
||||
end_row,
|
||||
end_column,
|
||||
} => {
|
||||
let location = get_position(location);
|
||||
|
||||
write!(f, "{error} Occured at {location}: \"{source}\"")
|
||||
write!(
|
||||
f,
|
||||
"{error} Occured at ({start_row}, {start_column}) to ({end_row}, {end_column}). Source: {source}"
|
||||
)
|
||||
}
|
||||
SerdeJson(message) => write!(f, "JSON processing error: {message}"),
|
||||
ParserCancelled => write!(
|
||||
|
@ -115,7 +115,7 @@ impl Interpreter {
|
||||
};
|
||||
|
||||
if let Some(abstract_tree) = &self.abstract_tree {
|
||||
abstract_tree.check_type(&self.context)?;
|
||||
abstract_tree.check_type(source, &self.context)?;
|
||||
abstract_tree.run(source, &self.context)
|
||||
} else {
|
||||
Ok(Value::none())
|
||||
|
@ -11,6 +11,8 @@ pub use crate::{
|
||||
value::{function::Function, list::List, map::Map, Value},
|
||||
};
|
||||
|
||||
pub use tree_sitter::Node as SyntaxNode;
|
||||
|
||||
mod abstract_tree;
|
||||
pub mod built_in_functions;
|
||||
mod error;
|
||||
|
@ -57,10 +57,12 @@ impl AbstractTree for Function {
|
||||
Ok(Function::ContextDefined(inner_function))
|
||||
}
|
||||
|
||||
fn check_type(&self, _context: &Map) -> Result<()> {
|
||||
fn check_type(&self, _source: &str, _context: &Map) -> Result<()> {
|
||||
match self {
|
||||
Function::BuiltIn(_) => Ok(()),
|
||||
Function::ContextDefined(defined_function) => defined_function.check_type(_context),
|
||||
Function::ContextDefined(defined_function) => {
|
||||
defined_function.check_type(_source, _context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user