[fine] WIP: Classes

This commit is contained in:
John Doty 2024-01-20 08:56:53 -08:00
parent e6c96fde38
commit 0ee89bf26b
7 changed files with 370 additions and 59 deletions

View file

@ -57,6 +57,17 @@ impl fmt::Display for Error {
}
}
pub struct FieldDecl {
pub name: Rc<str>,
pub field_type: Type,
}
pub struct ClassDecl {
pub name: Rc<str>,
pub fields: Vec<FieldDecl>,
pub decl_tree: TreeRef,
}
#[derive(Clone)]
pub enum Type {
// Signals a type error. If you receive this then you know that an error
@ -91,6 +102,7 @@ pub enum Type {
Function(Vec<Box<Type>>, Box<Type>),
List(Box<Type>),
Class(Rc<ClassDecl>),
}
impl Type {
@ -136,6 +148,7 @@ impl fmt::Display for Type {
// TODO: Better names
TypeVariable(_) => write!(f, "$_"),
List(t) => write!(f, "list<{t}>"),
Class(c) => write!(f, "class {}", c.name),
}
}
}
@ -173,6 +186,10 @@ pub enum Declaration {
declaration_type: Type,
id: ExternalFunctionId,
},
Class {
declaration_type: Type,
declaration: TreeRef, //?
},
}
pub struct Environment {
@ -404,7 +421,9 @@ impl<'a> Semantics<'a> {
}
pub fn snapshot_errors(&self) -> Vec<Error> {
(*self.errors.borrow()).clone()
let mut result = (*self.errors.borrow()).clone();
result.sort_by(|a, b| a.start.0.cmp(&b.start.0));
result
}
pub fn logical_parent(&self, tr: TreeRef) -> Option<TreeRef> {
@ -681,6 +700,16 @@ impl<'a> Semantics<'a> {
.all(|(a, b)| self.type_compat(a, b))
}
(Type::Class(ca), Type::Class(cb)) => {
// TODO: If we were doing structural comparisons here...
// maybe? MAYBE?
//
// Like if this is directional we can look for field
// subsets { ..:int, ..:int } can be { ..:int } etc.
//
ca.decl_tree == cb.decl_tree
}
// Avoid introducing more errors
(Type::Error, _) => true,
(_, Type::Error) => true,
@ -736,6 +765,8 @@ impl<'a> Semantics<'a> {
TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree),
TreeKind::ListConstructor => self.type_of_list_constructor(t, tree),
TreeKind::NewObjectExpression => self.type_of_new_object_expression(tree),
_ => self.internal_compiler_error(Some(t), "asking for a nonsense type"),
};
@ -865,6 +896,13 @@ impl<'a> Semantics<'a> {
);
return Some(Type::Error);
}
Declaration::Class { .. } => {
self.report_error_tree_ref(
left_tree,
"cannot assign a new value to a class declaration",
);
return Some(Type::Error);
}
},
None => {
self.report_error_tree_ref(
@ -1041,6 +1079,13 @@ impl<'a> Semantics<'a> {
fn type_of_call(&self, tree: &Tree) -> Option<Type> {
assert_eq!(tree.kind, TreeKind::CallExpression);
// TODO: Move the vast majority of error checking out of this
// function: once you know that the 0th tree (the function
// expression) yields a function type, assume the type of the
// call is the type of the function return. Don't bother
// matching argument types &c; do that in an explicit
// check_call_expression function below.
let f_ref = tree.nth_tree(0)?;
let f = self.type_of(f_ref);
@ -1151,6 +1196,9 @@ impl<'a> Semantics<'a> {
Declaration::ExternFunction {
declaration_type, ..
} => declaration_type.clone(),
Declaration::Class {
declaration_type, ..
} => declaration_type.clone(),
});
}
@ -1211,6 +1259,13 @@ impl<'a> Semantics<'a> {
Some(Type::List(Box::new(element_type)))
}
fn type_of_new_object_expression(&self, tree: &Tree) -> Option<Type> {
assert_eq!(tree.kind, TreeKind::NewObjectExpression);
// NOTE: Matching fields is done in the check function.
Some(self.type_of(tree.nth_tree(1)?))
}
fn type_of_list_constructor_element(&self, tree: &Tree) -> Option<Type> {
assert_eq!(tree.kind, TreeKind::ListConstructorElement);
Some(self.type_of(tree.nth_tree(0)?))
@ -1280,6 +1335,12 @@ impl<'a> Semantics<'a> {
} => {
eprintln!("{declaration_type:?} (extern {id:?})");
}
Declaration::Class {
declaration_type,
declaration,
} => {
eprintln!("{declaration_type:?} (class {declaration:?})");
}
};
}
environment = env.parent.clone();
@ -1321,10 +1382,13 @@ pub fn check(s: &Semantics) {
| TreeKind::GroupingExpression
| TreeKind::UnaryExpression
| TreeKind::ConditionalExpression
| TreeKind::CallExpression
| TreeKind::BinaryExpression => {
let _ = s.type_of(t);
}
TreeKind::CallExpression => {
let _ = s.type_of(t);
}
TreeKind::ArgumentList => {}
TreeKind::Argument => {
let _ = s.type_of(t);
@ -1343,6 +1407,12 @@ pub fn check(s: &Semantics) {
let _ = s.type_of(t);
}
TreeKind::ForStatement => check_for_statement(s, t),
TreeKind::ClassDecl => {}
TreeKind::FieldDecl => {}
TreeKind::FieldList => {}
TreeKind::NewObjectExpression => check_new_object_expression(s, tree),
TreeKind::FieldValue => {}
}
}
}
@ -1426,6 +1496,72 @@ fn check_for_statement(s: &Semantics, t: TreeRef) {
let _ = s.environment_of(t);
}
// TODO: TEST: Check mutual recursion with function calls
// TODO: TEST: Missing fields
// TODO: TEST: Extra fields
// TODO: TEST: Existing and type mismatch
fn check_new_object_expression(s: &Semantics, tree: &Tree) {
let Some(type_expression) = tree.nth_tree(1) else {
return;
};
let Some(field_list) = tree.child_tree_of_kind(s.syntax_tree, TreeKind::FieldList) else {
return;
};
let class_type = s.type_of(type_expression);
match &class_type {
Type::Class(c) => {
let mut any_errors = false;
let mut field_bindings = HashMap::new();
for field in field_list.children_of_kind(s.syntax_tree, TreeKind::FieldValue) {
let f = &s.syntax_tree[field];
if let Some(name) = f.nth_token(0) {
let field_type = s.type_of(field);
field_bindings.insert(name.as_str(), (field, field_type));
} else {
any_errors = true;
}
}
// Check individual bindings...
for f in c.fields.iter() {
if let Some((field_tree, expr_type)) = field_bindings.get(&*f.name) {
if !s.type_compat(&f.field_type, expr_type) {
s.report_error_tree_ref(
*field_tree,
format!(
"field {} has is of type {}, but this expression generates a {}",
f.name, f.field_type, expr_type,
),
);
}
field_bindings.remove(&*f.name);
} else if !any_errors {
s.report_error_tree(
tree,
format!("missing an initializer for field {}", f.name),
);
}
}
if !any_errors {
for (n, (field_tree, _)) in field_bindings.iter() {
s.report_error_tree_ref(
*field_tree,
format!("{} does not have a field named {}", class_type, n),
);
}
}
}
Type::Error => (),
ct => s.report_error_tree_ref(
type_expression,
format!("expected this to be a class type, but it is {ct}"),
),
}
}
#[cfg(test)]
mod tests {
use super::*;