From 3e8771476f3a725d20c6cd3fd1195c950b573312 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Mon, 21 Aug 2023 18:24:46 -0400 Subject: [PATCH] feat: More Shape derivations Some BuildError bits as well. --- src/ast/mod.rs | 296 ++++++++++++++++++++++++-------------- src/ast/test.rs | 42 +++--- src/ast/typecheck/mod.rs | 51 ++----- src/ast/typecheck/test.rs | 39 ++--- 4 files changed, 235 insertions(+), 193 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index ab0c0b2..7ce0f26 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -19,7 +19,8 @@ use std::cmp::Eq; use std::cmp::Ordering; use std::cmp::PartialEq; use std::cmp::PartialOrd; -use std::convert::{Into, TryFrom, TryInto}; +use std::collections::BTreeMap; +use std::convert::{Into, TryFrom}; use std::fmt; use std::hash::Hash; use std::hash::Hasher; @@ -30,9 +31,6 @@ use abortable_parser; use crate::build::scope::Scope; use crate::build::Val; -use crate::error::{BuildError, ErrorType::TypeFail}; - -use self::walk::Walker; pub mod printer; pub mod rewrite; @@ -79,9 +77,9 @@ impl Position { pub fn new(line: usize, column: usize, offset: usize) -> Self { Position { file: None, - line: line, - column: column, - offset: offset, + line, + column, + offset, } } @@ -140,9 +138,9 @@ impl Token { // Constructs a new Token with a type and a Position. pub fn new_with_pos>(f: S, typ: TokenType, pos: Position) -> Self { Token { - typ: typ, + typ, fragment: f.into(), - pos: pos, + pos, } } } @@ -231,7 +229,7 @@ pub struct FuncShapeDef { } #[derive(PartialEq, Debug, Clone)] -pub struct ModuleShapeDef { +pub struct ModuleShape { items: TupleShape, ret: Box, } @@ -251,8 +249,8 @@ pub enum Value { #[derive(PartialEq, Debug, Clone)] pub enum ImportShape { - Resolved(TupleShape), - Unresolved(PositionedItem) + Resolved(Position, TupleShape), + Unresolved(PositionedItem), } #[doc = "Shapes represent the types that UCG values or expressions can have."] @@ -266,39 +264,43 @@ pub enum Shape { Tuple(PositionedItem), List(PositionedItem), Func(FuncShapeDef), - Module(ModuleShapeDef), + Module(ModuleShape), Hole(PositionedItem), // 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(pos, BuildError), // A type hole We don't know what this type is yet. + Narrowed(PositionedItem>), // A narrowed type. We know *some* of the possible options. + 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 { - pub fn resolve(&self, right: &Shape) -> Option { - Some(match (self, right) { + pub fn narrow(&self, right: &Shape) -> Self { + dbg!((self, right)); + dbg!(match (self, right) { (Shape::Str(_), Shape::Str(_)) | (Shape::Boolean(_), Shape::Boolean(_)) | (Shape::Empty(_), Shape::Empty(_)) | (Shape::Int(_), Shape::Int(_)) | (Shape::Float(_), Shape::Float(_)) => self.clone(), - (Shape::Hole(_), other) - | (other, Shape::Hole(_)) => other.clone(), + (Shape::Hole(_), other) | (other, Shape::Hole(_)) => other.clone(), (Shape::List(left_slist), Shape::List(right_slist)) => { // TODO - unimplemented!("Can't merge these yet.") + unimplemented!("Can't merge these yet."); } (Shape::Tuple(left_slist), Shape::Tuple(right_slist)) => { // TODO - unimplemented!("Can't merge these yet.") + unimplemented!("Can't merge these yet."); } (Shape::Func(left_opshape), Shape::Func(right_opshape)) => { // TODO - unimplemented!("Can't merge these yet.") + unimplemented!("Can't merge these yet."); } (Shape::Module(left_opshape), Shape::Module(right_opshape)) => { // 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::Func(_) => "func", Shape::Module(_) => "module", + Shape::Narrowed(_) => "narrowed", + Shape::Import(_) => "import", Shape::Hole(_) => "type-hole", + Shape::TypeErr(_, _) => "type-error", } } @@ -329,7 +334,11 @@ impl Shape { Shape::Tuple(flds) => &flds.pos, Shape::Func(def) => def.ret.pos(), Shape::Module(def) => def.ret.pos(), + Shape::Narrowed(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::Tuple(flds) => Shape::Tuple(PositionedItem::new(flds.val, pos)), 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) -> Shape { let shape = match self { Value::Empty(p) => Shape::Empty(p.clone()), Value::Boolean(p) => Shape::Boolean(p.clone()), Value::Int(p) => Shape::Int(p.clone()), Value::Float(p) => Shape::Float(p.clone()), Value::Str(p) => Shape::Str(p.clone()), - // Symbols in a shape are placeholders. They allow a form of genericity - // in the shape. They can be any type and are only refined down. - // by their presence in an expression. - Value::Symbol(p) => Shape::Hole(p.clone()), + Value::Symbol(p) => { + if let Some(s) = symbol_table.get(&p.val) { + s.clone() + } else { + Shape::Hole(p.clone()) + } + } Value::Tuple(flds) => { let mut field_shapes = Vec::new(); 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())) } Value::List(flds) => { let mut field_shapes = Vec::new(); 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())) } @@ -453,14 +473,6 @@ impl Value { } } -impl TryFrom<&Value> for Shape { - type Error = crate::error::BuildError; - - fn try_from(v: &Value) -> Result { - Ok(v.derive_shape()) - } -} - /// Represents an expansion of a Macro that is expected to already have been /// defined. #[derive(PartialEq, Debug, Clone)] @@ -532,7 +544,12 @@ impl PositionedItem { /// Constructs a new Positioned with a value and a Position. 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 { scope: None, pos: pos.into(), - arg_set: arg_set, + arg_set, out_expr: None, arg_tuple: None, statements: stmts, @@ -862,29 +879,14 @@ impl Expression { } } - fn derive_shape(&self) -> Shape { - // FIXME(jwall): Implement this + fn derive_shape(&self, symbol_table: &mut BTreeMap) -> Shape { let shape = match self { - Expression::Simple(v) => v.derive_shape(), + Expression::Simple(v) => v.derive_shape(symbol_table), Expression::Format(def) => { Shape::Str(PositionedItem::new("".to_owned(), def.pos.clone())) } - Expression::Not(def) => { - let shape = def.expr.as_ref().derive_shape(); - 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::Not(def) => derive_not_shape(def, symbol_table), + Expression::Grouped(v, _pos) => v.as_ref().derive_shape(symbol_table), Expression::Range(def) => Shape::List(PositionedItem::new( vec![Shape::Int(PositionedItem::new(0, def.start.pos().clone()))], def.pos.clone(), @@ -900,48 +902,12 @@ impl Expression { def.path.pos.clone(), ))), Expression::Binary(def) => { - let left_shape = def.left.derive_shape(); - let right_shape = def.right.derive_shape(); - match left_shape.resolve(&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, - )); - } - } + let left_shape = def.left.derive_shape(symbol_table); + let right_shape = def.right.derive_shape(symbol_table); + left_shape.narrow(&right_shape) } - Expression::Copy(def) => { - let base_shape = def.selector.derive_shape(); - 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::Copy(def) => derive_copy_shape(def, symbol_table), + Expression::Include(def) => derive_include_shape(def), Expression::Call(_) => todo!(), Expression::Func(_) => todo!(), Expression::Select(_) => todo!(), @@ -954,11 +920,129 @@ impl Expression { } } -impl TryFrom<&Expression> for Shape { - type Error = crate::error::BuildError; +fn derive_include_shape( + 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 { - Ok(e.derive_shape()) +fn derive_not_shape(def: &NotDef, symbol_table: &mut BTreeMap) -> 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) -> 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::>(); + 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::>(); + // 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())) + } } } diff --git a/src/ast/test.rs b/src/ast/test.rs index 7603680..a6dfb4b 100644 --- a/src/ast/test.rs +++ b/src/ast/test.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + // Copyright 2020 Jeremy Wall // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,30 +21,6 @@ use crate::iter::OffsetStrIter; use crate::parse::expression; 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] fn derive_shape_values() { let value_cases = vec![ @@ -108,7 +86,7 @@ fn derive_shape_values() { ]; 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 { - 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); + }; + }; } } diff --git a/src/ast/typecheck/mod.rs b/src/ast/typecheck/mod.rs index 6a8662f..be23993 100644 --- a/src/ast/typecheck/mod.rs +++ b/src/ast/typecheck/mod.rs @@ -106,48 +106,15 @@ impl Visitor for Checker { } fn leave_expression(&mut self, expr: &Expression) { - match expr { - Expression::Binary(_) => { - // Collapse the two shapes in the stack into one shape for this expression. - if let Some(right) = self.shape_stack.pop() { - if let Some(left) = self.shape_stack.pop() { - if let Some(shape) = left.resolve(&right) { - // Then give them a new position - self.shape_stack.push(shape.with_pos(expr.pos().clone())); - } else { - 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!(), + let shape = expr.derive_shape(&mut self.symbol_table); + if let Shape::TypeErr(pos, msg) = &shape { + self.err_stack.push(BuildError::with_pos( + msg.clone(), + ErrorType::TypeFail, + pos.clone(), + )); + } else { + self.shape_stack.push(shape); } } diff --git a/src/ast/typecheck/test.rs b/src/ast/typecheck/test.rs index 2368b13..be1f55b 100644 --- a/src/ast/typecheck/test.rs +++ b/src/ast/typecheck/test.rs @@ -4,7 +4,20 @@ use crate::ast::walk::Walker; use crate::ast::Position; 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] fn simple_binary_typecheck() { @@ -20,28 +33,16 @@ fn simple_binary_typecheck() { ); } +// TODO Test that leverages symbol tables and let bindings. #[test] fn simple_binary_typefail() { - let mut checker = Checker::new(); - let expr_str = "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, 5, 4)); + assert_type_fail!("1 + true;", "Expected int but got boolean", Position::new(1, 5, 4)); + assert_type_fail!("1 + \"\";", "Expected int but got str", Position::new(1, 5, 4)); + assert_type_fail!("1 + [];", "Expected int but got list", Position::new(1, 5, 4)); + assert_type_fail!("1 + {};", "Expected int but got tuple", Position::new(1, 5, 4)); } #[test] fn multiple_binary_typefail() { - let mut checker = Checker::new(); - 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)); + assert_type_fail!("1 + 1 + true;", "Expected int but got boolean", Position::new(1, 9, 8)); }