From 36ed61b8bbe08456baab19c091c21afc6de711f5 Mon Sep 17 00:00:00 2001 From: John Doty Date: Thu, 25 Jan 2024 22:09:59 -0800 Subject: [PATCH] [fine] Name the type comparison method properly --- fine/src/compiler.rs | 2 +- fine/src/semantics.rs | 73 +++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index 25411fc2..512919cd 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -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 diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index 40f8b355..e55a44a6 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -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!(