feat: list shape narrowing

This commit is contained in:
Jeremy Wall 2023-08-30 16:56:16 -04:00 committed by Jeremy Wall
parent 46e55484dd
commit 5835adbf7a
3 changed files with 136 additions and 25 deletions

View File

@ -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<String>),
}
#[derive(PartialEq, Debug, Clone)]
pub struct NarrowedShape {
pub pos: Position,
pub types: Vec<Shape>,
}
impl NarrowedShape {
pub fn new(types: Vec<Shape>, line: usize, column: usize, offset: usize) -> Self {
Self::new_with_pos(types, Position::new(line, column, offset))
}
pub fn new_with_pos(types: Vec<Shape>, 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<f64>),
Str(PositionedItem<String>),
Tuple(PositionedItem<TupleShape>),
List(PositionedItem<ShapeList>),
List(NarrowedShape),
Func(FuncShapeDef),
Module(ModuleShape),
Hole(PositionedItem<String>), // 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.
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<Shape>, 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<String, Shape>)
),
// 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<String, Shape>)
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<String, Shape>)
.collect::<Vec<Shape>>();
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<String, Shape>)
);
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(),
)),

View File

@ -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),
)),

View File

@ -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]