feat: More Shape derivations

Some BuildError bits as well.
This commit is contained in:
Jeremy Wall 2023-08-21 18:24:46 -04:00 committed by Jeremy Wall
parent 0e93ffb27b
commit 3e8771476f
4 changed files with 235 additions and 193 deletions

View File

@ -19,7 +19,8 @@ use std::cmp::Eq;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::cmp::PartialEq; use std::cmp::PartialEq;
use std::cmp::PartialOrd; use std::cmp::PartialOrd;
use std::convert::{Into, TryFrom, TryInto}; use std::collections::BTreeMap;
use std::convert::{Into, TryFrom};
use std::fmt; use std::fmt;
use std::hash::Hash; use std::hash::Hash;
use std::hash::Hasher; use std::hash::Hasher;
@ -30,9 +31,6 @@ use abortable_parser;
use crate::build::scope::Scope; use crate::build::scope::Scope;
use crate::build::Val; use crate::build::Val;
use crate::error::{BuildError, ErrorType::TypeFail};
use self::walk::Walker;
pub mod printer; pub mod printer;
pub mod rewrite; pub mod rewrite;
@ -79,9 +77,9 @@ impl Position {
pub fn new(line: usize, column: usize, offset: usize) -> Self { pub fn new(line: usize, column: usize, offset: usize) -> Self {
Position { Position {
file: None, file: None,
line: line, line,
column: column, column,
offset: offset, offset,
} }
} }
@ -140,9 +138,9 @@ impl Token {
// Constructs a new Token with a type and a Position. // Constructs a new Token with a type and a Position.
pub fn new_with_pos<S: Into<String>>(f: S, typ: TokenType, pos: Position) -> Self { pub fn new_with_pos<S: Into<String>>(f: S, typ: TokenType, pos: Position) -> Self {
Token { Token {
typ: typ, typ,
fragment: f.into(), fragment: f.into(),
pos: pos, pos,
} }
} }
} }
@ -231,7 +229,7 @@ pub struct FuncShapeDef {
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub struct ModuleShapeDef { pub struct ModuleShape {
items: TupleShape, items: TupleShape,
ret: Box<Shape>, ret: Box<Shape>,
} }
@ -251,8 +249,8 @@ pub enum Value {
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub enum ImportShape { pub enum ImportShape {
Resolved(TupleShape), Resolved(Position, TupleShape),
Unresolved(PositionedItem<String>) Unresolved(PositionedItem<String>),
} }
#[doc = "Shapes represent the types that UCG values or expressions can have."] #[doc = "Shapes represent the types that UCG values or expressions can have."]
@ -266,39 +264,43 @@ pub enum Shape {
Tuple(PositionedItem<TupleShape>), Tuple(PositionedItem<TupleShape>),
List(PositionedItem<ShapeList>), List(PositionedItem<ShapeList>),
Func(FuncShapeDef), Func(FuncShapeDef),
Module(ModuleShapeDef), Module(ModuleShape),
Hole(PositionedItem<String>), // A type hole We don't know what this type is yet. Hole(PositionedItem<String>), // A type hole We don't know what this type is yet.
Import(ImportShape), // A type hole We don't know what this type is yet. Narrowed(PositionedItem<Vec<Shape>>), // A narrowed type. We know *some* of the possible options.
TypeErr(pos, BuildError), // A type hole We don't know what this type is yet. Import(ImportShape), // A type hole We don't know what this type is yet.
TypeErr(Position, String), // A type hole We don't know what this type is yet.
} }
impl Shape { impl Shape {
pub fn resolve(&self, right: &Shape) -> Option<Self> { pub fn narrow(&self, right: &Shape) -> Self {
Some(match (self, right) { dbg!((self, right));
dbg!(match (self, right) {
(Shape::Str(_), Shape::Str(_)) (Shape::Str(_), Shape::Str(_))
| (Shape::Boolean(_), Shape::Boolean(_)) | (Shape::Boolean(_), Shape::Boolean(_))
| (Shape::Empty(_), Shape::Empty(_)) | (Shape::Empty(_), Shape::Empty(_))
| (Shape::Int(_), Shape::Int(_)) | (Shape::Int(_), Shape::Int(_))
| (Shape::Float(_), Shape::Float(_)) => self.clone(), | (Shape::Float(_), Shape::Float(_)) => self.clone(),
(Shape::Hole(_), other) (Shape::Hole(_), other) | (other, Shape::Hole(_)) => other.clone(),
| (other, Shape::Hole(_)) => other.clone(),
(Shape::List(left_slist), Shape::List(right_slist)) => { (Shape::List(left_slist), Shape::List(right_slist)) => {
// TODO // TODO
unimplemented!("Can't merge these yet.") unimplemented!("Can't merge these yet.");
} }
(Shape::Tuple(left_slist), Shape::Tuple(right_slist)) => { (Shape::Tuple(left_slist), Shape::Tuple(right_slist)) => {
// TODO // TODO
unimplemented!("Can't merge these yet.") unimplemented!("Can't merge these yet.");
} }
(Shape::Func(left_opshape), Shape::Func(right_opshape)) => { (Shape::Func(left_opshape), Shape::Func(right_opshape)) => {
// TODO // TODO
unimplemented!("Can't merge these yet.") unimplemented!("Can't merge these yet.");
} }
(Shape::Module(left_opshape), Shape::Module(right_opshape)) => { (Shape::Module(left_opshape), Shape::Module(right_opshape)) => {
// TODO // TODO
unimplemented!("Can't merge these yet.") unimplemented!("Can't merge these yet.");
} }
_ => return None, _ => Shape::TypeErr(
right.pos().clone(),
format!("Expected {} but got {}", self.type_name(), right.type_name()),
),
}) })
} }
@ -314,7 +316,10 @@ impl Shape {
Shape::Tuple(flds) => "tuple", Shape::Tuple(flds) => "tuple",
Shape::Func(_) => "func", Shape::Func(_) => "func",
Shape::Module(_) => "module", Shape::Module(_) => "module",
Shape::Narrowed(_) => "narrowed",
Shape::Import(_) => "import",
Shape::Hole(_) => "type-hole", Shape::Hole(_) => "type-hole",
Shape::TypeErr(_, _) => "type-error",
} }
} }
@ -329,7 +334,11 @@ impl Shape {
Shape::Tuple(flds) => &flds.pos, Shape::Tuple(flds) => &flds.pos,
Shape::Func(def) => def.ret.pos(), Shape::Func(def) => def.ret.pos(),
Shape::Module(def) => def.ret.pos(), Shape::Module(def) => def.ret.pos(),
Shape::Narrowed(pi) => &pi.pos,
Shape::Hole(pi) => &pi.pos, Shape::Hole(pi) => &pi.pos,
Shape::TypeErr(pos, _) => pos,
Shape::Import(ImportShape::Resolved(p, _)) => p,
Shape::Import(ImportShape::Unresolved(pi)) => &pi.pos,
} }
} }
@ -343,7 +352,15 @@ impl Shape {
Shape::List(lst) => Shape::List(PositionedItem::new(lst.val, pos)), Shape::List(lst) => Shape::List(PositionedItem::new(lst.val, pos)),
Shape::Tuple(flds) => Shape::Tuple(PositionedItem::new(flds.val, pos)), Shape::Tuple(flds) => Shape::Tuple(PositionedItem::new(flds.val, pos)),
Shape::Func(_) | Shape::Module(_) => self.clone(), Shape::Func(_) | Shape::Module(_) => self.clone(),
Shape::Hole(_) => Shape::Hole(pos), Shape::Narrowed(pi) => Shape::Narrowed(pi.with_pos(pos)),
Shape::Hole(pi) => Shape::Hole(pi.with_pos(pos)),
Shape::Import(ImportShape::Resolved(_, s)) => {
Shape::Import(ImportShape::Resolved(pos, s))
}
Shape::Import(ImportShape::Unresolved(pi)) => {
Shape::Import(ImportShape::Unresolved(pi.with_pos(pos)))
}
Shape::TypeErr(_, msg) => Shape::TypeErr(pos, msg),
} }
} }
} }
@ -423,28 +440,31 @@ impl Value {
) )
} }
fn derive_shape(&self) -> Shape { fn derive_shape(&self, symbol_table: &mut BTreeMap<String, Shape>) -> Shape {
let shape = match self { let shape = match self {
Value::Empty(p) => Shape::Empty(p.clone()), Value::Empty(p) => Shape::Empty(p.clone()),
Value::Boolean(p) => Shape::Boolean(p.clone()), Value::Boolean(p) => Shape::Boolean(p.clone()),
Value::Int(p) => Shape::Int(p.clone()), Value::Int(p) => Shape::Int(p.clone()),
Value::Float(p) => Shape::Float(p.clone()), Value::Float(p) => Shape::Float(p.clone()),
Value::Str(p) => Shape::Str(p.clone()), Value::Str(p) => Shape::Str(p.clone()),
// Symbols in a shape are placeholders. They allow a form of genericity Value::Symbol(p) => {
// in the shape. They can be any type and are only refined down. if let Some(s) = symbol_table.get(&p.val) {
// by their presence in an expression. s.clone()
Value::Symbol(p) => Shape::Hole(p.clone()), } else {
Shape::Hole(p.clone())
}
}
Value::Tuple(flds) => { Value::Tuple(flds) => {
let mut field_shapes = Vec::new(); let mut field_shapes = Vec::new();
for &(ref tok, ref expr) in &flds.val { for &(ref tok, ref expr) in &flds.val {
field_shapes.push((tok.clone(), expr.derive_shape())); field_shapes.push((tok.clone(), expr.derive_shape(symbol_table)));
} }
Shape::Tuple(PositionedItem::new(field_shapes, flds.pos.clone())) Shape::Tuple(PositionedItem::new(field_shapes, flds.pos.clone()))
} }
Value::List(flds) => { Value::List(flds) => {
let mut field_shapes = Vec::new(); let mut field_shapes = Vec::new();
for f in &flds.elems { for f in &flds.elems {
field_shapes.push(f.derive_shape()); field_shapes.push(f.derive_shape(symbol_table));
} }
Shape::List(PositionedItem::new(field_shapes, flds.pos.clone())) Shape::List(PositionedItem::new(field_shapes, flds.pos.clone()))
} }
@ -453,14 +473,6 @@ impl Value {
} }
} }
impl TryFrom<&Value> for Shape {
type Error = crate::error::BuildError;
fn try_from(v: &Value) -> Result<Self, Self::Error> {
Ok(v.derive_shape())
}
}
/// Represents an expansion of a Macro that is expected to already have been /// Represents an expansion of a Macro that is expected to already have been
/// defined. /// defined.
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
@ -532,7 +544,12 @@ impl<T> PositionedItem<T> {
/// Constructs a new Positioned<T> with a value and a Position. /// Constructs a new Positioned<T> with a value and a Position.
pub fn new_with_pos(v: T, pos: Position) -> Self { pub fn new_with_pos(v: T, pos: Position) -> Self {
PositionedItem { pos: pos, val: v } PositionedItem { pos, val: v }
}
pub fn with_pos(mut self, pos: Position) -> Self {
self.pos = pos;
self
} }
} }
@ -747,7 +764,7 @@ impl ModuleDef {
ModuleDef { ModuleDef {
scope: None, scope: None,
pos: pos.into(), pos: pos.into(),
arg_set: arg_set, arg_set,
out_expr: None, out_expr: None,
arg_tuple: None, arg_tuple: None,
statements: stmts, statements: stmts,
@ -862,29 +879,14 @@ impl Expression {
} }
} }
fn derive_shape(&self) -> Shape { fn derive_shape(&self, symbol_table: &mut BTreeMap<String, Shape>) -> Shape {
// FIXME(jwall): Implement this
let shape = match self { let shape = match self {
Expression::Simple(v) => v.derive_shape(), Expression::Simple(v) => v.derive_shape(symbol_table),
Expression::Format(def) => { Expression::Format(def) => {
Shape::Str(PositionedItem::new("".to_owned(), def.pos.clone())) Shape::Str(PositionedItem::new("".to_owned(), def.pos.clone()))
} }
Expression::Not(def) => { Expression::Not(def) => derive_not_shape(def, symbol_table),
let shape = def.expr.as_ref().derive_shape(); Expression::Grouped(v, _pos) => v.as_ref().derive_shape(symbol_table),
if let Shape::Boolean(b) = shape {
Shape::Boolean(PositionedItem::new(!b.val, def.pos.clone()))
} else {
// TODO(jwall): Display implementations for shapes.
return Shape::TypeErr(def.pos.clone(), BuildError::new(
format!(
"Expected Boolean value in Not expression but got: {:?}",
shape
),
TypeFail,
));
}
}
Expression::Grouped(v, _pos) => v.as_ref().derive_shape(),
Expression::Range(def) => Shape::List(PositionedItem::new( Expression::Range(def) => Shape::List(PositionedItem::new(
vec![Shape::Int(PositionedItem::new(0, def.start.pos().clone()))], vec![Shape::Int(PositionedItem::new(0, def.start.pos().clone()))],
def.pos.clone(), def.pos.clone(),
@ -900,48 +902,12 @@ impl Expression {
def.path.pos.clone(), def.path.pos.clone(),
))), ))),
Expression::Binary(def) => { Expression::Binary(def) => {
let left_shape = def.left.derive_shape(); let left_shape = def.left.derive_shape(symbol_table);
let right_shape = def.right.derive_shape(); let right_shape = def.right.derive_shape(symbol_table);
match left_shape.resolve(&right_shape) { left_shape.narrow(&right_shape)
Some(shape) => shape,
None => {
return Shape::TypeErr(def.pos.clone(), BuildError::new(
format!(
"Expected {} value on right hand side of expression but got: {}",
left_shape.type_name(),
right_shape.type_name()
),
TypeFail,
));
}
}
} }
Expression::Copy(def) => { Expression::Copy(def) => derive_copy_shape(def, symbol_table),
let base_shape = def.selector.derive_shape(); Expression::Include(def) => derive_include_shape(def),
match base_shape {
Shape::TypeErr(_, _) => base_shape,
Shape::Empty(_)
| Shape::Boolean(_)
| Shape::Int(_)
| Shape::Float(_)
| Shape::Str(_)
| Shape::List(_)
| Shape::Func(_) => Shape::TypeErr(def.pos.clone(), BuildError::new(format!("Not a Copyable type {}", base_shape.type_name()), TypeFail)),
// This is an interesting one. Do we assume tuple or module here?
// TODO(jwall): Maybe we want a Shape::Narrowed?
Shape::Hole(_) => todo!(),
// These have understandable ways to resolve the type.
Shape::Module(_) => todo!(),
Shape::Tuple(t_def) => {
let mut base_fields = t_def.clone();
base_fields.val.extend(def.fields.iter().map(|(tok, expr) | (tok.clone(), expr.derive_shape())));
Shape::Tuple(base_fields).with_pos(def.pos.clone())
},
Shape::Import(_) => todo!(),
}
},
Expression::Include(_) => todo!(),
Expression::Call(_) => todo!(), Expression::Call(_) => todo!(),
Expression::Func(_) => todo!(), Expression::Func(_) => todo!(),
Expression::Select(_) => todo!(), Expression::Select(_) => todo!(),
@ -954,11 +920,129 @@ impl Expression {
} }
} }
impl TryFrom<&Expression> for Shape { fn derive_include_shape(
type Error = crate::error::BuildError; IncludeDef {
pos,
path: _path,
typ: _typ,
}: &IncludeDef,
) -> Shape {
Shape::Narrowed(PositionedItem::new(
vec![
Shape::Tuple(PositionedItem::new(vec![], pos.clone())),
Shape::List(PositionedItem::new(vec![], pos.clone())),
],
pos,
))
}
fn try_from(e: &Expression) -> Result<Self, Self::Error> { fn derive_not_shape(def: &NotDef, symbol_table: &mut BTreeMap<String, Shape>) -> Shape {
Ok(e.derive_shape()) let shape = def.expr.as_ref().derive_shape(symbol_table);
if let Shape::Boolean(b) = shape {
Shape::Boolean(PositionedItem::new(!b.val, def.pos.clone()))
} else {
// TODO(jwall): Display implementations for shapes.
Shape::TypeErr(
def.pos.clone(),
format!(
"Expected Boolean value in Not expression but got: {:?}",
shape
),
)
}
}
fn derive_copy_shape(def: &CopyDef, symbol_table: &mut BTreeMap<String, Shape>) -> Shape {
let base_shape = def.selector.derive_shape(symbol_table);
match &base_shape {
// TODO(jwall): Should we allow a stack of these?
Shape::TypeErr(_, _) => base_shape,
Shape::Empty(_)
| Shape::Boolean(_)
| Shape::Int(_)
| Shape::Float(_)
| Shape::Str(_)
| Shape::List(_)
| Shape::Func(_) => Shape::TypeErr(
def.pos.clone(),
format!("Not a Copyable type {}", base_shape.type_name()),
),
// This is an interesting one. Do we assume tuple or module here?
// TODO(jwall): Maybe we want a Shape::Narrowed?
Shape::Hole(pi) => Shape::Narrowed(PositionedItem::new(
vec![
Shape::Tuple(PositionedItem::new(vec![], pi.pos.clone())),
Shape::Module(ModuleShape {
items: vec![],
ret: Box::new(Shape::Empty(pi.pos.clone())),
}),
Shape::Import(ImportShape::Unresolved(pi.clone())),
],
pi.pos.clone(),
)),
Shape::Narrowed(potentials) => {
// 1. Do the possible shapes include tuple, module, or import?
let filtered = potentials
.val
.iter()
.filter_map(|v| match v {
Shape::Tuple(_) | Shape::Module(_) | Shape::Import(_) | Shape::Hole(_) => {
Some(v.clone())
}
_ => None,
})
.collect::<Vec<Shape>>();
if !filtered.is_empty() {
// 1.1 Then return those and strip the others.
Shape::Narrowed(PositionedItem::new(filtered, def.pos.clone()))
} else {
// 2. Else return a type error
Shape::TypeErr(
def.pos.clone(),
format!("Not a Copyable type {}", base_shape.type_name()),
)
}
}
// These have understandable ways to resolve the type.
Shape::Module(mdef) => {
let arg_fields = def
.fields
.iter()
.map(|(tok, expr)| (tok.fragment.clone(), expr.derive_shape(symbol_table)))
.collect::<BTreeMap<String, Shape>>();
// 1. Do our copyable fields have the right names and shapes based on mdef.items.
for (tok, shape) in mdef.items.iter() {
if let Some(s) = arg_fields.get(&tok.fragment) {
if let Shape::TypeErr(pos, msg) = shape.narrow(s) {
return Shape::TypeErr(pos, msg);
}
}
}
// 1.1 If so then return the ret as our shape.
mdef.ret.as_ref().clone()
}
Shape::Tuple(t_def) => {
let mut base_fields = t_def.clone();
base_fields.val.extend(
def.fields
.iter()
.map(|(tok, expr)| (tok.clone(), expr.derive_shape(symbol_table))),
);
Shape::Tuple(base_fields).with_pos(def.pos.clone())
}
Shape::Import(ImportShape::Unresolved(_)) => Shape::Narrowed(PositionedItem::new(
vec![Shape::Tuple(PositionedItem::new(vec![], def.pos.clone()))],
def.pos.clone(),
)),
Shape::Import(ImportShape::Resolved(_, tuple_shape)) => {
let mut base_fields = tuple_shape.clone();
base_fields.extend(
def.fields
.iter()
.map(|(tok, expr)| (tok.clone(), expr.derive_shape(symbol_table))),
);
Shape::Tuple(PositionedItem::new(base_fields, def.pos.clone()))
}
} }
} }

View File

@ -1,3 +1,5 @@
use std::collections::BTreeMap;
// Copyright 2020 Jeremy Wall // Copyright 2020 Jeremy Wall
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
@ -19,30 +21,6 @@ use crate::iter::OffsetStrIter;
use crate::parse::expression; use crate::parse::expression;
use crate::tokenizer::tokenize; use crate::tokenizer::tokenize;
macro_rules! assert_shape {
($input:expr => $shape:expr) => {{
let iter = OffsetStrIter::new($input);
let tokenized = match tokenize(iter, None) {
Ok(toks) => toks,
Err(err) => panic!("failed to parse {} with error {}", $input, err),
};
let toks = SliceIter::new(&tokenized);
if let ParseResult::Complete(_, ref expr) = expression(toks) {
assert_eq!(
match expr.derive_shape() {
Ok(shape) => shape,
Err(err) => {
panic!("failed to parse {} with error {}", $input, err)
}
},
$shape
);
} else {
assert!(false, format!("failed to parse expression! {:?}", $input));
};
}};
}
#[test] #[test]
fn derive_shape_values() { fn derive_shape_values() {
let value_cases = vec![ let value_cases = vec![
@ -108,7 +86,7 @@ fn derive_shape_values() {
]; ];
for (val, shape) in value_cases { for (val, shape) in value_cases {
assert_eq!(val.derive_shape(), shape); assert_eq!(val.derive_shape(&mut BTreeMap::new()), shape);
} }
} }
@ -161,6 +139,18 @@ fn derive_shape_expressions() {
]; ];
for (expr, shape) in expr_cases { for (expr, shape) in expr_cases {
assert_shape!(expr => shape); {
let iter = OffsetStrIter::new(expr);
let tokenized = match tokenize(iter, None) {
Ok(toks) => toks,
Err(err) => panic!("failed to parse {} with error {}", expr, err),
};
let toks = SliceIter::new(&tokenized);
if let ParseResult::Complete(_, ref expr) = expression(toks) {
assert_eq!(expr.derive_shape(&mut BTreeMap::new()), shape);
} else {
assert!(false, "failed to parse expression! {:?}", expr);
};
};
} }
} }

