[fine] Improvements to classes
- Classes are defined lazily - Member access is via environment - Member access is just a binary expression with a weird environment - Slot loads look like variable loads now - Associativity in the parser (ugh)
This commit is contained in:
parent
0d48bfb113
commit
2839b43f6d
5 changed files with 286 additions and 123 deletions
|
|
@ -68,6 +68,23 @@ pub struct ClassDecl {
|
|||
pub decl_tree: TreeRef,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClassRef(Rc<ClassDecl>);
|
||||
|
||||
impl ClassRef {
|
||||
pub fn new(class: ClassDecl) -> Self {
|
||||
ClassRef(Rc::new(class))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ClassRef {
|
||||
type Target = ClassDecl;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Type {
|
||||
// Signals a type error. If you receive this then you know that an error
|
||||
|
|
@ -99,7 +116,10 @@ pub enum Type {
|
|||
|
||||
Function(Vec<Box<Type>>, Box<Type>),
|
||||
List(Box<Type>),
|
||||
Class(Rc<ClassDecl>),
|
||||
|
||||
// Classes need to be fetched explicitly from the semantics; they are
|
||||
// computed lazily.
|
||||
Class(TreeRef, Rc<str>),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
|
|
@ -144,7 +164,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),
|
||||
Class(_, name) => write!(f, "class {}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +174,7 @@ pub enum Location {
|
|||
Argument,
|
||||
Local,
|
||||
Module,
|
||||
// TODO: Member
|
||||
Slot,
|
||||
// TODO: ArrayIndex
|
||||
}
|
||||
|
||||
|
|
@ -204,6 +224,7 @@ impl Environment {
|
|||
let base = parent.as_ref().map(|p| p.next_index).unwrap_or(0);
|
||||
let next_index = match (parent_location, location) {
|
||||
(_, Location::Argument) => 0,
|
||||
(_, Location::Slot) => 0,
|
||||
|
||||
(Location::Local, Location::Local) => base,
|
||||
(_, Location::Local) => 0,
|
||||
|
|
@ -221,8 +242,12 @@ impl Environment {
|
|||
}
|
||||
|
||||
pub fn insert(&mut self, token: &Token, t: Type) -> Option<Declaration> {
|
||||
self.insert_name(token.as_str().into(), t)
|
||||
}
|
||||
|
||||
pub fn insert_name(&mut self, name: Box<str>, t: Type) -> Option<Declaration> {
|
||||
let result = self.declarations.insert(
|
||||
token.as_str().into(),
|
||||
name,
|
||||
Declaration::Variable {
|
||||
declaration_type: t,
|
||||
location: self.location,
|
||||
|
|
@ -341,6 +366,17 @@ fn set_logical_parents(
|
|||
}
|
||||
}
|
||||
}
|
||||
TreeKind::MemberAccess => {
|
||||
// The LHS has the environment of my parent, because I set up an
|
||||
// environment containing only members of the type of the LHS. The
|
||||
// RHS has that one.
|
||||
if let Some(lhs) = tree.nth_tree(0) {
|
||||
set_logical_parents(parents, syntax_tree, lhs, parent);
|
||||
}
|
||||
if let Some(rhs) = tree.nth_tree(2) {
|
||||
set_logical_parents(parents, syntax_tree, rhs, Some(t));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// By default, the parent for each child is current tree.
|
||||
for child in &tree.children {
|
||||
|
|
@ -375,6 +411,7 @@ pub struct Semantics<'a> {
|
|||
types: RefCell<Vec<Incremental<Type>>>,
|
||||
environments: RefCell<Vec<Incremental<EnvironmentRef>>>,
|
||||
root_environment: EnvironmentRef,
|
||||
classes: RefCell<Vec<Incremental<ClassRef>>>,
|
||||
}
|
||||
|
||||
impl<'a> Semantics<'a> {
|
||||
|
|
@ -394,6 +431,7 @@ impl<'a> Semantics<'a> {
|
|||
types: RefCell::new(vec![Incremental::None; tree.len()]),
|
||||
environments: RefCell::new(vec![Incremental::None; tree.len()]),
|
||||
root_environment: EnvironmentRef::new(root_environment),
|
||||
classes: RefCell::new(vec![Incremental::None; tree.len()]),
|
||||
};
|
||||
|
||||
// NOTE: We ensure all the known errors are reported before we move
|
||||
|
|
@ -519,6 +557,8 @@ impl<'a> Semantics<'a> {
|
|||
|
||||
TreeKind::ForStatement => self.environment_of_for(parent, tree),
|
||||
|
||||
TreeKind::MemberAccess => self.environment_of_member_access(parent, tree),
|
||||
|
||||
_ => parent,
|
||||
};
|
||||
|
||||
|
|
@ -629,6 +669,13 @@ impl<'a> Semantics<'a> {
|
|||
Location::Local => Location::Local,
|
||||
Location::Module => Location::Module,
|
||||
Location::Argument => Location::Local,
|
||||
|
||||
// TODO: Wait can I... do wild stuff? e.g.:
|
||||
//
|
||||
// foo.{let y = 23; bar}
|
||||
//
|
||||
// ???
|
||||
Location::Slot => Location::Local,
|
||||
};
|
||||
|
||||
let mut environment = Environment::new(Some(parent), location);
|
||||
|
|
@ -689,6 +736,91 @@ impl<'a> Semantics<'a> {
|
|||
EnvironmentRef::new(environment)
|
||||
}
|
||||
|
||||
fn environment_of_member_access(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
|
||||
// TODO: Build an "error environment" and then anything that tried to
|
||||
// bind in it would know to give up and not report a new error.
|
||||
|
||||
assert_eq!(tree.kind, TreeKind::MemberAccess);
|
||||
|
||||
// Build environment out of members of type of lhs
|
||||
let Some(lhs) = tree.nth_tree(0) else {
|
||||
return parent;
|
||||
};
|
||||
let Some(op) = tree.nth_token(1) else {
|
||||
return parent;
|
||||
};
|
||||
let typ = self.type_of(lhs);
|
||||
match &typ {
|
||||
Type::Class(ct, _) => {
|
||||
// NOTE: The parent here is None! environment search after a dot
|
||||
// is constrained to the scope of the type, obviously!
|
||||
let class = self.class_of(*ct);
|
||||
let mut env = Environment::new(None, Location::Slot);
|
||||
|
||||
// TODO: Cache environment of type.
|
||||
// NOTE: It is important that this go in order! The location
|
||||
// index is the slot index!
|
||||
for field in class.fields.iter() {
|
||||
env.insert_name((&*field.name).into(), field.field_type.clone());
|
||||
}
|
||||
|
||||
EnvironmentRef::new(env)
|
||||
}
|
||||
Type::Error => parent, // TODO: WRONG
|
||||
_ => {
|
||||
// TODO: This is probably wrong, yeah?
|
||||
self.report_error(op.start, format!("cannot access members of '{typ}'"));
|
||||
parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn class_of(&self, t: TreeRef) -> ClassRef {
|
||||
{
|
||||
// I want to make sure that this borrow is dropped after this block.
|
||||
let mut borrow = self.classes.borrow_mut();
|
||||
let state = &mut borrow[t.index()];
|
||||
match state {
|
||||
Incremental::None => (),
|
||||
Incremental::Complete(e) => return e.clone(),
|
||||
Incremental::InProgress => {
|
||||
drop(borrow);
|
||||
self.internal_compiler_error(Some(t), "circular class dependency");
|
||||
}
|
||||
}
|
||||
*state = Incremental::InProgress;
|
||||
}
|
||||
|
||||
// TODO: Right now there's only one way to make a class decl. :P
|
||||
let tree = &self.syntax_tree[t];
|
||||
assert_eq!(tree.kind, TreeKind::ClassDecl);
|
||||
|
||||
let name = tree.nth_token(1).map(|t| t.as_str()).unwrap_or("<??>");
|
||||
let mut fields = Vec::new();
|
||||
for field in tree.children_of_kind(self.syntax_tree, TreeKind::FieldDecl) {
|
||||
let f = &self.syntax_tree[field];
|
||||
if let Some(field_name) = f.nth_token(0) {
|
||||
let field_type = f
|
||||
.nth_tree(2)
|
||||
.map(|t| self.type_of(t))
|
||||
.unwrap_or(Type::Error);
|
||||
fields.push(FieldDecl {
|
||||
name: field_name.as_str().into(),
|
||||
field_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let result = ClassRef::new(ClassDecl {
|
||||
name: name.into(),
|
||||
fields: fields.into(),
|
||||
decl_tree: t,
|
||||
});
|
||||
|
||||
self.classes.borrow_mut()[t.index()] = Incremental::Complete(result.clone());
|
||||
result
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
@ -709,14 +841,14 @@ impl<'a> Semantics<'a> {
|
|||
.all(|(a, b)| self.type_compat(a, b))
|
||||
}
|
||||
|
||||
(Type::Class(ca), Type::Class(cb)) => {
|
||||
(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
|
||||
ca == cb
|
||||
}
|
||||
|
||||
// Avoid introducing more errors
|
||||
|
|
@ -1182,29 +1314,8 @@ impl<'a> Semantics<'a> {
|
|||
fn type_of_member_access(&self, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::MemberAccess);
|
||||
|
||||
let op = tree.nth_token(1)?;
|
||||
let member = tree.nth_token(2)?;
|
||||
let typ = self.type_of(tree.nth_tree(0)?);
|
||||
match &typ {
|
||||
Type::Class(c) => {
|
||||
// TODO: Accelerate?
|
||||
if let Some(field) = c.fields.iter().find(|f| &*f.name == member.as_str()) {
|
||||
Some(field.field_type.clone())
|
||||
} else {
|
||||
self.report_error(
|
||||
op.start,
|
||||
format!("'{typ}' does not have a member named '{member}'"),
|
||||
);
|
||||
Some(Type::Error)
|
||||
}
|
||||
}
|
||||
Type::Error => Some(Type::Error),
|
||||
_ => {
|
||||
// TODO: This is probably wrong, yeah?
|
||||
self.report_error(op.start, format!("cannot access members of '{typ}'"));
|
||||
Some(Type::Error)
|
||||
}
|
||||
}
|
||||
// Type of member access is the type of the RHS.
|
||||
Some(self.type_of(tree.nth_tree(2)?))
|
||||
}
|
||||
|
||||
fn type_of_expression_statement(&self, tree: &Tree) -> Option<Type> {
|
||||
|
|
@ -1320,26 +1431,9 @@ impl<'a> Semantics<'a> {
|
|||
fn type_of_class_decl(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::ClassDecl);
|
||||
|
||||
// TODO: Field types need to be lazy because of recursion.
|
||||
|
||||
// The type of a class is computed lazily.
|
||||
let name = tree.nth_token(1)?;
|
||||
let mut fields = Vec::new();
|
||||
for field in tree.children_of_kind(self.syntax_tree, TreeKind::FieldDecl) {
|
||||
let f = &self.syntax_tree[field];
|
||||
let field_name = f.nth_token(0)?;
|
||||
fields.push(FieldDecl {
|
||||
name: field_name.as_str().into(),
|
||||
field_type: self.type_of(f.nth_tree(2)?),
|
||||
});
|
||||
}
|
||||
|
||||
let cd = ClassDecl {
|
||||
name: name.as_str().into(),
|
||||
fields: fields.into(),
|
||||
decl_tree: t,
|
||||
};
|
||||
|
||||
Some(Type::Class(cd.into()))
|
||||
Some(Type::Class(t, name.as_str().into()))
|
||||
}
|
||||
|
||||
fn type_of_field_value(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
|
||||
|
|
@ -1533,6 +1627,8 @@ pub fn check(s: &Semantics) {
|
|||
TreeKind::FieldList => {}
|
||||
TreeKind::NewObjectExpression => check_new_object_expression(s, tree),
|
||||
TreeKind::FieldValue => {}
|
||||
TreeKind::SelfParameter => {}
|
||||
TreeKind::SelfReference => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1626,7 +1722,9 @@ fn check_new_object_expression(s: &Semantics, tree: &Tree) {
|
|||
|
||||
let class_type = s.type_of(type_expression);
|
||||
match &class_type {
|
||||
Type::Class(c) => {
|
||||
Type::Class(c, _) => {
|
||||
let class = s.class_of(*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) {
|
||||
|
|
@ -1640,7 +1738,7 @@ fn check_new_object_expression(s: &Semantics, tree: &Tree) {
|
|||
}
|
||||
|
||||
// Check individual bindings...
|
||||
for f in c.fields.iter() {
|
||||
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) {
|
||||
s.report_error_tree_ref(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue