use std::fmt::Display;
use crate::{
ast::{Expr, Identifier},
parser::meta::{Meta, MetaId},
typechecker::scope::Declaration,
};
use super::{
ResolvedPath, TypeChecker,
scope::{DeclarationKind, ValueKind},
scoped_display::TypeDisplay,
types::Type,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Level {
Error,
Info,
}
#[derive(Clone, Debug)]
pub struct Label {
pub level: Level,
pub id: MetaId,
pub message: String,
}
impl Label {
fn error(msg: impl Display, id: MetaId) -> Self {
Label {
level: Level::Error,
id,
message: msg.to_string(),
}
}
fn info(msg: impl Display, id: MetaId) -> Self {
Label {
level: Level::Info,
id,
message: msg.to_string(),
}
}
}
#[derive(Clone, Debug)]
pub struct TypeError {
pub description: String,
pub location: MetaId,
pub labels: Vec<Label>,
}
impl TypeChecker {
pub fn error_simple(
&self,
description: impl Display,
msg: impl Display,
span: MetaId,
) -> TypeError {
TypeError {
description: description.to_string(),
location: span,
labels: vec![Label::error(msg, span)],
}
}
pub fn error_duplicate_fields(
&self,
field_name: &str,
locations: &[MetaId],
) -> TypeError {
TypeError {
description: format!(
"field `{field_name}` appears multiple times in the same record"
),
location: locations[0],
labels: locations
.iter()
.map(|&span| {
Label::error(
format!("field `{field_name}` declared here"),
span,
)
})
.collect(),
}
}
pub fn error_field_mismatch<'a>(
&self,
span: MetaId,
invalid: impl IntoIterator<Item = &'a Meta<Identifier>>,
duplicate: impl IntoIterator<Item = &'a Meta<Identifier>>,
missing: impl IntoIterator<Item = Identifier>,
) -> TypeError {
let missing: Vec<_> =
missing.into_iter().map(|m| m.as_str()).collect();
let description = if missing.len() > 1 {
let fields = join_quoted(missing);
format!(
"field mismatch: missing fields {fields} in record literal"
)
} else if let [m] = &missing[..] {
format!(
"field: mismatch: missing field `{}` in record literal",
m
)
} else {
"field mismatch".into()
};
let mut labels = vec![Label::error(&description, span)];
for field in invalid {
labels.push(Label::info("invalid field", field.id));
}
for field in duplicate {
labels.push(Label::info("duplicate field", field.id));
}
TypeError {
description,
location: span,
labels,
}
}
pub fn error_expected_type(
&self,
ident: &Meta<Identifier>,
stub: Declaration,
) -> TypeError {
let kind = describe_declaration(&stub);
TypeError {
description: format!("expected type, but found {kind} `{ident}`",),
location: ident.id,
labels: vec![Label::error("expected type", ident.id)],
}
}
pub fn error_expected_module(
&self,
ident: &Meta<Identifier>,
declaration: Declaration,
) -> TypeError {
let kind = describe_declaration(&declaration);
TypeError {
description: format!(
"expected a module, but found {kind} `{ident}`",
),
location: ident.id,
labels: vec![Label::error(
format!("`{ident}` is a {kind}, not a module"),
ident.id,
)],
}
}
pub fn error_declared_twice(
&self,
new_declaration: &Meta<Identifier>,
old_declaration: MetaId,
) -> TypeError {
let (item_type, name) = if let Some(rest) =
new_declaration.as_str().strip_prefix("test#")
{
("test", rest)
} else {
("item", new_declaration.as_str())
};
TypeError {
description: format!(
"{item_type} `{name}` is declared multiple times"
),
location: new_declaration.id,
labels: vec![
Label::error(
format!("`{name}` redefined here"),
new_declaration.id,
),
Label::info(
format!("`{name}` previously declared here"),
old_declaration,
),
],
}
}
pub fn error_not_defined(&self, ident: &Meta<Identifier>) -> TypeError {
TypeError {
description: format!("cannot find value `{ident}` in this scope"),
location: ident.id,
labels: vec![Label::error("not found in this scope", ident.id)],
}
}
pub fn error_number_of_arguments_dont_match(
&self,
call_type: &str,
method_name: &Meta<Identifier>,
takes: usize,
given: usize,
) -> TypeError {
TypeError {
description: format!(
"{call_type} `{method_name}` takes {takes} arguments but {given} arguments were given"
),
location: method_name.id,
labels: vec![Label::error(
format!(
"takes {takes} arguments but {given} arguments were given"
),
method_name.id,
)],
}
}
pub fn error_can_only_match_on_enum(
&self,
ty: &Type,
span: MetaId,
) -> TypeError {
TypeError {
description: format!(
"cannot match on the type `{}`, \
because only matching on enums is supported.",
ty.display(&self.type_info)
),
location: span,
labels: vec![Label::error(
format!(
"cannot match on type `{}`",
ty.display(&self.type_info)
),
span,
)],
}
}
pub fn error_variant_does_not_have_fields(
&self,
variant: &Meta<Identifier>,
ty: &Type,
) -> TypeError {
TypeError {
description: format!(
"pattern has fields, but the variant `{variant}` of `{}` doesn't have one",
ty.display(&self.type_info)
),
location: variant.id,
labels: vec![Label::error("unexpected data field", variant.id)],
}
}
pub fn error_need_arguments_on_pattern(
&self,
variant: &Meta<Identifier>,
ty: &Type,
) -> TypeError {
TypeError {
description: format!(
"pattern has no arguments, but variant `{variant}` of `{}` does have arguments",
ty.display(&self.type_info)
),
location: variant.id,
labels: vec![Label::error("missing arguments", variant.id)],
}
}
pub fn error_variant_does_not_exist(
&self,
variant: &Meta<Identifier>,
ty: &Type,
) -> TypeError {
TypeError {
description: format!(
"the variant `{variant}` does not exist on `{}`",
ty.display(&self.type_info),
),
location: variant.id,
labels: vec![Label::error(
format!(
"variant does not exist on `{}`",
ty.display(&self.type_info),
),
variant.id,
)],
}
}
pub fn error_mismatched_types(
&self,
expected: &Type,
got: &Type,
span: MetaId,
cause: Option<MetaId>,
) -> TypeError {
let type_info = &self.type_info;
let mut labels = vec![Label::error(
format!(
"expected `{}`, found `{}`",
expected.display(type_info),
got.display(type_info),
),
span,
)];
if let Some(span) = cause {
labels.push(Label::info(
format!(
"expected because this is `{}`",
expected.display(type_info)
),
span,
));
}
TypeError {
description: "mismatched types".into(),
location: span,
labels,
}
}
pub fn error_nonexhaustive_match(
&self,
span: MetaId,
missing_variants: &[Identifier],
) -> TypeError {
let missing_variants: Vec<_> =
missing_variants.iter().map(|s| s.as_str()).collect();
TypeError {
description: format!(
"match expression is not exhaustive, missing variants {}",
join_quoted(&missing_variants)
),
location: span,
labels: vec![Label::error(
format!("missing variants {}", join_quoted(missing_variants)),
span,
)],
}
}
pub fn error_unreachable_expression<T>(
&self,
expr: &Meta<T>,
) -> TypeError {
TypeError {
description: "expression is unreachable".into(),
location: expr.id,
labels: vec![Label::error("unreachable", expr.id)],
}
}
pub fn error_cannot_diverge_here(
&self,
divergence_type: &str,
expr: &Meta<Expr>,
) -> TypeError {
TypeError {
description: format!("cannot `{divergence_type}` here"),
location: expr.id,
labels: vec![Label::error("not allowed", expr.id)],
}
}
pub fn error_expected_value(
&self,
ident: &Meta<Identifier>,
declaration: &Declaration,
) -> TypeError {
let kind = describe_declaration(declaration);
TypeError {
description: format!(
"expected a value, but found {kind} `{}`",
declaration.name.ident
),
location: ident.id,
labels: vec![
Label::error("expected a value", ident.id),
Label::info(
format!(
"{kind} `{}` defined here",
declaration.name.ident
),
declaration.id,
),
],
}
}
pub fn error_expected_numeric_value(
&self,
expr: &Meta<Expr>,
ty: &Type,
) -> TypeError {
TypeError {
description: format!(
"expected a numeric value, found type `{}`",
ty.display(&self.type_info),
),
location: expr.id,
labels: vec![Label::error("not a numeric value", expr.id)],
}
}
pub fn error_expected_value_path(
&self,
ident: &Meta<Identifier>,
path: &ResolvedPath,
) -> TypeError {
let (name, kind) = describe_path(path);
TypeError {
description: format!(
"expected a value, but found {kind} `{}`",
name
),
location: ident.id,
labels: vec![Label::error("expected a value", ident.id)],
}
}
pub fn error_expected_function(
&self,
ident: &Meta<Identifier>,
resolved_path: &ResolvedPath,
) -> TypeError {
let (kind, name) = describe_path(resolved_path);
TypeError {
description: format!(
"expected a function, but found {kind} `{name}`",
),
location: ident.id,
labels: vec![Label::error("expected a function", ident.id)],
}
}
pub fn error_no_field_on_type(
&self,
ty: &impl TypeDisplay,
ident: &Meta<Identifier>,
) -> TypeError {
self.error_simple(
format!(
"no field `{ident}` on type `{}`",
ty.display(&self.type_info)
),
format!("unknown field `{ident}`"),
ident.id,
)
}
pub fn error_no_method_on_type(
&self,
ty: &impl TypeDisplay,
ident: &Meta<Identifier>,
) -> TypeError {
self.error_simple(
format!(
"no method `{ident}` on type `{}`",
ty.display(&self.type_info)
),
format!("unknown method `{ident}`"),
ident.id,
)
}
pub fn error_no_field_or_method_on_type(
&self,
ty: &impl TypeDisplay,
ident: &Meta<Identifier>,
) -> TypeError {
self.error_simple(
format!(
"no field or method `{ident}` on type `{}`",
ty.display(&self.type_info)
),
format!("unknown field or method `{ident}`"),
ident.id,
)
}
}
fn describe_declaration(d: &Declaration) -> &str {
match &d.kind {
DeclarationKind::Value(ValueKind::Local, _) => "variable",
DeclarationKind::Value(ValueKind::Constant, _) => "constant",
DeclarationKind::Value(ValueKind::Context(..), _) => "context",
DeclarationKind::Type(..) => "type",
DeclarationKind::Function(..) => "function",
DeclarationKind::Module => "module",
DeclarationKind::Method(..) => "method",
DeclarationKind::Variant(..) => "variant",
DeclarationKind::TypeParam(..) => "type parameter",
}
}
fn describe_path(p: &ResolvedPath) -> (&str, Identifier) {
match p {
ResolvedPath::Function { name, .. } => ("function", name.ident),
ResolvedPath::Method { name, .. } => ("method", name.ident),
ResolvedPath::Value(path_value) => {
if let Some(f) = path_value.fields.last() {
("field", f.0)
} else {
("value", path_value.name.ident)
}
}
ResolvedPath::StaticMethod { name, .. } => {
("static method", name.ident)
}
ResolvedPath::EnumConstructor { variant, .. } => {
("enum constructor", variant.name)
}
}
}
fn join_quoted<T: std::fmt::Display>(
list: impl IntoIterator<Item = T>,
) -> String {
let mut list: Vec<_> =
list.into_iter().map(|s| format!("`{s}`")).collect();
let last = list.pop().unwrap();
if list.is_empty() {
return last;
}
format!("{} and {last}", list.join(", "))
}