[fine] Many test improvements, error improvements

- Check for more error conditions
- Move to more definitive error assertions
- Simpler error messages in some cases
- Test more conditions more thoroughly, revisit old tests
This commit is contained in:
John Doty 2024-01-21 08:14:42 -08:00
parent d0b74db715
commit 5f0a0b3268
20 changed files with 186 additions and 118 deletions

View file

@ -308,7 +308,6 @@ fn compile_literal(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
Instruction::PushFalse
}),
Type::String => {
// TODO: Interpret string here make good!
let mut result = String::new();
let mut input = tok.as_str().chars();
while let Some(ch) = input.next() {
@ -514,7 +513,6 @@ fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> O
}
fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declaration) -> CR {
// TODO: Load function declaration. :P
let instruction = match declaration {
Declaration::Variable {
location, index, ..
@ -555,6 +553,8 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
Instruction::LoadFunction(index)
}
Declaration::ExternFunction { id, .. } => Instruction::LoadExternFunction(id.id()),
// There is no universe where it's possible to use a class as a variable.
Declaration::Class { .. } => Instruction::Panic,
};

View file

@ -510,7 +510,6 @@ fn file(p: &mut CParser) {
let m = p.start();
while !p.eof() {
match p.peek() {
TokenKind::Fun => function(p),
TokenKind::Class => class(p),
_ => statement(p),
}
@ -660,6 +659,7 @@ fn block(p: &mut CParser) {
fn statement(p: &mut CParser) {
match p.peek() {
TokenKind::Fun => function(p),
TokenKind::LeftBrace => block(p),
TokenKind::Let => statement_let(p),
TokenKind::Return => statement_return(p),
@ -988,11 +988,14 @@ mod tests {
#[test]
fn tree_ref_size() {
// What's the point of doing all that work if the tree ref isn't nice
// and "small"?
// and "small"? TreeRef is pervasive throughout the system: we use
// them to key function definitions and the type checker and use them
// to link classes to their definitions, etc. It's important that an
// Option<TreeRef> be *extremely* cheap to manipulate.
//
// TODO: This is a dumb optimization because tokens are
// TODO: This optimization isn't as good as it might be because tokens are
// huge so Child is huge no matter what we do. If we retain
// tokens out of line then we can re-visit this optimization.
// tokens out of line then we can take full advantage of this.
assert_eq!(4, std::mem::size_of::<Option<TreeRef>>());
}
}

View file

@ -86,9 +86,6 @@ pub enum Type {
// chain assignments, and so we flow the type of the assignment through.)
Assignment(Box<Type>),
// This is until generics are working
MagicPrintGarbage,
// An potentially-bound type variable.
// We need to ... like ... unify these things if possible.
TypeVariable(TreeRef),
@ -131,7 +128,6 @@ impl fmt::Display for Type {
F64 => write!(f, "f64"),
String => write!(f, "string"),
Bool => write!(f, "bool"),
MagicPrintGarbage => write!(f, "MagicPrintGarbage"),
Function(args, ret) => {
write!(f, "fun (")?;
let mut first = true;
@ -224,8 +220,8 @@ impl Environment {
}
}
pub fn insert(&mut self, token: &Token, t: Type) {
self.declarations.insert(
pub fn insert(&mut self, token: &Token, t: Type) -> Option<Declaration> {
let result = self.declarations.insert(
token.as_str().into(),
Declaration::Variable {
declaration_type: t,
@ -234,6 +230,7 @@ impl Environment {
},
);
self.next_index += 1;
result
}
pub fn bind(&self, token: &Token) -> Option<&Declaration> {
@ -387,14 +384,7 @@ impl<'a> Semantics<'a> {
set_logical_parents(&mut logical_parents, tree, root, None);
}
let mut root_environment = Environment::new(None, Location::Module);
root_environment.declarations.insert(
"print".into(),
Declaration::ExternFunction {
declaration_type: Type::MagicPrintGarbage,
id: ExternalFunctionId(0),
},
);
let root_environment = Environment::new(None, Location::Module);
let mut semantics = Semantics {
syntax_tree: tree,
@ -526,23 +516,6 @@ impl<'a> Semantics<'a> {
TreeKind::ForStatement => self.environment_of_for(parent, tree),
// TODO: Blocks should introduce a local environment if required.
// Test with a type error in a block statement and a
// binding outside. You will need a new assertion type and
// possibly a compile/run to ensure it works.
//
// let x = 7;
// {
// let x = 23;
// }
// print(x); // 7
//
// {
// let y = 12; // check: `y` is local not global!
// }
// print(y); // error, cannot find 'y'
// TODO: MORE Things that introduce an environment!
_ => parent,
};
@ -558,18 +531,23 @@ impl<'a> Semantics<'a> {
Child::Tree(t) => {
let ct = &self.syntax_tree[*t];
if ct.kind == TreeKind::FunctionDecl {
// TODO: Should I have accessors for function decls?
let Some(name) = ct.nth_token(1) else {
continue;
};
environment.declarations.insert(
let existing = environment.declarations.insert(
name.as_str().into(),
Declaration::Function {
declaration_type: self.type_of(*t),
declaration: *t,
},
);
if existing.is_some() {
self.report_error_tree(
ct,
format!("duplicate definition of function '{name}'"),
);
}
}
}
_ => {}
@ -585,35 +563,42 @@ impl<'a> Semantics<'a> {
match child {
Child::Tree(t) => {
let ct = &self.syntax_tree[*t];
match ct.kind {
let binding = match ct.kind {
TreeKind::FunctionDecl => {
// TODO: Should I have accessors for function decls?
let Some(name) = ct.nth_token(1) else {
continue;
};
environment.declarations.insert(
name.as_str().into(),
Declaration::Function {
declaration_type: self.type_of(*t),
declaration: *t,
},
);
let declaration = Declaration::Function {
declaration_type: self.type_of(*t),
declaration: *t,
};
Some(("function", name, declaration))
}
TreeKind::ClassDecl => {
let Some(name) = ct.nth_token(1) else {
continue;
};
environment.declarations.insert(
name.as_str().into(),
Declaration::Class {
declaration_type: self.type_of(*t),
declaration: *t,
},
let declaration = Declaration::Class {
declaration_type: self.type_of(*t),
declaration: *t,
};
Some(("class", name, declaration))
}
_ => None,
};
if let Some((what, name, declaration)) = binding {
let existing = environment
.declarations
.insert(name.as_str().into(), declaration);
if existing.is_some() {
self.report_error_tree(
ct,
format!("duplicate definition of {what} '{name}'"),
);
}
_ => {}
}
}
_ => {}
@ -667,7 +652,12 @@ impl<'a> Semantics<'a> {
};
let declaration_type = self.type_of(*ct);
environment.insert(param_name, declaration_type);
if environment.insert(param_name, declaration_type).is_some() {
self.report_error_tree(
param,
format!("duplicate definition of parameter '{param_name}'"),
);
}
}
EnvironmentRef::new(environment)
@ -948,7 +938,7 @@ impl<'a> Semantics<'a> {
} else {
self.report_error(
op.start,
format!("cannot assign a value of type `{right_type}` to type `{left_type}`"),
format!("cannot assign a value of type '{right_type}' to type '{left_type}'"),
);
Some(Type::Error)
}
@ -1106,7 +1096,7 @@ impl<'a> Semantics<'a> {
if !self.type_compat(&then_type, &else_type) {
self.report_error_tree(
tree,
format!("the type of the `then` branch ({then_type}) must match the type of the `else` branch ({else_type})"),
format!("the type of the 'then' branch ('{then_type}') must match the type of the 'else' branch ('{else_type}')"),
);
Some(Type::Error)
} else {
@ -1172,18 +1162,6 @@ impl<'a> Semantics<'a> {
Some(*ret.clone())
}
Type::MagicPrintGarbage => {
if arg_types.len() > 1 {
self.report_error_tree(tree, "print takes a single argument");
Some(Type::Error)
} else if arg_types.len() == 0 {
Some(Type::Nothing)
} else {
let mut arg_types = arg_types;
let (_, t) = arg_types.pop().unwrap();
Some(t)
}
}
_ => {
self.report_error_tree_ref(f_ref, format!("expected a function type, got: {f}"));
Some(Type::Error)
@ -1266,8 +1244,7 @@ impl<'a> Semantics<'a> {
declaration_type, ..
} => declaration_type.clone(),
Declaration::Class { .. } => {
// TODO: Test this case
self.report_error_tree(tree, format!("{id} is a class, not a value (did you mean to create a new instance with `new`?)"));
self.report_error_tree(tree, format!("{id} is a class, not a value (did you mean to create a new instance with 'new'?)"));
Type::Error
}
});
@ -1396,7 +1373,7 @@ impl<'a> Semantics<'a> {
Declaration::Class { .. } => {
self.report_error_tree(
tree,
format!("`{id}` is a class, and cannot be the value of a field"),
format!("'{id}' is a class, and cannot be the value of a field"),
);
Some(Type::Error)
}
@ -1500,7 +1477,9 @@ pub fn check(s: &Semantics) {
TreeKind::Error => {} // already reported
TreeKind::File => {}
TreeKind::FunctionDecl => check_function_decl(s, t, tree),
TreeKind::ParamList => {}
TreeKind::ParamList => {
let _ = s.environment_of(t);
}
TreeKind::Parameter => {
let _ = s.type_of(t);
}
@ -1546,7 +1525,7 @@ pub fn check(s: &Semantics) {
}
TreeKind::ForStatement => check_for_statement(s, t),
TreeKind::ClassDecl => {}
TreeKind::ClassDecl => check_class_declaration(s, tree),
TreeKind::FieldDecl => {}
TreeKind::FieldList => {}
TreeKind::NewObjectExpression => check_new_object_expression(s, tree),
@ -1582,7 +1561,7 @@ fn check_function_decl(s: &Semantics, t: TreeRef, tree: &Tree) {
(start, end_pos)
});
s.report_error_span(start, end, format!("the body of this function yields a value of type `{body_type}`, but callers expect this function to produce a `{return_type}`"));
s.report_error_span(start, end, format!("the body of this function yields a value of type '{body_type}', but callers expect this function to produce a '{return_type}'"));
}
}
}
@ -1617,7 +1596,7 @@ fn check_return_statement(s: &Semantics, tree: &Tree) {
};
if !s.type_compat(&expected_type, &actual_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}`"));
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}'"));
}
}
Type::Error => (),
@ -1634,11 +1613,6 @@ 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;
@ -1700,6 +1674,22 @@ fn check_new_object_expression(s: &Semantics, tree: &Tree) {
}
}
fn check_class_declaration(s: &Semantics, tree: &Tree) {
let mut fields = HashMap::new();
for field in tree.children_of_kind(s.syntax_tree, TreeKind::FieldDecl) {
let f = &s.syntax_tree[field];
let Some(name) = f.nth_token(0) else {
continue;
};
match fields.insert(name.as_str(), field) {
Some(_) => {
s.report_error_tree(f, format!("duplicate definition of field '{name}'"));
}
None => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -13,7 +13,8 @@ pub enum VMErrorCode {
#[error("internal error: stack type mismatch: {0:?} is not {1:?}")]
StackTypeMismatch(StackValue, Type),
// TODO: This one is *not* like the others!
// TODO: This one is *not* like the others! Distinguish between internal
// errors and user errors?
#[error("divide by zero")]
DivideByZero,