Improve type mismatch messages

This commit is contained in:
Shunsuke Shibayama 2022-11-25 17:20:39 +09:00
parent 14bb674068
commit d26909c317
7 changed files with 50 additions and 27 deletions

View file

@ -874,7 +874,7 @@ impl Context {
&proj, &proj,
t_loc, t_loc,
self.caused_by(), self.caused_by(),
self.get_no_candidate_hint(&proj), Self::get_no_candidate_hint(&proj),
))) )))
} }
} }
@ -1169,7 +1169,7 @@ impl Context {
&proj, &proj,
t_loc, t_loc,
self.caused_by(), self.caused_by(),
self.get_no_candidate_hint(&proj), Self::get_no_candidate_hint(&proj),
))) )))
} }
} }

View file

@ -1,7 +1,7 @@
use erg_common::enum_unwrap; use erg_common::enum_unwrap;
use crate::ty::typaram::TyParam; use crate::ty::typaram::TyParam;
use crate::ty::Type; use crate::ty::{HasType, Type};
use crate::context::Context; use crate::context::Context;
@ -11,8 +11,9 @@ enum Sequence {
Backward, Backward,
} }
// TODO: these should not be in Context
impl Context { impl Context {
fn readable_type(&self, typ: &Type) -> Type { fn readable_type(typ: &Type) -> Type {
match typ { match typ {
Type::FreeVar(fv) if fv.constraint_is_sandwiched() => { Type::FreeVar(fv) if fv.constraint_is_sandwiched() => {
let (sub, sup) = fv.get_subsup().unwrap(); let (sub, sup) = fv.get_subsup().unwrap();
@ -25,7 +26,20 @@ impl Context {
} }
} }
pub(crate) fn get_type_mismatch_hint(&self, expected: &Type, found: &Type) -> Option<String> { /// TODO: custom types
fn get_verb_and_preposition(trait_: &Type) -> Option<(&str, &str, Sequence)> {
match &trait_.qual_name()[..] {
"Add" => Some(("add", "and", Sequence::Forward)),
"Sub" => Some(("subtract", "from", Sequence::Backward)),
"Mul" => Some(("multiply", "and", Sequence::Forward)),
"Div" => Some(("divide", "by", Sequence::Forward)),
"Eq" => Some(("compare", "and", Sequence::Forward)),
"Ord" => Some(("compare", "and", Sequence::Forward)),
_ => None,
}
}
pub(crate) fn get_type_mismatch_hint(expected: &Type, found: &Type) -> Option<String> {
let expected = if let Type::FreeVar(fv) = expected { let expected = if let Type::FreeVar(fv) = expected {
if fv.is_linked() { if fv.is_linked() {
fv.crack().clone() fv.crack().clone()
@ -38,27 +52,26 @@ impl Context {
}; };
match (&expected.qual_name()[..], &found.qual_name()[..]) { match (&expected.qual_name()[..], &found.qual_name()[..]) {
("Eq", "Float") => Some(String::from("Float has no equivalence relation defined. you should use `l - r <= Float.EPSILON` instead of `l == r`.")), ("Eq", "Float") => Some(String::from("Float has no equivalence relation defined. you should use `l - r <= Float.EPSILON` instead of `l == r`.")),
_ => None, _ => {
let (verb, preposition, _sequence) = Self::get_verb_and_preposition(&expected)?;
found.union_types()
.map(|(t1, t2)| format!("cannot {verb} {t1} {preposition} {t2}"))
.or_else(|| {
let ts = expected.inner_ts();
Some(format!("cannot {verb} {found} {preposition} {}", ts[0]))
})
}
} }
} }
pub(crate) fn get_no_candidate_hint(&self, proj: &Type) -> Option<String> { pub(crate) fn get_no_candidate_hint(proj: &Type) -> Option<String> {
match proj { match proj {
Type::Proj { lhs, rhs: _ } => { Type::Proj { lhs, rhs: _ } => {
if let Type::FreeVar(fv) = lhs.as_ref() { if let Type::FreeVar(fv) = lhs.as_ref() {
let (sub, sup) = fv.get_subsup()?; let (sub, sup) = fv.get_subsup()?;
// TODO: automating let (verb, preposition, sequence) = Self::get_verb_and_preposition(&sup)?;
let (verb, preposition, sequence) = match &sup.qual_name()[..] {
"Add" => Some(("add", "and", Sequence::Forward)),
"Sub" => Some(("subtract", "from", Sequence::Backward)),
"Mul" => Some(("multiply", "and", Sequence::Forward)),
"Div" => Some(("divide", "by", Sequence::Forward)),
"Eq" => Some(("compare", "and", Sequence::Forward)),
"Ord" => Some(("compare", "and", Sequence::Forward)),
_ => None,
}?;
let sup = enum_unwrap!(sup.typarams().remove(0), TyParam::Type); let sup = enum_unwrap!(sup.typarams().remove(0), TyParam::Type);
let sup = self.readable_type(&sup); let sup = Self::readable_type(&sup);
let (l, r) = if sequence == Sequence::Forward { let (l, r) = if sequence == Sequence::Forward {
(sub, sup) (sub, sup)
} else { } else {

View file

@ -57,7 +57,7 @@ impl Context {
&spec_t, &spec_t,
body_t, body_t,
self.get_candidates(body_t), self.get_candidates(body_t),
self.get_type_mismatch_hint(&spec_t, body_t), Self::get_type_mismatch_hint(&spec_t, body_t),
))); )));
} }
Ok(()) Ok(())
@ -256,7 +256,7 @@ impl Context {
&mono("LambdaFunc"), &mono("LambdaFunc"),
t, t,
self.get_candidates(t), self.get_candidates(t),
self.get_type_mismatch_hint(&mono("LambdaFunc"), t), Self::get_type_mismatch_hint(&mono("LambdaFunc"), t),
))); )));
} }
} }
@ -1075,7 +1075,7 @@ impl Context {
param_t, param_t,
arg_t, arg_t,
self.get_candidates(arg_t), self.get_candidates(arg_t),
self.get_type_mismatch_hint(param_t, arg_t), Self::get_type_mismatch_hint(param_t, arg_t),
) )
}) })
.collect(), .collect(),
@ -1130,7 +1130,7 @@ impl Context {
param_t, param_t,
arg_t, arg_t,
self.get_candidates(arg_t), self.get_candidates(arg_t),
self.get_type_mismatch_hint(param_t, arg_t), Self::get_type_mismatch_hint(param_t, arg_t),
) )
}) })
.collect(), .collect(),
@ -1187,7 +1187,7 @@ impl Context {
pt.typ(), pt.typ(),
arg_t, arg_t,
self.get_candidates(arg_t), self.get_candidates(arg_t),
self.get_type_mismatch_hint(pt.typ(), arg_t), Self::get_type_mismatch_hint(pt.typ(), arg_t),
) )
}) })
.collect(), .collect(),

