add pretty diagnostics

This commit is contained in:
Romain Paquet 2024-07-03 19:59:12 +02:00
parent e157bf036a
commit f415c4abbe
12 changed files with 1037 additions and 603 deletions

View file

@ -1,8 +1,10 @@
use crate::typing::{BinaryOperator, Identifier, ModulePath, Type, TypingContext};
use ariadne::{ColorGenerator, Fmt, Label, Report, ReportKind, Span as _};
use std::fmt::Debug;
use super::UnaryOperator;
use super::{Span, UnaryOperator};
use crate::typing::{BinaryOperator, Identifier, ModulePath, Type};
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub struct TypeError {
pub file: Option<std::path::PathBuf>,
pub module: ModulePath,
@ -10,72 +12,31 @@ pub struct TypeError {
pub kind: TypeErrorKind,
}
impl std::fmt::Display for TypeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Error\n")?;
if let Some(path) = &self.file {
f.write_fmt(format_args!(" in file {}\n", path.display()))?;
}
f.write_fmt(format_args!(" in module {}\n", self.module))?;
if let Some(name) = &self.function {
f.write_fmt(format_args!(" in function {}\n", name))?;
}
f.write_fmt(format_args!("{:#?}", self.kind))?;
Ok(())
}
#[derive(PartialEq, Debug)]
pub struct TypeAndSpan {
pub ty: Type,
pub span: Span,
}
#[derive(Default)]
pub struct TypeErrorBuilder {
file: Option<std::path::PathBuf>,
module: Option<ModulePath>,
function: Option<String>,
kind: Option<TypeErrorKind>,
}
impl TypeError {
pub fn builder() -> TypeErrorBuilder {
TypeErrorBuilder::default()
}
}
impl TypeErrorBuilder {
pub fn context(mut self, ctx: &TypingContext) -> Self {
self.file = ctx.file.clone();
self.module = Some(ctx.module.clone());
self.function = ctx.function.clone();
self
}
pub fn kind(mut self, kind: TypeErrorKind) -> Self {
self.kind = Some(kind);
self
}
pub fn build(self) -> TypeError {
TypeError {
file: self.file,
module: self.module.expect("TypeError builder is missing module"),
function: self.function,
kind: self.kind.expect("TypeError builder is missing kind"),
}
}
#[derive(PartialEq, Debug)]
pub struct BinOpAndSpan {
pub op: BinaryOperator,
pub span: Span,
}
#[derive(Debug, PartialEq)]
pub enum TypeErrorKind {
InvalidBinaryOperator {
operator: BinaryOperator,
lht: Type,
rht: Type,
operator: BinOpAndSpan,
lhs: TypeAndSpan,
rhs: TypeAndSpan,
},
BlockTypeDoesNotMatchFunctionType {
block_type: Type,
function_type: Type,
},
ReturnTypeDoesNotMatchFunctionType {
function_type: Type,
return_type: Type,
return_expr: Option<TypeAndSpan>,
return_stmt: TypeAndSpan,
},
UnknownIdentifier {
identifier: String,
@ -86,7 +47,6 @@ pub enum TypeErrorKind {
},
AssignUndeclared,
VariableRedeclaration,
ReturnStatementsMismatch,
UnknownFunctionCalled(Identifier),
WrongFunctionArguments,
ConditionIsNotBool,
@ -96,3 +56,132 @@ pub enum TypeErrorKind {
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, 0u32, 0)
.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 report = Report::build(ReportKind::Error, 0u32, 0)
.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),
function
.return_type
.as_ref()
.unwrap_or(&Type::Unit)
.to_string()
.fg(signature_color)
))
.with_color(block_color),
]);
let report =
report.with_note("The last expression of a function's body is returned");
let report = if function.return_type.is_none() {
report.with_help(
"You may need to add the return type to the function's signature",
)
} else {
report
};
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 report = Report::build(ReportKind::Error, *return_stmt.span.source(), 0)
.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)
)
}),
);
let report = if let Some(ret_ty_span) = function.return_type_span {
report.with_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)
)))
} else {
report
};
let report = if function.return_type.is_none() {
report.with_help(
"You may need to add the return type to the function's signature",
)
} else {
report
};
report.finish()
}
_ => todo!(),
}
}
}