From 5835adbf7afadf2a963be01a31081b3f40f2d668 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 30 Aug 2023 16:56:16 -0400 Subject: [PATCH] feat: list shape narrowing --- src/ast/mod.rs | 108 ++++++++++++++++++++++++++++++-------- src/ast/test.rs | 6 +-- src/ast/typecheck/test.rs | 47 +++++++++++++++++ 3 files changed, 136 insertions(+), 25 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index d993dc4..d5ca4d7 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -20,7 +20,7 @@ use std::cmp::Ordering; use std::cmp::PartialEq; use std::cmp::PartialOrd; use std::collections::BTreeMap; -use std::convert::{Into, TryFrom}; +use std::convert::Into; use std::fmt; use std::hash::Hash; use std::hash::Hasher; @@ -253,6 +253,27 @@ pub enum ImportShape { Unresolved(PositionedItem), } +#[derive(PartialEq, Debug, Clone)] +pub struct NarrowedShape { + pub pos: Position, + pub types: Vec, +} + +impl NarrowedShape { + pub fn new(types: Vec, line: usize, column: usize, offset: usize) -> Self { + Self::new_with_pos(types, Position::new(line, column, offset)) + } + + pub fn new_with_pos(types: Vec, pos: Position) -> Self { + Self { pos, types } + } + + pub fn with_pos(mut self, pos: Position) -> Self { + self.pos = pos; + self + } +} + #[doc = "Shapes represent the types that UCG values or expressions can have."] #[derive(PartialEq, Debug, Clone)] pub enum Shape { @@ -262,28 +283,27 @@ pub enum Shape { Float(PositionedItem), Str(PositionedItem), Tuple(PositionedItem), - List(PositionedItem), + List(NarrowedShape), Func(FuncShapeDef), Module(ModuleShape), Hole(PositionedItem), // 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. + Narrowed(NarrowedShape), // 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 narrow(&self, right: &Shape) -> Self { - dbg!((self, right)); - dbg!(match (self, right) { + 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::List(left_slist), Shape::List(right_slist)) => { - // TODO - unimplemented!("Can't merge these yet."); + (Shape::Narrowed(left_slist), Shape::Narrowed(right_slist)) + | (Shape::List(left_slist), Shape::List(right_slist)) => { + self.narrow_list_shapes(left_slist, right_slist, right) } (Shape::Tuple(left_slist), Shape::Tuple(right_slist)) => { // TODO @@ -299,9 +319,30 @@ impl Shape { } _ => Shape::TypeErr( right.pos().clone(), - format!("Expected {} but got {}", self.type_name(), right.type_name()), + format!( + "Expected {} but got {}", + self.type_name(), + right.type_name() + ), ), - }) + } + } + + fn narrow_list_shapes( + &self, + left_slist: &NarrowedShape, + right_slist: &NarrowedShape, + right: &Shape, + ) -> Shape { + let left_iter = left_slist.types.iter(); + let right_iter = right_slist.types.iter(); + if is_shape_subset(left_iter, right_slist) { + self.clone() + } else if is_shape_subset(right_iter, left_slist) { + right.clone() + } else { + Shape::TypeErr(right.pos().clone(), "Incompatible List Shapes".to_owned()) + } } pub fn type_name(&self) -> &'static str { @@ -349,7 +390,7 @@ impl Shape { Shape::Float(s) => Shape::Float(PositionedItem::new(s.val, pos)), Shape::Boolean(b) => Shape::Boolean(PositionedItem::new(b.val, pos)), Shape::Empty(p) => Shape::Empty(pos), - Shape::List(lst) => Shape::List(PositionedItem::new(lst.val, pos)), + Shape::List(lst) => Shape::List(NarrowedShape::new_with_pos(lst.types, pos)), Shape::Tuple(flds) => Shape::Tuple(PositionedItem::new(flds.val, pos)), Shape::Func(_) | Shape::Module(_) => self.clone(), Shape::Narrowed(pi) => Shape::Narrowed(pi.with_pos(pos)), @@ -365,6 +406,29 @@ impl Shape { } } +fn is_shape_subset(mut right_iter: std::slice::Iter, left_slist: &NarrowedShape) -> bool { + let right_subset = loop { + let mut matches = false; + let ls = if let Some(ls) = right_iter.next() { + ls + } else { + break true; + }; + for rs in left_slist.types.iter() { + let s = ls.narrow(rs); + if let Shape::TypeErr(_, _) = s { + // noop + } else { + matches = true; + } + } + if !matches { + break matches; + } + }; + right_subset +} + impl Value { /// Returns the type name of the Value it is called on as a string. pub fn type_name(&self) -> String { @@ -466,7 +530,7 @@ impl Value { for f in &flds.elems { field_shapes.push(f.derive_shape(symbol_table)); } - Shape::List(PositionedItem::new(field_shapes, flds.pos.clone())) + Shape::List(NarrowedShape::new_with_pos(field_shapes, flds.pos.clone())) } }; shape @@ -879,7 +943,7 @@ impl Expression { } 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( + Expression::Range(def) => Shape::List(NarrowedShape::new_with_pos( vec![Shape::Int(PositionedItem::new(0, def.start.pos().clone()))], def.pos.clone(), )), @@ -919,12 +983,12 @@ fn derive_include_shape( typ: _typ, }: &IncludeDef, ) -> Shape { - Shape::Narrowed(PositionedItem::new( + Shape::Narrowed(NarrowedShape::new_with_pos( vec![ Shape::Tuple(PositionedItem::new(vec![], pos.clone())), - Shape::List(PositionedItem::new(vec![], pos.clone())), + Shape::List(NarrowedShape::new_with_pos(vec![], pos.clone())), ], - pos, + pos.clone(), )) } @@ -961,7 +1025,7 @@ fn derive_copy_shape(def: &CopyDef, symbol_table: &mut BTreeMap) ), // 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( + Shape::Hole(pi) => Shape::Narrowed(NarrowedShape::new_with_pos( vec![ Shape::Tuple(PositionedItem::new(vec![], pi.pos.clone())), Shape::Module(ModuleShape { @@ -975,7 +1039,7 @@ fn derive_copy_shape(def: &CopyDef, symbol_table: &mut BTreeMap) Shape::Narrowed(potentials) => { // 1. Do the possible shapes include tuple, module, or import? let filtered = potentials - .val + .types .iter() .filter_map(|v| match v { Shape::Tuple(_) | Shape::Module(_) | Shape::Import(_) | Shape::Hole(_) => { @@ -986,7 +1050,7 @@ fn derive_copy_shape(def: &CopyDef, symbol_table: &mut BTreeMap) .collect::>(); if !filtered.is_empty() { // 1.1 Then return those and strip the others. - Shape::Narrowed(PositionedItem::new(filtered, def.pos.clone())) + Shape::Narrowed(NarrowedShape::new_with_pos(filtered, def.pos.clone())) } else { // 2. Else return a type error Shape::TypeErr( @@ -1022,7 +1086,7 @@ fn derive_copy_shape(def: &CopyDef, symbol_table: &mut BTreeMap) ); Shape::Tuple(base_fields).with_pos(def.pos.clone()) } - Shape::Import(ImportShape::Unresolved(_)) => Shape::Narrowed(PositionedItem::new( + Shape::Import(ImportShape::Unresolved(_)) => Shape::Narrowed(NarrowedShape::new_with_pos( vec![Shape::Tuple(PositionedItem::new(vec![], def.pos.clone()))], def.pos.clone(), )), diff --git a/src/ast/test.rs b/src/ast/test.rs index a6dfb4b..26511af 100644 --- a/src/ast/test.rs +++ b/src/ast/test.rs @@ -16,7 +16,7 @@ use std::collections::BTreeMap; use abortable_parser::iter::SliceIter; use abortable_parser::Result as ParseResult; -use crate::ast::{Expression, ListDef, Position, PositionedItem, Shape, Token, TokenType, Value}; +use crate::ast::{Expression, ListDef, Position, PositionedItem, Shape, Token, TokenType, Value ,NarrowedShape}; use crate::iter::OffsetStrIter; use crate::parse::expression; use crate::tokenizer::tokenize; @@ -78,7 +78,7 @@ fn derive_shape_values() { )))], pos: Position::new(0, 0, 0), }), - Shape::List(PositionedItem::new( + Shape::List(NarrowedShape::new_with_pos( vec![Shape::Int(PositionedItem::new(3, Position::new(0, 0, 0)))], Position::new(0, 0, 0), )), @@ -111,7 +111,7 @@ fn derive_shape_expressions() { ), ( "0:1;", - Shape::List(PositionedItem::new( + Shape::List(NarrowedShape::new_with_pos( vec![Shape::Int(PositionedItem::new(0, Position::new(0, 0, 0)))], Position::new(0, 0, 0), )), diff --git a/src/ast/typecheck/test.rs b/src/ast/typecheck/test.rs index f0defa4..69b6fb4 100644 --- a/src/ast/typecheck/test.rs +++ b/src/ast/typecheck/test.rs @@ -42,6 +42,48 @@ fn simple_binary_typecheck() { "1 + 1;", Shape::Int(PositionedItem::new(1, Position::new(1, 1, 0))) ); + assert_type_success!( + "\"\" + \"\";", + Shape::Str(PositionedItem::new(String::new(), Position::new(1, 1, 0))) + ); + assert_type_success!( + "1.0 + 1.0;", + Shape::Float(PositionedItem::new(1.0, Position::new(1, 1, 0))) + ); + assert_type_success!( + "[] + [];", + Shape::List(crate::ast::NarrowedShape::new(vec![], 1, 1, 0)) + ); + assert_type_success!( + "[1] + [2];", + Shape::List(crate::ast::NarrowedShape::new( + vec![Shape::Int(PositionedItem::new_with_pos( + 1, + Position::new(1, 1, 0) + ))], + 1, + 1, + 0 + )) + ); + assert_type_success!( + "[1, 1.0] + [1, 2.0];", + Shape::List(crate::ast::NarrowedShape::new( + vec![ + Shape::Int(PositionedItem::new_with_pos( + 1, + Position::new(1, 1, 0) + )), + Shape::Float(PositionedItem::new_with_pos( + 1.0, + Position::new(1, 1, 0) + )), + ], + 1, + 1, + 0 + )) + ); } // TODO Test that leverages symbol tables and let bindings. @@ -67,6 +109,11 @@ fn simple_binary_typefail() { "Expected int but got tuple", Position::new(1, 5, 4) ); + assert_type_fail!( + "[1] + [1.0];", + "Incompatible List Shapes", + Position::new(1, 7, 6) + ); } #[test]