[fine] WIP: Classes
This commit is contained in:
parent
e6c96fde38
commit
0ee89bf26b
7 changed files with 370 additions and 59 deletions
|
|
@ -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::*;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue