[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
|
|
@ -487,6 +487,9 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
|
|||
compiler_assert!(c, t, index < c.module.globals);
|
||||
Instruction::StoreModule(index)
|
||||
}
|
||||
Location::Slot => {
|
||||
ice!(c, t, "cannot have an identifier lvalue bind to a slot");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -496,6 +499,9 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
|
|||
};
|
||||
c.push(instruction);
|
||||
}
|
||||
|
||||
// TODO: Member
|
||||
// TODO: List element
|
||||
_ => ice!(c, t, "Unsupported lvalue type"),
|
||||
}
|
||||
OK
|
||||
|
|
@ -533,6 +539,10 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
|
|||
compiler_assert!(c, t, index < c.module.globals);
|
||||
Instruction::LoadModule(index)
|
||||
}
|
||||
Location::Slot => {
|
||||
// TODO: Assert slot is in field range?
|
||||
Instruction::LoadSlot(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
Declaration::Function { declaration, .. } => {
|
||||
|
|
@ -603,10 +613,11 @@ fn compile_argument(c: &mut Compiler, tree: &Tree) -> CR {
|
|||
|
||||
fn compile_new_object_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
|
||||
// We pass in the arguments.... by... field order?
|
||||
let Type::Class(class) = c.semantics.type_of(t) else {
|
||||
let Type::Class(ct, _) = c.semantics.type_of(t) else {
|
||||
c.push(Instruction::Panic);
|
||||
return OK;
|
||||
};
|
||||
let class = c.semantics.class_of(ct);
|
||||
|
||||
let field_list = tree.child_tree_of_kind(c.syntax, TreeKind::FieldList)?;
|
||||
let mut field_bindings = HashMap::new();
|
||||
|
|
@ -669,22 +680,10 @@ fn compile_field_value(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
|
|||
}
|
||||
|
||||
fn compile_member_access(c: &mut Compiler, tree: &Tree) -> CR {
|
||||
let lhs = tree.nth_tree(0)?;
|
||||
compile_expression(c, lhs);
|
||||
|
||||
let id = tree.nth_token(2)?;
|
||||
let Type::Class(cr) = c.semantics.type_of(lhs) else {
|
||||
return None;
|
||||
};
|
||||
let Some((index, _fld)) = cr
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, f)| &*f.name == id.as_str())
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
c.push(Instruction::LoadSlot(index));
|
||||
// In member access; the lhs sets up the object and in theory the rhs
|
||||
// binds against it. ::shrug::
|
||||
compile_expression(c, tree.nth_tree(0)?);
|
||||
compile_expression(c, tree.nth_tree(2)?);
|
||||
OK
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@ pub enum TreeKind {
|
|||
ConditionalExpression,
|
||||
ExpressionStatement,
|
||||
FieldDecl,
|
||||
FieldList,
|
||||
FieldValue,
|
||||
File,
|
||||
ForStatement,
|
||||
FunctionDecl,
|
||||
|
|
@ -139,18 +141,18 @@ pub enum TreeKind {
|
|||
ListConstructor,
|
||||
ListConstructorElement,
|
||||
LiteralExpression,
|
||||
MemberAccess,
|
||||
NewObjectExpression,
|
||||
ParamList,
|
||||
Parameter,
|
||||
ReturnStatement,
|
||||
ReturnType,
|
||||
SelfParameter,
|
||||
SelfReference,
|
||||
TypeExpression,
|
||||
TypeParameter,
|
||||
TypeParameterList,
|
||||
UnaryExpression,
|
||||
FieldList,
|
||||
NewObjectExpression,
|
||||
FieldValue,
|
||||
MemberAccess,
|
||||
}
|
||||
|
||||
pub struct Tree<'a> {
|
||||
|
|
@ -606,6 +608,8 @@ fn param_list(p: &mut CParser) {
|
|||
while !p.at(TokenKind::RightParen) && !p.eof() {
|
||||
if p.at(TokenKind::Identifier) {
|
||||
parameter(p);
|
||||
} else if p.at(TokenKind::Selff) {
|
||||
self_parameter(p);
|
||||
} else {
|
||||
if p.at_any(PARAM_LIST_RECOVERY) {
|
||||
break;
|
||||
|
|
@ -632,6 +636,21 @@ fn parameter(p: &mut CParser) {
|
|||
p.end(m, TreeKind::Parameter);
|
||||
}
|
||||
|
||||
fn self_parameter(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
p.expect_start(TokenKind::Selff);
|
||||
if p.eat(TokenKind::Colon) {
|
||||
p.error("self parameters cannot have explicit types");
|
||||
type_expr(p);
|
||||
}
|
||||
if !p.at(TokenKind::RightParen) {
|
||||
p.expect(TokenKind::Comma, "expected a comma between parameters");
|
||||
}
|
||||
|
||||
p.end(m, TreeKind::SelfParameter);
|
||||
}
|
||||
|
||||
fn return_type(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
|
|
@ -780,31 +799,27 @@ fn expression(p: &mut CParser) {
|
|||
expression_with_power(p, 0)
|
||||
}
|
||||
|
||||
// BINDING POWERS. When parsing expressions we only accept expressions that
|
||||
// meet a minimum binding power. (This is like "precedence" but I just super
|
||||
// don't like that terminology.)
|
||||
const ASSIGNMENT_POWER: u8 = 0; // =
|
||||
const OR_POWER: u8 = 1; // or
|
||||
const AND_POWER: u8 = 2; // and
|
||||
const EQUALITY_POWER: u8 = 3; // == !=
|
||||
const COMPARISON_POWER: u8 = 4; // < > <= >=
|
||||
const TERM_POWER: u8 = 5; // + -
|
||||
const FACTOR_POWER: u8 = 6; // * /
|
||||
const UNARY_POWER: u8 = 7; // ! -
|
||||
const UNARY_POWER: u8 = 14;
|
||||
|
||||
// const PRIMARY_POWER: u8 = 9;
|
||||
|
||||
fn token_power<'a>(token: TokenKind) -> Option<u8> {
|
||||
fn infix_power(token: TokenKind) -> Option<(u8, u8)> {
|
||||
// A dumb thing: the pair controls associativity.
|
||||
//
|
||||
// If lhs < rhs then it's left-associative, otherwise it's
|
||||
// right-associative.
|
||||
match token {
|
||||
TokenKind::Equal => Some(ASSIGNMENT_POWER),
|
||||
TokenKind::Or => Some(OR_POWER),
|
||||
TokenKind::And => Some(AND_POWER),
|
||||
TokenKind::EqualEqual | TokenKind::BangEqual => Some(EQUALITY_POWER),
|
||||
TokenKind::Equal => Some((1, 0)),
|
||||
TokenKind::Or => Some((2, 3)),
|
||||
TokenKind::And => Some((4, 5)),
|
||||
TokenKind::EqualEqual | TokenKind::BangEqual => Some((6, 7)),
|
||||
TokenKind::Less | TokenKind::Greater | TokenKind::GreaterEqual | TokenKind::LessEqual => {
|
||||
Some(COMPARISON_POWER)
|
||||
Some((8, 9))
|
||||
}
|
||||
TokenKind::Plus | TokenKind::Minus => Some(TERM_POWER),
|
||||
TokenKind::Star | TokenKind::Slash => Some(FACTOR_POWER),
|
||||
TokenKind::Plus | TokenKind::Minus => Some((10, 11)),
|
||||
TokenKind::Star | TokenKind::Slash => Some((12, 13)),
|
||||
//
|
||||
// UNARY_POWER goes here.
|
||||
//
|
||||
TokenKind::Dot => Some((16, 17)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -818,10 +833,11 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) {
|
|||
}
|
||||
|
||||
loop {
|
||||
let Some(power) = token_power(p.peek()) else {
|
||||
let token = p.peek();
|
||||
let Some((lp, rp)) = infix_power(token) else {
|
||||
break;
|
||||
};
|
||||
if power < minimum_power {
|
||||
if lp < minimum_power {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -829,18 +845,15 @@ fn expression_with_power(p: &mut CParser, minimum_power: u8) {
|
|||
// see won't we.
|
||||
let m = p.start_before(expr);
|
||||
p.advance(); // Consume the operator
|
||||
expression_with_power(p, power);
|
||||
expr = p.end(m, TreeKind::BinaryExpression);
|
||||
}
|
||||
|
||||
while p.at(TokenKind::Dot) {
|
||||
let m = p.start_before(expr);
|
||||
p.advance(); // Consume the dot
|
||||
p.expect(
|
||||
TokenKind::Identifier,
|
||||
"expected an identifier for member access",
|
||||
expression_with_power(p, rp);
|
||||
expr = p.end(
|
||||
m,
|
||||
if token == TokenKind::Dot {
|
||||
TreeKind::MemberAccess
|
||||
} else {
|
||||
TreeKind::BinaryExpression
|
||||
},
|
||||
);
|
||||
expr = p.end(m, TreeKind::MemberAccess);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -885,6 +898,7 @@ fn prefix_expression(p: &mut CParser) -> MarkClosed {
|
|||
TokenKind::If => conditional(p),
|
||||
|
||||
TokenKind::Identifier => identifier(p),
|
||||
TokenKind::Selff => self_reference(p),
|
||||
|
||||
TokenKind::LeftBracket => list_constructor(p),
|
||||
|
||||
|
|
@ -946,6 +960,15 @@ fn identifier(p: &mut CParser) -> MarkClosed {
|
|||
p.end(m, TreeKind::Identifier)
|
||||
}
|
||||
|
||||
fn self_reference(p: &mut CParser) -> MarkClosed {
|
||||
assert!(p.at(TokenKind::Selff));
|
||||
let m = p.start();
|
||||
|
||||
p.advance();
|
||||
|
||||
p.end(m, TreeKind::SelfReference)
|
||||
}
|
||||
|
||||
fn list_constructor(p: &mut CParser) -> MarkClosed {
|
||||
let m = p.start();
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -2,22 +2,31 @@ class Point {
|
|||
x: f64;
|
||||
y: f64;
|
||||
|
||||
fun something_static() -> f64 {
|
||||
12
|
||||
}
|
||||
// fun something_static() -> f64 {
|
||||
// 12
|
||||
// }
|
||||
|
||||
fun square_length(self) -> f64 {
|
||||
self.x * self.x + self.y * self.y
|
||||
}
|
||||
// fun square_length(self) -> f64 {
|
||||
// self.x * self.x + self.y * self.y
|
||||
// }
|
||||
}
|
||||
|
||||
class Line {
|
||||
start: Point;
|
||||
end: Point;
|
||||
}
|
||||
|
||||
fun test() -> f64 {
|
||||
let pt = new Point { x: 7, y: 23 };
|
||||
let z = pt.x + pt.square_length() + Point::something_static();
|
||||
let line = new Line {
|
||||
start: new Point { x: 7, y: 23 },
|
||||
end: new Point { x: 999, y: 99 },
|
||||
};
|
||||
|
||||
let z = line.start.x;// + pt.square_length() + Point::something_static();
|
||||
z
|
||||
}
|
||||
|
||||
// @ignore WIP: Methods
|
||||
/// @ignore WIP: Methods
|
||||
// @no-errors
|
||||
// @eval: Float(7.0)
|
||||
// @compiles-to:
|
||||
|
|
@ -35,17 +44,33 @@ fun test() -> f64 {
|
|||
// | 2: PushString(0)
|
||||
// | 3: NewObject(2)
|
||||
// | 4: Return
|
||||
// | function Line (4 args, 0 locals):
|
||||
// | strings (1):
|
||||
// | 0: Line
|
||||
// | code (5):
|
||||
// | 0: LoadArgument(1)
|
||||
// | 1: LoadArgument(0)
|
||||
// | 2: PushString(0)
|
||||
// | 3: NewObject(2)
|
||||
// | 4: Return
|
||||
// | function test (0 args, 2 locals):
|
||||
// | strings (0):
|
||||
// | code (10):
|
||||
// | 0: PushFloat(23.0)
|
||||
// | 1: PushFloat(7.0)
|
||||
// | code (17):
|
||||
// | 0: PushFloat(99.0)
|
||||
// | 1: PushFloat(999.0)
|
||||
// | 2: LoadFunction(1)
|
||||
// | 3: Call(2)
|
||||
// | 4: StoreLocal(0)
|
||||
// | 5: LoadLocal(0)
|
||||
// | 6: LoadSlot(0)
|
||||
// | 7: StoreLocal(1)
|
||||
// | 8: LoadLocal(1)
|
||||
// | 9: Return
|
||||
// | 4: PushFloat(23.0)
|
||||
// | 5: PushFloat(7.0)
|
||||
// | 6: LoadFunction(1)
|
||||
// | 7: Call(2)
|
||||
// | 8: LoadFunction(2)
|
||||
// | 9: Call(2)
|
||||
// | 10: StoreLocal(0)
|
||||
// | 11: LoadLocal(0)
|
||||
// | 12: LoadSlot(0)
|
||||
// | 13: LoadSlot(0)
|
||||
// | 14: StoreLocal(1)
|
||||
// | 15: LoadLocal(1)
|
||||
// | 16: Return
|
||||
// |
|
||||
|
|
|
|||
18
fine/tests/expression/errors/wild_member_access.fine
Normal file
18
fine/tests/expression/errors/wild_member_access.fine
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
class Foo {
|
||||
foo: f64;
|
||||
}
|
||||
|
||||
fun test() {
|
||||
let f = new Foo { foo: 12 };
|
||||
let z = f.{let y = 222; foo };
|
||||
}
|
||||
|
||||
// NOTE: The AST allows for generic expressions to the right of the dot.
|
||||
// We need to make sure things are parsed correctly.
|
||||
//
|
||||
// TODO: Better parser recovery will improve the specifics of the errors.
|
||||
// @expect-errors:
|
||||
// | 7:12: Error at '{': expected an expression
|
||||
// | 7:13: Error at 'let': expect ';' to end a let statement
|
||||
// | 7:26: cannot find value foo here
|
||||
// | 8:0: Error at '}': unbalanced '}'
|
||||
Loading…
Add table
Add a link
Reference in a new issue