Compare commits

..

No commits in common. "36ed61b8bbe08456baab19c091c21afc6de711f5" and "19e57db7249970cf8a92577c7fe19cebf8134afa" have entirely different histories.

3 changed files with 32 additions and 68 deletions

View file

@ -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, immediately following the `new` keyword is always a type expression,
so we know that e.g. `<` or whatever means "generic type parameter" so we know that e.g. `<` or whatever means "generic type parameter"
and not "less than". 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.

View file

@ -449,7 +449,7 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
} }
TokenKind::EqualEqual => { TokenKind::EqualEqual => {
compile_simple_binary_expression(c, tr, |c, arg_type| { 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);
c.push(Instruction::Discard); c.push(Instruction::Discard);
Instruction::PushTrue Instruction::PushTrue

View file

@ -957,31 +957,34 @@ impl<'a> Semantics<'a> {
result 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. // TODO: This is wrong; we because of numeric literals etc.
match (from, to) { match (a, b) {
(Type::F64, Type::F64) => true, (Type::F64, Type::F64) => true,
(Type::String, Type::String) => true, (Type::String, Type::String) => true,
(Type::Bool, Type::Bool) => true, (Type::Bool, Type::Bool) => true,
(Type::Unreachable, Type::Unreachable) => true, (Type::Unreachable, Type::Unreachable) => true,
(Type::Nothing, Type::Nothing) => true, (Type::Nothing, Type::Nothing) => true,
(Type::List(from), Type::List(to)) => self.can_convert(from, to), (Type::List(a), Type::List(b)) => self.type_compat(a, b),
(Type::Function(from_args, from_ret), Type::Function(to_args, to_ret)) => { (Type::Function(a_args, a_ret), Type::Function(b_args, b_ret)) => {
from_args.len() == from_args.len() a_args.len() == b_args.len()
&& self.can_convert(to_ret, from_ret) && self.type_compat(a_ret, b_ret)
&& from_args && a_args
.iter() .iter()
.zip(to_args.iter()) .zip(b_args.iter())
.all(|(from, to)| self.can_convert(from, to)) .all(|(a, b)| self.type_compat(a, b))
} }
(Type::Object(c_from, _), Type::Object(c_to, _)) => { (Type::Object(ca, _), Type::Object(cb, _)) => {
// TODO: Structural comparisons. All that matters is that // TODO: If we were doing structural comparisons here...
// c_to has a subset of fields and methods, and the // maybe? MAYBE?
// fields and methods are all compatible.
// //
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 // Avoid introducing more errors
@ -1200,7 +1203,7 @@ impl<'a> Semantics<'a> {
if left_type.is_error() || right_type.is_error() { if left_type.is_error() || right_type.is_error() {
Some(Type::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))) Some(Type::Assignment(Box::new(left_type)))
} else { } else {
self.report_error( self.report_error(
@ -1346,7 +1349,7 @@ impl<'a> Semantics<'a> {
None None
}; };
if !self.can_convert(&cond_type, &Type::Bool) { if !self.type_compat(&cond_type, &Type::Bool) {
if !cond_type.is_error() { if !cond_type.is_error() {
self.report_error_tree_ref(cond_tree, "conditions must yield a boolean"); self.report_error_tree_ref(cond_tree, "conditions must yield a boolean");
} }
@ -1362,20 +1365,14 @@ impl<'a> Semantics<'a> {
(then_type, else_type) => { (then_type, else_type) => {
let else_type = else_type.unwrap_or(Type::Nothing); let else_type = else_type.unwrap_or(Type::Nothing);
if self.can_convert(&then_type, &else_type) { if !self.type_compat(&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( self.report_error_tree(
tree, tree,
format!("the type of the 'then' branch ('{then_type}') must match the type of the 'else' branch ('{else_type}')"), format!("the type of the 'then' branch ('{then_type}') must match the type of the 'else' branch ('{else_type}')"),
); );
Some(Type::Error) 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() { for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() {
// a here is the type of the argument expression; p is if !self.type_compat(&a, p) {
// the declared type of the parameter.
if !self.can_convert(&a, p) {
self.report_error_tree_ref( self.report_error_tree_ref(
*t, *t,
format!( format!(
@ -1451,9 +1446,7 @@ impl<'a> Semantics<'a> {
} }
for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() { for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() {
// a here is the type of the argument expression; p is if !self.type_compat(&a, p) {
// the declared type of the parameter.
if !self.can_convert(&a, p) {
self.report_error_tree_ref( self.report_error_tree_ref(
*t, *t,
format!( format!(
@ -1643,15 +1636,10 @@ impl<'a> Semantics<'a> {
Some(child_type) Some(child_type)
} else if child_type.is_error() { } else if child_type.is_error() {
Some(list_type) 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 { } else {
// Even if there is some incompatibility we stick if !self.type_compat(&child_type, &list_type) {
// 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}")); 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) Some(list_type)
} }
} }
@ -1659,6 +1647,7 @@ impl<'a> Semantics<'a> {
} }
let element_type = element_type.unwrap_or_else(|| Type::TypeVariable(t)); let element_type = element_type.unwrap_or_else(|| Type::TypeVariable(t));
Some(Type::List(Box::new(element_type))) 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) { if let Some(body) = tree.child_of_kind(s.syntax_tree, TreeKind::Block) {
let body_type = s.type_of(body); 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. // Just work very hard to get an appropriate error span.
let (start, end) = return_type_tree let (start, end) = return_type_tree
.map(|t| { .map(|t| {
@ -1939,7 +1928,7 @@ fn check_return_statement(s: &Semantics, tree: &Tree) {
Type::Error 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}'")); 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... // Check individual bindings...
for f in class.fields.iter() { for f in class.fields.iter() {
if let Some((field_tree, expr_type)) = field_bindings.get(&*f.name) { 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( s.report_error_tree_ref(
*field_tree, *field_tree,
format!( format!(