View File

@ -106,48 +106,15 @@ impl Visitor for Checker {
} }
fn leave_expression(&mut self, expr: &Expression) { fn leave_expression(&mut self, expr: &Expression) {
match expr { let shape = expr.derive_shape(&mut self.symbol_table);
Expression::Binary(_) => { if let Shape::TypeErr(pos, msg) = &shape {
// Collapse the two shapes in the stack into one shape for this expression. self.err_stack.push(BuildError::with_pos(
if let Some(right) = self.shape_stack.pop() { msg.clone(),
if let Some(left) = self.shape_stack.pop() { ErrorType::TypeFail,
if let Some(shape) = left.resolve(&right) { pos.clone(),
// Then give them a new position ));
self.shape_stack.push(shape.with_pos(expr.pos().clone())); } else {
} else { self.shape_stack.push(shape);
self.err_stack.push(BuildError::with_pos(
format!(
"Expected {} but got {}",
left.type_name(),
right.type_name()
),
ErrorType::TypeFail,
right.pos().clone(),
));
}
}
}
}
Simple(val) => self.shape_stack.push(val.derive_shape()),
Not(def) => {
// TODO(jwall): We expect the result of def.expr to be boolean.
// If it isn't then we have a problem
todo!();
},
Copy(_) => todo!(),
Range(_) => todo!(),
Grouped(_, _) => todo!(),
Format(_) => todo!(),
Include(_) => todo!(),
Import(_) => todo!(),
Call(_) => todo!(),
Cast(_) => todo!(),
Func(_) => todo!(),
Select(_) => todo!(),
FuncOp(_) => todo!(),
Module(_) => todo!(),
Fail(_) => todo!(),
Debug(_) => todo!(),
} }
} }

