diff --git a/fine/design.md b/fine/design.md index df9cc5d8..506a3d0d 100644 --- a/fine/design.md +++ b/fine/design.md @@ -39,28 +39,3 @@ The `new` keyword is a compromise: we know that the context immediately following the `new` keyword is always a type expression, so we know that e.g. `<` or whatever means "generic type parameter" and not "less than". - -## Lambdas/Closures/Anonymous Functions - -Looking for a syntax here; I want to keep `fun` as a declaration like -`let` and not let it enter the expression space. I don't like -fat-arrow syntax because it makes expression parsing very ambiguous, -potentially requiring a lot of lookahead. (TODO: Is that true?) - -Maybe a leading character like ` \x => x+1 ` or ` \(x,y) => x+y `? - -## Interfaces/Traits/Whatever - -These are incomplete structural types. Methods are easier to make -compatible than members, but members should also work so long as they -are strict prefixes of the thing. - -What about sound casting with narrowing? That's union types baby, do -we really want those? It could be neat if we're doing otherwise -structural-compatibility. - -## On Objects and Classes - -Sometimes I think it should all be structural types. - -Maybe later there can be anonymous types that match shapes. diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index 512919cd..25411fc2 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.can_convert(&arg_type, &Type::Nothing) { + if c.semantics.type_compat(&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 e55a44a6..40f8b355 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -957,31 +957,34 @@ impl<'a> Semantics<'a> { result } - pub fn can_convert(&self, from: &Type, to: &Type) -> bool { + pub fn type_compat(&self, a: &Type, b: &Type) -> bool { + // TODO: Convert this into "can become" or something. // TODO: This is wrong; we because of numeric literals etc. - match (from, to) { + match (a, b) { (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(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 + (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 .iter() - .zip(to_args.iter()) - .all(|(from, to)| self.can_convert(from, to)) + .zip(b_args.iter()) + .all(|(a, b)| self.type_compat(a, b)) } - (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. + (Type::Object(ca, _), Type::Object(cb, _)) => { + // TODO: If we were doing structural comparisons here... + // maybe? MAYBE? // - c_from == c_to + // Like if this is directional we can look for field + // subsets { ..:int, ..:int } can be { ..:int } etc. + // + ca == cb } // Avoid introducing more errors @@ -1200,7 +1203,7 @@ impl<'a> Semantics<'a> { if left_type.is_error() || right_type.is_error() { Some(Type::Error) - } else if self.can_convert(&right_type, &left_type) { + } else if self.type_compat(&left_type, &right_type) { Some(Type::Assignment(Box::new(left_type))) } else { self.report_error( @@ -1346,7 +1349,7 @@ impl<'a> Semantics<'a> { None }; - if !self.can_convert(&cond_type, &Type::Bool) { + if !self.type_compat(&cond_type, &Type::Bool) { if !cond_type.is_error() { self.report_error_tree_ref(cond_tree, "conditions must yield a boolean"); } @@ -1362,20 +1365,14 @@ impl<'a> Semantics<'a> { (then_type, else_type) => { let else_type = else_type.unwrap_or(Type::Nothing); - 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 { + if !self.type_compat(&then_type, &else_type) { 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) } } } @@ -1419,9 +1416,7 @@ impl<'a> Semantics<'a> { } for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() { - // a here is the type of the argument expression; p is - // the declared type of the parameter. - if !self.can_convert(&a, p) { + if !self.type_compat(&a, p) { self.report_error_tree_ref( *t, format!( @@ -1451,9 +1446,7 @@ impl<'a> Semantics<'a> { } for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() { - // a here is the type of the argument expression; p is - // the declared type of the parameter. - if !self.can_convert(&a, p) { + if !self.type_compat(&a, p) { self.report_error_tree_ref( *t, format!( @@ -1643,15 +1636,10 @@ 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 { - // 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}")); + 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}")); + } Some(list_type) } } @@ -1659,6 +1647,7 @@ impl<'a> Semantics<'a> { } let element_type = element_type.unwrap_or_else(|| Type::TypeVariable(t)); + Some(Type::List(Box::new(element_type))) } @@ -1889,7 +1878,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.can_convert(&body_type, &return_type) { + if !s.type_compat(&body_type, &return_type) { // Just work very hard to get an appropriate error span. let (start, end) = return_type_tree .map(|t| { @@ -1939,7 +1928,7 @@ fn check_return_statement(s: &Semantics, tree: &Tree) { Type::Error }; - if !s.can_convert(&actual_type, &expected_type) { + if !s.type_compat(&expected_type, &actual_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}'")); } } @@ -1985,7 +1974,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.can_convert(expr_type, &f.field_type) { + if !s.type_compat(&f.field_type, expr_type) { s.report_error_tree_ref( *field_tree, format!(