lila/src/typing/error.rs

196 lines
6.7 KiB
Rust

use ariadne::{ColorGenerator, Fmt, Label, Report, ReportKind, Span as _};
use std::fmt::Debug;
use super::{Span, UnaryOperator};
use crate::typing::{BinaryOperator, Identifier, ModulePath, Type};
#[derive(Debug, PartialEq)]
pub struct TypeError {
pub file: Option<std::path::PathBuf>,
pub module: ModulePath,
pub function: Option<String>,
pub kind: TypeErrorKind,
}
#[derive(PartialEq, Debug)]
pub struct TypeAndSpan {
pub ty: Type,
pub span: Span,
}
#[derive(PartialEq, Debug)]
pub struct BinOpAndSpan {
pub op: BinaryOperator,
pub span: Span,
}
#[derive(Debug, PartialEq)]
pub enum TypeErrorKind {
InvalidBinaryOperator {
operator: BinOpAndSpan,
lhs: TypeAndSpan,
rhs: TypeAndSpan,
},
BlockTypeDoesNotMatchFunctionType {
block_type: Type,
},
ReturnTypeDoesNotMatchFunctionType {
return_expr: Option<TypeAndSpan>,
return_stmt: TypeAndSpan,
},
UnknownIdentifier {
identifier: String,
},
AssignmentMismatch {
lht: Type,
rht: Type,
},
AssignUndeclared,
VariableRedeclaration,
UnknownFunctionCalled(Identifier),
WrongFunctionArguments,
ConditionIsNotBool,
IfElseMismatch,
InvalidUnaryOperator {
operator: UnaryOperator,
inner: Type,
},
}
impl TypeError {
pub fn to_report(&self, ast: &crate::ast::Module) -> Report<Span> {
let mut colors = ColorGenerator::new();
let c0 = colors.next();
let c1 = colors.next();
colors.next();
let c2 = colors.next();
match &self.kind {
TypeErrorKind::InvalidBinaryOperator { operator, lhs, rhs } => {
Report::build(ReportKind::Error, lhs.span.source, lhs.span.start)
.with_message(format!(
"Invalid binary operation {} between {} and {}",
operator.op.to_string().fg(c0),
lhs.ty.to_string().fg(c1),
rhs.ty.to_string().fg(c2),
))
.with_labels([
Label::new(operator.span).with_color(c0),
Label::new(lhs.span)
.with_message(format!("This has type {}", lhs.ty.to_string().fg(c1)))
.with_color(c1)
.with_order(2),
Label::new(rhs.span)
.with_message(format!("This has type {}", rhs.ty.to_string().fg(c2)))
.with_color(c2)
.with_order(1),
])
.finish()
}
TypeErrorKind::BlockTypeDoesNotMatchFunctionType { block_type } => {
let function = ast
.functions
.iter()
.find(|f| f.name == *self.function.as_ref().unwrap())
.unwrap();
let block_color = c0;
let signature_color = c1;
let span = function.body.value.as_ref().unwrap().span;
let func_return_type_text = function
.return_type
.as_ref()
.unwrap_or(&Type::Unit)
.to_string()
.fg(signature_color);
let mut report = Report::build(
ReportKind::Error,
span.source,
function.body.value.as_ref().unwrap().span.start,
)
.with_message("Function body does not match the signature")
.with_labels([
Label::new(function.body.span.unwrap())
.with_message("In this function's body")
.with_color(c2),
Label::new(span)
.with_message(format!(
"Returned expression has type {} but the function should return {}",
block_type.to_string().fg(block_color),
func_return_type_text,
))
.with_color(block_color),
]);
if let Some(span) = function.return_type_span {
report.add_label(
Label::new(span)
.with_message(format!("The signature shows {}", func_return_type_text))
.with_color(signature_color),
);
}
report.set_note("The last expression of a function's body is returned");
if function.return_type.is_none() {
report
.set_help("You may need to add the return type to the function's signature")
}
report.finish()
}
TypeErrorKind::ReturnTypeDoesNotMatchFunctionType {
return_expr,
return_stmt,
} => {
let function = ast
.functions
.iter()
.find(|f| f.name == *self.function.as_ref().unwrap())
.unwrap();
let is_bare_return = return_expr.is_none();
let mut report = Report::build(
ReportKind::Error,
*return_stmt.span.source(),
return_stmt.span.start,
)
.with_message("Return type does not match the function's signature")
.with_label(
Label::new(return_expr.as_ref().unwrap_or(return_stmt).span)
.with_color(c1)
.with_message(if is_bare_return {
format!("Bare return has type {}", Type::Unit.to_string().fg(c1))
} else {
format!(
"This expression has type {}",
return_stmt.ty.to_string().fg(c1)
)
}),
);
if let Some(ret_ty_span) = function.return_type_span {
report.add_label(Label::new(ret_ty_span).with_color(c0).with_message(format!(
"The signature shows {}",
function.return_type.as_ref().unwrap().to_string().fg(c0)
)))
}
if function.return_type.is_none() {
report
.set_help("You may need to add the return type to the function's signature")
}
report.finish()
}
_ => todo!(),
}
}
}