View File

@ -4,7 +4,20 @@ use crate::ast::walk::Walker;
use crate::ast::Position; use crate::ast::Position;
use crate::parse; use crate::parse;
use super::Checker; use super::*;
macro_rules! assert_type_fail {
($e:expr, $msg:expr, $pos:expr) => {{
let mut checker = Checker::new();
let mut expr = parse($e.into(), None).unwrap();
checker.walk_statement_list(expr.iter_mut().collect());
let result = dbg!(checker.result());
assert!(result.is_err(), "We expect this to fail a typecheck.");
let err = result.unwrap_err();
assert_eq!(err.msg, $msg);
assert_eq!(err.pos.unwrap(), $pos);
}}
}
#[test] #[test]
fn simple_binary_typecheck() { fn simple_binary_typecheck() {
@ -20,28 +33,16 @@ fn simple_binary_typecheck() {
); );
} }
// TODO Test that leverages symbol tables and let bindings.
#[test] #[test]
fn simple_binary_typefail() { fn simple_binary_typefail() {
let mut checker = Checker::new(); assert_type_fail!("1 + true;", "Expected int but got boolean", Position::new(1, 5, 4));
let expr_str = "1 + true;"; assert_type_fail!("1 + \"\";", "Expected int but got str", Position::new(1, 5, 4));
let mut expr = parse(expr_str.into(), None).unwrap(); assert_type_fail!("1 + [];", "Expected int but got list", Position::new(1, 5, 4));
checker.walk_statement_list(expr.iter_mut().collect()); assert_type_fail!("1 + {};", "Expected int but got tuple", Position::new(1, 5, 4));
let result = checker.result();
assert!(result.is_err(), "We expect this to fail a typecheck.");
let err = result.unwrap_err();
assert_eq!(err.msg, "Expected int but got boolean");
assert_eq!(err.pos.unwrap(), Position::new(1, 5, 4));
} }
#[test] #[test]
fn multiple_binary_typefail() { fn multiple_binary_typefail() {
let mut checker = Checker::new(); assert_type_fail!("1 + 1 + true;", "Expected int but got boolean", Position::new(1, 9, 8));
let expr_str = "1 + 1 + true;";
let mut expr = parse(expr_str.into(), None).unwrap();
checker.walk_statement_list(expr.iter_mut().collect());
let result = checker.result();
assert!(result.is_err(), "We expect this to fail a typecheck.");
let err = result.unwrap_err();
assert_eq!(err.msg, "Expected int but got boolean");
assert_eq!(err.pos.unwrap(), Position::new(1, 9, 8));
} }