-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
partiql-types
and literals typing
This is the first commit to introduce types to `partiql-lang-rust`. With the changes in this commit, we're introducing `partiql-types` create which includes the initial model for our built-in types. We also introduce an `AstTyper` in `partiql-ast-passes` which provides an API for receiving `partiql-ast` and outputting a mapping from AST Node Ids to their corresponding types. In the first version, we're loosely typing the literal with the expection that this goes through multiple iterations. Here is the high-level plan: [ ] Type literals [ ] Type variable references in SFW [ ] Type operators [ ] Type using schemas in typing environment [ ] Add type coercions (using a Type Lattice?) [ ] Update CHANGELOG.md and adh documentation
- Loading branch information
Showing
10 changed files
with
477 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ | |
pub mod error; | ||
pub mod name_resolver; | ||
pub mod typer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
use crate::error::{AstTransformError, AstTransformationError}; | ||
use indexmap::IndexMap; | ||
use partiql_ast::ast; | ||
use partiql_ast::ast::{Bag, Expr, List, Lit, NodeId, Query, QuerySet}; | ||
use partiql_ast::visit::{Traverse, Visit, Visitor}; | ||
use partiql_catalog::Catalog; | ||
use partiql_types::{ArrayType, BagType, StaticType, StaticTypeKind, StructType}; | ||
|
||
pub type AstTypeMap<T> = IndexMap<ast::NodeId, T>; | ||
|
||
#[derive(Debug, Clone)] | ||
#[allow(dead_code)] | ||
pub struct AstTyper<'c, T> { | ||
id_stack: Vec<NodeId>, | ||
container_stack: Vec<Vec<StaticType>>, | ||
errors: Vec<AstTransformError>, | ||
type_map: AstTypeMap<T>, | ||
catalog: &'c dyn Catalog, | ||
} | ||
|
||
impl<'c> AstTyper<'c, StaticType> { | ||
pub fn new(catalog: &'c dyn Catalog) -> Self { | ||
AstTyper { | ||
id_stack: Default::default(), | ||
container_stack: Default::default(), | ||
errors: Default::default(), | ||
type_map: Default::default(), | ||
catalog, | ||
} | ||
} | ||
|
||
pub fn map_types( | ||
mut self, | ||
query: &ast::AstNode<ast::Query>, | ||
) -> Result<AstTypeMap<StaticType>, AstTransformationError> { | ||
query.visit(&mut self); | ||
if !self.errors.is_empty() { | ||
return Err(AstTransformationError { | ||
errors: self.errors, | ||
}); | ||
} | ||
Ok(self.type_map) | ||
} | ||
|
||
#[inline] | ||
fn current_node(&self) -> &NodeId { | ||
self.id_stack.last().unwrap() | ||
} | ||
} | ||
|
||
impl<'c, 'ast> Visitor<'ast> for AstTyper<'c, StaticType> { | ||
fn enter_ast_node(&mut self, id: NodeId) -> Traverse { | ||
self.id_stack.push(id); | ||
Traverse::Continue | ||
} | ||
fn exit_ast_node(&mut self, id: NodeId) -> Traverse { | ||
assert_eq!(self.id_stack.pop(), Some(id)); | ||
Traverse::Continue | ||
} | ||
|
||
fn enter_query(&mut self, _query: &'ast Query) -> Traverse { | ||
Traverse::Continue | ||
} | ||
|
||
fn exit_query(&mut self, _query: &'ast Query) -> Traverse { | ||
Traverse::Continue | ||
} | ||
|
||
fn enter_query_set(&mut self, _query_set: &'ast QuerySet) -> Traverse { | ||
match _query_set { | ||
QuerySet::SetOp(_) => { | ||
todo!() | ||
} | ||
QuerySet::Select(_) => {} | ||
QuerySet::Expr(_) => {} | ||
QuerySet::Values(_) => { | ||
todo!() | ||
} | ||
QuerySet::Table(_) => { | ||
todo!() | ||
} | ||
} | ||
Traverse::Continue | ||
} | ||
|
||
fn exit_query_set(&mut self, _query_set: &'ast QuerySet) -> Traverse { | ||
Traverse::Continue | ||
} | ||
|
||
fn enter_expr(&mut self, _expr: &'ast Expr) -> Traverse { | ||
Traverse::Continue | ||
} | ||
|
||
fn exit_expr(&mut self, _expr: &'ast Expr) -> Traverse { | ||
Traverse::Continue | ||
} | ||
|
||
fn enter_lit(&mut self, _lit: &'ast Lit) -> Traverse { | ||
let kind = match _lit { | ||
Lit::Null => StaticTypeKind::Null, | ||
Lit::Missing => StaticTypeKind::Missing, | ||
Lit::Int8Lit(_) => StaticTypeKind::Int, | ||
Lit::Int16Lit(_) => StaticTypeKind::Int, | ||
Lit::Int32Lit(_) => StaticTypeKind::Int, | ||
Lit::Int64Lit(_) => StaticTypeKind::Int, | ||
Lit::DecimalLit(_) => StaticTypeKind::Decimal, | ||
Lit::NumericLit(_) => StaticTypeKind::Decimal, | ||
Lit::RealLit(_) => StaticTypeKind::Float32, | ||
Lit::FloatLit(_) => StaticTypeKind::Float32, | ||
Lit::DoubleLit(_) => StaticTypeKind::Float64, | ||
Lit::BoolLit(_) => StaticTypeKind::Bool, | ||
Lit::IonStringLit(_) => todo!(), | ||
Lit::CharStringLit(_) => StaticTypeKind::String, | ||
Lit::NationalCharStringLit(_) => StaticTypeKind::String, | ||
Lit::BitStringLit(_) => todo!(), | ||
Lit::HexStringLit(_) => todo!(), | ||
Lit::StructLit(_) => StaticTypeKind::Struct(StructType::unconstrained()), | ||
Lit::ListLit(_) => StaticTypeKind::Array(ArrayType::array()), | ||
Lit::BagLit(_) => StaticTypeKind::Bag(BagType::bag()), | ||
Lit::TypedLit(_, _) => todo!(), | ||
}; | ||
|
||
let ty = StaticType::new(kind); | ||
|
||
let id = *self.current_node(); | ||
if let Some(c) = self.container_stack.last_mut() { | ||
c.push(ty.clone()) | ||
} | ||
self.type_map.insert(id, ty); | ||
Traverse::Continue | ||
} | ||
|
||
fn enter_struct(&mut self, _struct: &'ast ast::Struct) -> Traverse { | ||
self.container_stack.push(vec![]); | ||
Traverse::Continue | ||
} | ||
|
||
fn exit_struct(&mut self, _struct: &'ast ast::Struct) -> Traverse { | ||
let id = *self.current_node(); | ||
let fields = self.container_stack.pop(); | ||
// TODO Check if all keys are StaticType::String | ||
match fields { | ||
Some(f) => { | ||
f.chunks(2).for_each(|w| { | ||
match w[0].is_string() { | ||
true => {} | ||
// TODO remove the panic with Traverse::Continue and capturing the error | ||
_ => { | ||
self.errors.push(AstTransformError::IllegalState( | ||
"Struct keys can only be of String type".to_string(), | ||
)); | ||
} | ||
} | ||
}); | ||
|
||
let ty = StaticType::new_struct(StructType::unconstrained()); | ||
self.type_map.insert(id, ty.clone()); | ||
if let Some(c) = self.container_stack.last_mut() { | ||
c.push(ty) | ||
} | ||
} | ||
None => { | ||
let ty = StaticType::new_struct(StructType::unconstrained()); | ||
self.type_map.insert(id, ty.clone()); | ||
if let Some(c) = self.container_stack.last_mut() { | ||
c.push(ty) | ||
} | ||
} | ||
} | ||
|
||
Traverse::Continue | ||
} | ||
|
||
fn enter_bag(&mut self, _bag: &'ast Bag) -> Traverse { | ||
self.container_stack.push(vec![]); | ||
Traverse::Continue | ||
} | ||
|
||
fn exit_bag(&mut self, _bag: &'ast Bag) -> Traverse { | ||
// TODO add schema validation of BAG elements, e.g. for Schema Bag<Int> if there is at least | ||
// one element that isn't INT there is a type checking error. | ||
|
||
// TODO clarify if we need to record the internal types of bag literal or stick w/Schema? | ||
self.container_stack.pop(); | ||
|
||
let id = *self.current_node(); | ||
let ty = StaticType::new_bag(BagType::bag()); | ||
|
||
self.type_map.insert(id, ty.clone()); | ||
if let Some(s) = self.container_stack.last_mut() { | ||
s.push(ty) | ||
} | ||
Traverse::Continue | ||
} | ||
|
||
fn enter_list(&mut self, _list: &'ast List) -> Traverse { | ||
self.container_stack.push(vec![]); | ||
Traverse::Continue | ||
} | ||
|
||
fn exit_list(&mut self, _list: &'ast List) -> Traverse { | ||
// TODO clarify if we need to record the internal types of bag literal or stick w/Schema? | ||
// one element that isn't INT there is a type checking error. | ||
|
||
// We don't care about internal elements for now | ||
self.container_stack.pop(); | ||
|
||
let id = *self.current_node(); | ||
let ty = StaticType::new_array(ArrayType::array()); | ||
|
||
self.type_map.insert(id, ty.clone()); | ||
if let Some(s) = self.container_stack.last_mut() { | ||
s.push(ty) | ||
} | ||
Traverse::Continue | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use assert_matches::assert_matches; | ||
use partiql_ast::ast; | ||
use partiql_catalog::PartiqlCatalog; | ||
use partiql_types::{StaticType, StaticTypeKind}; | ||
|
||
#[test] | ||
fn simple_test() { | ||
assert_matches!(run_literal_test("NULL"), StaticTypeKind::Null); | ||
assert_matches!(run_literal_test("MISSING"), StaticTypeKind::Missing); | ||
assert_matches!(run_literal_test("Missing"), StaticTypeKind::Missing); | ||
assert_matches!(run_literal_test("true"), StaticTypeKind::Bool); | ||
assert_matches!(run_literal_test("false"), StaticTypeKind::Bool); | ||
assert_matches!(run_literal_test("1"), StaticTypeKind::Int); | ||
assert_matches!(run_literal_test("1.5"), StaticTypeKind::Decimal); | ||
assert_matches!(run_literal_test("'hello world!'"), StaticTypeKind::String); | ||
assert_matches!( | ||
run_literal_test("[1, 2 , {'a': b}]"), | ||
StaticTypeKind::Array(_) | ||
); | ||
assert_matches!( | ||
run_literal_test("<<'1', {'a': b}>>"), | ||
StaticTypeKind::Bag(_) | ||
); | ||
assert_matches!( | ||
run_literal_test("{'a': 1, 'b': 3, 'c': [1, 2]}"), | ||
StaticTypeKind::Struct(_) | ||
); | ||
} | ||
|
||
fn run_literal_test(q: &str) -> StaticTypeKind { | ||
let out = type_statement(q); | ||
let values: Vec<&StaticType> = out.values().collect(); | ||
values.last().unwrap().kind().clone() | ||
} | ||
|
||
fn type_statement(q: &str) -> AstTypeMap<StaticType> { | ||
let parsed = partiql_parser::Parser::default() | ||
.parse(q) | ||
.expect("Expect successful parse"); | ||
|
||
let catalog = PartiqlCatalog::default(); | ||
let typer = AstTyper::new(&catalog); | ||
if let ast::Expr::Query(q) = parsed.ast.as_ref() { | ||
typer.map_types(&q).expect("type map") | ||
} else { | ||
panic!("Typing statement other than `Query` are unsupported") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.