Compare commits

...

2 commits

3 changed files with 68 additions and 32 deletions

View file

@ -39,3 +39,28 @@ 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.

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!(