View file

@ -1285,7 +1285,7 @@ impl Context {
maybe_sup, maybe_sup,
maybe_sub, maybe_sub,
self.get_candidates(maybe_sub), self.get_candidates(maybe_sub),
self.get_type_mismatch_hint(maybe_sup, maybe_sub), Self::get_type_mismatch_hint(maybe_sup, maybe_sub),
))); )));
} }
match (maybe_sub, maybe_sup) { match (maybe_sub, maybe_sup) {

View file

@ -16,9 +16,9 @@ use erg_common::{
use erg_parser::error::{ParserRunnerError, ParserRunnerErrors}; use erg_parser::error::{ParserRunnerError, ParserRunnerErrors};
use crate::ty::{Predicate, Type}; use crate::context::Context;
use crate::hir::{Expr, Identifier, Signature}; use crate::hir::{Expr, Identifier, Signature};
use crate::ty::{Predicate, Type};
pub fn ordinal_num(n: usize) -> String { pub fn ordinal_num(n: usize) -> String {
match n.to_string().chars().last().unwrap() { match n.to_string().chars().last().unwrap() {
@ -972,6 +972,7 @@ passed keyword args: {kw_args_len}"
caused_by: String, caused_by: String,
hint: Option<String>, hint: Option<String>,
) -> Self { ) -> Self {
let hint = hint.or_else(|| Context::get_type_mismatch_hint(trait_, class));
Self::new( Self::new(
ErrorCore::new( ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], hint)], vec![SubMessage::ambiguous_new(loc, vec![], hint)],

View file

@ -168,7 +168,7 @@ impl ASTLowerer {
expect, expect,
found, found,
self.ctx.get_candidates(found), self.ctx.get_candidates(found),
self.ctx.get_type_mismatch_hint(expect, found), Context::get_type_mismatch_hint(expect, found),
) )
}) })
} }

View file

@ -1779,6 +1779,15 @@ impl Type {
} }
} }
pub fn union_types(&self) -> Option<(Type, Type)> {
match self {
Type::FreeVar(fv) if fv.is_linked() => fv.crack().union_types(),
Type::Refinement(refine) => refine.t.union_types(),
Type::Or(t1, t2) => Some((*t1.clone(), *t2.clone())),
_ => None,
}
}
/// assert!((A or B).contains_union(B)) /// assert!((A or B).contains_union(B))
pub fn contains_union(&self, typ: &Type) -> bool { pub fn contains_union(&self, typ: &Type) -> bool {
match self { match self {