[fine] Name the type comparison method properly

This commit is contained in:
John Doty 2024-01-25 22:09:59 -08:00
parent e66ffbb5ae
commit 36ed61b8bb
2 changed files with 43 additions and 32 deletions

View file

@ -449,7 +449,7 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
}
TokenKind::EqualEqual => {
compile_simple_binary_expression(c, tr, |c, arg_type| {
if c.semantics.type_compat(&arg_type, &Type::Nothing) {
if c.semantics.can_convert(&arg_type, &Type::Nothing) {
c.push(Instruction::Discard);
c.push(Instruction::Discard);
Instruction::PushTrue

View file

@ -957,34 +957,31 @@ impl<'a> Semantics<'a> {
result
}
pub fn type_compat(&self, a: &Type, b: &Type) -> bool {
// TODO: Convert this into "can become" or something.
pub fn can_convert(&self, from: &Type, to: &Type) -> bool {
// TODO: This is wrong; we because of numeric literals etc.
match (a, b) {
match (from, to) {
(Type::F64, Type::F64) => true,
(Type::String, Type::String) => true,
(Type::Bool, Type::Bool) => true,
(Type::Unreachable, Type::Unreachable) => true,
(Type::Nothing, Type::Nothing) => true,
(Type::List(a), Type::List(b)) => self.type_compat(a, b),
(Type::Function(a_args, a_ret), Type::Function(b_args, b_ret)) => {
a_args.len() == b_args.len()
&& self.type_compat(a_ret, b_ret)
&& a_args
(Type::List(from), Type::List(to)) => self.can_convert(from, to),
(Type::Function(from_args, from_ret), Type::Function(to_args, to_ret)) => {
from_args.len() == from_args.len()
&& self.can_convert(to_ret, from_ret)
&& from_args
.iter()
.zip(b_args.iter())
.all(|(a, b)| self.type_compat(a, b))
.zip(to_args.iter())
.all(|(from, to)| self.can_convert(from, to))
}
(Type::Object(ca, _), Type::Object(cb, _)) => {
// TODO: If we were doing structural comparisons here...
// maybe? MAYBE?
(Type::Object(c_from, _), Type::Object(c_to, _)) => {
// TODO: Structural comparisons. All that matters is that
// c_to has a subset of fields and methods, and the
// fields and methods are all compatible.
//
// Like if this is directional we can look for field
// subsets { ..:int, ..:int } can be { ..:int } etc.
//
ca == cb
c_from == c_to
}
// Avoid introducing more errors
@ -1203,7 +1200,7 @@ impl<'a> Semantics<'a> {
if left_type.is_error() || right_type.is_error() {
Some(Type::Error)
} else if self.type_compat(&left_type, &right_type) {
} else if self.can_convert(&right_type, &left_type) {
Some(Type::Assignment(Box::new(left_type)))
} else {
self.report_error(
@ -1349,7 +1346,7 @@ impl<'a> Semantics<'a> {
None
};
if !self.type_compat(&cond_type, &Type::Bool) {
if !self.can_convert(&cond_type, &Type::Bool) {
if !cond_type.is_error() {
self.report_error_tree_ref(cond_tree, "conditions must yield a boolean");
}
@ -1365,14 +1362,20 @@ impl<'a> Semantics<'a> {
(then_type, else_type) => {
let else_type = else_type.unwrap_or(Type::Nothing);
if !self.type_compat(&then_type, &else_type) {
if self.can_convert(&then_type, &else_type) {
Some(else_type)
// I know, I know, as of this writing there is no real
// reason to ever check this, because this relationship
// is symmetrical. But someday it won't be, and you'll be
// glad I had the forethought to be thorough.
} else if self.can_convert(&else_type, &then_type) {
Some(then_type)
} else {
self.report_error_tree(
tree,
format!("the type of the 'then' branch ('{then_type}') must match the type of the 'else' branch ('{else_type}')"),
);
Some(Type::Error)
} else {
Some(then_type)
}
}
}
@ -1416,7 +1419,9 @@ impl<'a> Semantics<'a> {
}
for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() {
if !self.type_compat(&a, p) {
// a here is the type of the argument expression; p is
// the declared type of the parameter.
if !self.can_convert(&a, p) {
self.report_error_tree_ref(
*t,
format!(
@ -1446,7 +1451,9 @@ impl<'a> Semantics<'a> {
}
for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() {
if !self.type_compat(&a, p) {
// a here is the type of the argument expression; p is
// the declared type of the parameter.
if !self.can_convert(&a, p) {
self.report_error_tree_ref(
*t,
format!(
@ -1636,10 +1643,15 @@ impl<'a> Semantics<'a> {
Some(child_type)
} else if child_type.is_error() {
Some(list_type)
} else if self.can_convert(&child_type, &list_type) {
Some(list_type)
} else if self.can_convert(&list_type, &child_type) {
Some(child_type)
} else {
if !self.type_compat(&child_type, &list_type) {
self.report_error_tree_ref(ct, format!("list element of type {child_type} is not compatible with the list type {list_type}"));
}
// Even if there is some incompatibility we stick
// with the list type, preferring to guess what was
// intended rather than just error out.
self.report_error_tree_ref(ct, format!("list element of type {child_type} is not compatible with the list type {list_type}"));
Some(list_type)
}
}
@ -1647,7 +1659,6 @@ impl<'a> Semantics<'a> {
}
let element_type = element_type.unwrap_or_else(|| Type::TypeVariable(t));
Some(Type::List(Box::new(element_type)))
}
@ -1878,7 +1889,7 @@ fn check_function_decl(s: &Semantics, t: TreeRef, tree: &Tree) {
if let Some(body) = tree.child_of_kind(s.syntax_tree, TreeKind::Block) {
let body_type = s.type_of(body);
if !s.type_compat(&body_type, &return_type) {
if !s.can_convert(&body_type, &return_type) {
// Just work very hard to get an appropriate error span.
let (start, end) = return_type_tree
.map(|t| {
@ -1928,7 +1939,7 @@ fn check_return_statement(s: &Semantics, tree: &Tree) {
Type::Error
};
if !s.type_compat(&expected_type, &actual_type) {
if !s.can_convert(&actual_type, &expected_type) {
s.report_error_tree(tree, format!("callers of this function expect a value of type '{expected_type}' but this statement returns a value of type '{actual_type}'"));
}
}
@ -1974,7 +1985,7 @@ fn check_new_object_expression(s: &Semantics, tree: &Tree) {
// Check individual bindings...
for f in class.fields.iter() {
if let Some((field_tree, expr_type)) = field_bindings.get(&*f.name) {
if !s.type_compat(&f.field_type, expr_type) {
if !s.can_convert(expr_type, &f.field_type) {
s.report_error_tree_ref(
*field_tree,
format!(