add pretty diagnostics
This commit is contained in:
parent
e157bf036a
commit
f415c4abbe
12 changed files with 1037 additions and 603 deletions
|
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue