forked from M-Labs/nac3
refactor the using of rustpython fold again, now can use with_scope, need further testing
This commit is contained in:
parent
7eb0ab41d4
commit
4abe99f6b3
@ -8,10 +8,10 @@ use std::collections::HashMap;
|
||||
|
||||
pub struct ContextStack {
|
||||
/// stack level, starts from 0
|
||||
pub level: u32,
|
||||
level: u32,
|
||||
/// stack of symbol definitions containing (name, level) where `level` is the smallest level
|
||||
/// where the name is assigned a value
|
||||
pub sym_def: Vec<(String, u32)>,
|
||||
sym_def: Vec<(String, u32)>,
|
||||
}
|
||||
|
||||
pub struct InferenceContext<'a> {
|
||||
@ -25,9 +25,9 @@ pub struct InferenceContext<'a> {
|
||||
/// identifier to (type, readable, location) mapping.
|
||||
/// an identifier might be defined earlier but has no value (for some code path), thus not
|
||||
/// readable.
|
||||
pub sym_table: HashMap<String, (Type, bool, Location)>,
|
||||
sym_table: HashMap<String, (Type, bool, Location)>,
|
||||
/// stack
|
||||
pub stack: ContextStack,
|
||||
stack: ContextStack,
|
||||
}
|
||||
|
||||
// non-trivial implementations here
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::convert::TryInto;
|
||||
use std::fs::create_dir_all;
|
||||
|
||||
use crate::typecheck::context::InferenceContext;
|
||||
use crate::typecheck::inference_core;
|
||||
@ -9,411 +8,49 @@ use crate::typecheck::primitives;
|
||||
use rustpython_parser::ast;
|
||||
use rustpython_parser::ast::fold::Fold;
|
||||
|
||||
use super::inference_core::resolve_call;
|
||||
|
||||
pub struct ExpressionTypeInferencer<'a> {
|
||||
pub ctx: InferenceContext<'a>
|
||||
}
|
||||
|
||||
impl<'a> ExpressionTypeInferencer<'a> { // NOTE: add location here in the function parameter for better error message?
|
||||
|
||||
fn infer_constant_val(&self, constant: &ast::Constant) -> Result<Option<Type>, String> {
|
||||
match constant {
|
||||
ast::Constant::Bool(_) =>
|
||||
Ok(Some(self.ctx.get_primitive(primitives::BOOL_TYPE))),
|
||||
|
||||
ast::Constant::Int(val) => {
|
||||
let int32: Result<i32, _> = val.try_into();
|
||||
let int64: Result<i64, _> = val.try_into();
|
||||
|
||||
if int32.is_ok() {
|
||||
Ok(Some(self.ctx.get_primitive(primitives::INT32_TYPE)))
|
||||
} else if int64.is_ok() {
|
||||
Ok(Some(self.ctx.get_primitive(primitives::INT64_TYPE)))
|
||||
} else {
|
||||
Err("Integer out of bound".into())
|
||||
}
|
||||
},
|
||||
|
||||
ast::Constant::Float(_) =>
|
||||
Ok(Some(self.ctx.get_primitive(primitives::FLOAT_TYPE))),
|
||||
|
||||
ast::Constant::Tuple(vals) => {
|
||||
let result = vals
|
||||
.into_iter()
|
||||
.map(|x| self.infer_constant_val(x))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if result.iter().all(|x| x.is_ok()) {
|
||||
Ok(Some(TypeEnum::ParametricType(
|
||||
primitives::TUPLE_TYPE,
|
||||
result
|
||||
.into_iter()
|
||||
.map(|x| x.unwrap().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
).into()))
|
||||
} else {
|
||||
Err("Some elements in tuple cannot be typed".into())
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err("not supported".into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn infer_list_val(&self, elts: &Vec<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if elts.is_empty() {
|
||||
Ok(Some(TypeEnum::ParametricType(primitives::LIST_TYPE, vec![TypeEnum::BotType.into()]).into()))
|
||||
} else {
|
||||
let types = elts
|
||||
.iter()
|
||||
.map(|x| &x.custom)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if types.iter().all(|x| x.is_some()) {
|
||||
let head = types.iter().next().unwrap(); // here unwrap alone should be fine after the previous check
|
||||
if types.iter().all(|x| x.eq(head)) {
|
||||
Ok(Some(TypeEnum::ParametricType(primitives::LIST_TYPE, vec![(*head).clone().unwrap()]).into()))
|
||||
} else {
|
||||
Err("inhomogeneous list is not allowed".into())
|
||||
}
|
||||
} else {
|
||||
Err("list elements must have some type".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_tuple_val(&self, elts: &Vec<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
let types = elts
|
||||
.iter()
|
||||
.map(|x| (x.custom).clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if types.iter().all(|x| x.is_some()) {
|
||||
Ok(Some(TypeEnum::ParametricType(
|
||||
primitives::TUPLE_TYPE,
|
||||
types.into_iter().map(|x| x.unwrap()).collect()).into())) // unwrap alone should be fine after the previous check
|
||||
} else {
|
||||
Err("tuple elements must have some type".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_arrtibute(&self, value: &Box<ast::Expr<Option<Type>>>, attr: &str) -> Result<Option<Type>, String> {
|
||||
let ty = value.custom.clone().ok_or_else(|| "no value".to_string())?;
|
||||
if let TypeEnum::TypeVariable(id) = ty.as_ref() {
|
||||
let v = self.ctx.get_variable_def(*id);
|
||||
if v.bound.is_empty() {
|
||||
return Err("no fields on unbounded type variable".into());
|
||||
}
|
||||
let ty = v.bound[0].get_base(&self.ctx).and_then(|v| v.fields.get(attr));
|
||||
if ty.is_none() {
|
||||
return Err("unknown field".into());
|
||||
}
|
||||
for x in v.bound[1..].iter() {
|
||||
let ty1 = x.get_base(&self.ctx).and_then(|v| v.fields.get(attr));
|
||||
if ty1 != ty {
|
||||
return Err("unknown field (type mismatch between variants)".into());
|
||||
}
|
||||
}
|
||||
return Ok(Some(ty.unwrap().clone()));
|
||||
}
|
||||
|
||||
match ty.get_base(&self.ctx) {
|
||||
Some(b) => match b.fields.get(attr) {
|
||||
Some(t) => Ok(Some(t.clone())),
|
||||
None => Err("no such field".into()),
|
||||
},
|
||||
None => Err("this object has no fields".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_bool_ops(&self, values: &Vec<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
assert_eq!(values.len(), 2);
|
||||
let left = values[0].custom.clone().ok_or_else(|| "no value".to_string())?;
|
||||
let right = values[1].custom.clone().ok_or_else(|| "no value".to_string())?;
|
||||
let b = self.ctx.get_primitive(primitives::BOOL_TYPE);
|
||||
if left == b && right == b {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Err("bool operands must be bool".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn _infer_bin_ops(&self, _left: &Box<ast::Expr<Option<Type>>>, _op: &ast::Operator, _right: &Box<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
Err("no need this function".into())
|
||||
}
|
||||
|
||||
fn infer_unary_ops(&self, op: &ast::Unaryop, operand: &Box<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if let ast::Unaryop::Not = op {
|
||||
if (**operand).custom == Some(self.ctx.get_primitive(primitives::BOOL_TYPE)) {
|
||||
Ok(Some(self.ctx.get_primitive(primitives::BOOL_TYPE)))
|
||||
} else {
|
||||
Err("logical not must be applied to bool".into())
|
||||
}
|
||||
} else {
|
||||
inference_core::resolve_call(&self.ctx, (**operand).custom.clone(), magic_methods::unaryop_name(op), &[])
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_compare(&self, left: &Box<ast::Expr<Option<Type>>>, ops: &Vec<ast::Cmpop>, comparators: &Vec<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
assert!(comparators.len() > 0);
|
||||
if left.custom.is_none() || (!comparators.iter().all(|x| x.custom.is_some())) {
|
||||
Err("comparison operands must have type".into())
|
||||
} else {
|
||||
let bool_type = Some(self.ctx.get_primitive(primitives::BOOL_TYPE));
|
||||
let ty_first = resolve_call(
|
||||
&self.ctx,
|
||||
Some(left.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?.clone()),
|
||||
magic_methods::comparison_name(&ops[0]).ok_or_else(|| "unsupported comparison".to_string())?,
|
||||
&[comparators[0].custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?])?;
|
||||
if ty_first != bool_type {
|
||||
return Err("comparison result must be boolean".into());
|
||||
}
|
||||
|
||||
for ((a, b), op)
|
||||
in comparators[..(comparators.len() - 1)]
|
||||
.iter()
|
||||
.zip(comparators[1..].iter())
|
||||
.zip(ops[1..].iter()) {
|
||||
let ty = resolve_call(
|
||||
&self.ctx,
|
||||
Some(a.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?.clone()),
|
||||
magic_methods::comparison_name(op).ok_or_else(|| "unsupported comparison".to_string())?,
|
||||
&[b.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?.clone()])?;
|
||||
if ty != bool_type {
|
||||
return Err("comparison result must be boolean".into());
|
||||
}
|
||||
}
|
||||
Ok(bool_type)
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_call(&self, func: &Box<ast::Expr<Option<Type>>>, args: &Vec<ast::Expr<Option<Type>>>, _keywords: &Vec<ast::Keyword<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if args.iter().all(|x| x.custom.is_some()) {
|
||||
match &func.node {
|
||||
ast::ExprKind::Name {id, ctx: _}
|
||||
=> resolve_call(
|
||||
&self.ctx,
|
||||
None,
|
||||
id,
|
||||
&args.iter().map(|x| x.custom.clone().unwrap()).collect::<Vec<_>>()),
|
||||
|
||||
ast::ExprKind::Attribute {value, attr, ctx: _}
|
||||
=> resolve_call(
|
||||
&self.ctx,
|
||||
Some(value.custom.clone().ok_or_else(|| "no value".to_string())?),
|
||||
&attr,
|
||||
&args.iter().map(|x| x.custom.clone().unwrap()).collect::<Vec<_>>()),
|
||||
|
||||
_ => Err("not supported".into())
|
||||
}
|
||||
} else {
|
||||
Err("function params must have type".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_subscript(&self, value: &Box<ast::Expr<Option<Type>>>, slice: &Box<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
// let tt = value.custom.ok_or_else(|| "no value".to_string())?.as_ref();
|
||||
|
||||
let t = if let TypeEnum::ParametricType(primitives::LIST_TYPE, ls) = value.custom.as_ref().ok_or_else(|| "no value".to_string())?.as_ref() {
|
||||
ls[0].clone()
|
||||
} else {
|
||||
return Err("subscript is not supported for types other than list".into());
|
||||
};
|
||||
|
||||
if let ast::ExprKind::Slice {lower, upper, step} = &slice.node {
|
||||
let int32_type = self.ctx.get_primitive(primitives::INT32_TYPE);
|
||||
let l = lower.as_ref().map_or(
|
||||
Ok(&int32_type),
|
||||
|x| x.custom.as_ref().ok_or("lower bound cannot be typped".to_string()))?;
|
||||
let u = upper.as_ref().map_or(
|
||||
Ok(&int32_type),
|
||||
|x| x.custom.as_ref().ok_or("upper bound cannot be typped".to_string()))?;
|
||||
let s = step.as_ref().map_or(
|
||||
Ok(&int32_type),
|
||||
|x| x.custom.as_ref().ok_or("step cannot be typped".to_string()))?;
|
||||
|
||||
if l == &int32_type && u == &int32_type && s == &int32_type {
|
||||
Ok(value.custom.clone())
|
||||
} else {
|
||||
Err("slice must be int32 type".into())
|
||||
}
|
||||
} else if slice.custom == Some(self.ctx.get_primitive(primitives::INT32_TYPE)) {
|
||||
Ok(Some(t))
|
||||
} else {
|
||||
Err("slice or index must be int32 type".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_if_expr(&self, test: &Box<ast::Expr<Option<Type>>>, body: &Box<ast::Expr<Option<Type>>>, orelse: &Box<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if test.custom != Some(self.ctx.get_primitive(primitives::BOOL_TYPE)) {
|
||||
Err("test should be bool".into())
|
||||
} else {
|
||||
if body.custom == orelse.custom {
|
||||
Ok(body.custom.clone())
|
||||
} else {
|
||||
Err("divergent type at if expression".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_list_comprehesion(&mut self, elt: &Box<ast::Expr<Option<Type>>>, generators: &Vec<ast::Comprehension<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if generators[0]
|
||||
.ifs
|
||||
.iter()
|
||||
.all(|x| x.custom == Some(self.ctx.get_primitive(primitives::BOOL_TYPE))) {
|
||||
Ok(Some(TypeEnum::ParametricType(
|
||||
primitives::LIST_TYPE,
|
||||
vec![elt.custom.clone().ok_or_else(|| "elements should have value".to_string())?]).into()))
|
||||
} else {
|
||||
Err("test must be bool".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_comprehension_first(&mut self, node: ast::Comprehension<Option<Type>>) -> Result<ast::Comprehension<Option<Type>>, String> {
|
||||
Ok(ast::Comprehension {
|
||||
target: node.target,
|
||||
iter: Box::new(self.fold_expr(*node.iter)?),
|
||||
ifs: node.ifs,
|
||||
is_async: node.is_async
|
||||
})
|
||||
}
|
||||
|
||||
fn fold_comprehension_second(&mut self, node: ast::Comprehension<Option<Type>>) -> Result<ast::Comprehension<Option<Type>>, String> {
|
||||
Ok(ast::Comprehension {
|
||||
target: Box::new(self.fold_expr(*node.target)?),
|
||||
iter: node.iter,
|
||||
ifs: node
|
||||
.ifs
|
||||
.into_iter()
|
||||
.map(|x| self.fold_expr(x))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
is_async: node.is_async
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_simple_binding(&mut self, name: &ast::Expr<Option<Type>>, ty: Type) -> Result<(), String> {
|
||||
match &name.node {
|
||||
ast::ExprKind::Name {id, ctx: _} => {
|
||||
if id == "_" {
|
||||
Ok(())
|
||||
} else if self.ctx.defined(id) {
|
||||
Err("duplicated naming".into())
|
||||
} else {
|
||||
self.ctx.assign(id.clone(), ty, name.location)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
ast::ExprKind::Tuple {elts, ctx: _} => {
|
||||
if let TypeEnum::ParametricType(primitives::TUPLE_TYPE, ls) = ty.as_ref() {
|
||||
if elts.len() == ls.len() {
|
||||
for (a, b) in elts.iter().zip(ls.iter()) {
|
||||
self.infer_simple_binding(a, b.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err("different length".into())
|
||||
}
|
||||
} else {
|
||||
Err("not supported".into())
|
||||
}
|
||||
}
|
||||
_ => Err("not supported".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> ast::fold::Fold<Option<Type>> for ExpressionTypeInferencer<'a> {
|
||||
impl<'a> ast::fold::Fold<Option<Type>> for InferenceContext<'a> {
|
||||
type TargetU = Option<Type>;
|
||||
type Error = String;
|
||||
|
||||
fn map_user(&mut self, user: Option<Type>) -> Result<Self::TargetU, Self::Error> {
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
|
||||
fn fold_expr(&mut self, node: ast::Expr<Option<Type>>) -> Result<ast::Expr<Self::TargetU>, Self::Error> {
|
||||
assert_eq!(node.custom, None); // NOTE: should pass
|
||||
assert_eq!(node.custom, None);
|
||||
let mut expr = node;
|
||||
|
||||
if let ast::Expr {location, custom, node: ast::ExprKind::ListComp {elt, generators } } = expr {
|
||||
// is list comprehension, only fold generators which does not include unknown identifiers introduced by list comprehension
|
||||
if generators.len() != 1 {
|
||||
return Err("only 1 generator statement is supported".into())
|
||||
}
|
||||
let generators_first_folded = generators
|
||||
.into_iter()
|
||||
.map(|x| self.fold_comprehension_first(x)).collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let gen = &generators_first_folded[0];
|
||||
let iter_type = gen.iter.custom.as_ref().ok_or("no value".to_string())?.as_ref();
|
||||
|
||||
if let TypeEnum::ParametricType(primitives::LIST_TYPE, ls) = iter_type {
|
||||
self.ctx.stack.level += 1; // FIXME: how to use with_scope??
|
||||
|
||||
self.infer_simple_binding(&gen.target, ls[0].clone())?;
|
||||
expr = ast::Expr {
|
||||
location,
|
||||
custom,
|
||||
node: ast::ExprKind::ListComp {
|
||||
elt: Box::new(self.fold_expr(*elt)?),
|
||||
generators: generators_first_folded
|
||||
.into_iter()
|
||||
.map(|x| self.fold_comprehension_second(x))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
}
|
||||
};
|
||||
|
||||
self.ctx.stack.level -= 1;
|
||||
while !self.ctx.stack.sym_def.is_empty() {
|
||||
let (_, level) = self.ctx.stack.sym_def.last().unwrap();
|
||||
if *level > self.ctx.stack.level {
|
||||
let (name, _) = self.ctx.stack.sym_def.pop().unwrap();
|
||||
let (t, b, l) = self.ctx.sym_table.get_mut(&name).unwrap();
|
||||
// set it to be unreadable
|
||||
*b = false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return Err("iteration is supported for list only".into());
|
||||
}
|
||||
} else {
|
||||
// if not listcomp which requires special handling, skip current level, make sure child nodes have their type
|
||||
expr = ast::fold::fold_expr(self, expr)?;
|
||||
}
|
||||
match &expr.node {
|
||||
ast::ExprKind::ListComp { .. } => expr = self.prefold_list_comprehension(expr)?,
|
||||
_ => expr = rustpython_parser::ast::fold::fold_expr(self, expr)?
|
||||
};
|
||||
|
||||
match &expr.node {
|
||||
ast::ExprKind::Constant {value, kind: _} =>
|
||||
Ok(ast::Expr {
|
||||
location: expr.location,
|
||||
custom: self.infer_constant_val(value)?,
|
||||
custom: self.infer_constant(value)?,
|
||||
node: expr.node
|
||||
}),
|
||||
|
||||
ast::ExprKind::Name {id, ctx: _} =>
|
||||
Ok(ast::Expr {
|
||||
location: expr.location,
|
||||
custom: Some(self.ctx.resolve(id)?),
|
||||
custom: Some(self.resolve(id)?),
|
||||
node: expr.node
|
||||
}),
|
||||
|
||||
ast::ExprKind::List {elts, ctx: _} => {
|
||||
ast::ExprKind::List {elts, ctx: _} =>
|
||||
Ok(ast::Expr {
|
||||
location: expr.location,
|
||||
custom: self.infer_list_val(elts)?,
|
||||
custom: self.infer_list(elts)?,
|
||||
node: expr.node
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
ast::ExprKind::Tuple {elts, ctx: _} =>
|
||||
Ok(ast::Expr {
|
||||
location: expr.location,
|
||||
custom: self.infer_tuple_val(elts)?,
|
||||
custom: self.infer_tuple(elts)?,
|
||||
node: expr.node
|
||||
}),
|
||||
|
||||
@ -435,7 +72,7 @@ impl<'a> ast::fold::Fold<Option<Type>> for ExpressionTypeInferencer<'a> {
|
||||
Ok(ast::Expr {
|
||||
location: expr.location,
|
||||
custom: inference_core::resolve_call(
|
||||
&self.ctx,
|
||||
&self,
|
||||
Some(left.custom.clone().ok_or_else(|| "no value".to_string())?),
|
||||
magic_methods::binop_name(op),
|
||||
&[right.custom.clone().ok_or_else(|| "no value".to_string())?])?,
|
||||
@ -463,14 +100,6 @@ impl<'a> ast::fold::Fold<Option<Type>> for ExpressionTypeInferencer<'a> {
|
||||
node: expr.node
|
||||
}),
|
||||
|
||||
/* // REVIEW: add a new primitive type for slice and do type check of bounds here?
|
||||
ast::ExprKind::Slice {lower, upper, step } =>
|
||||
Ok(ast::Expr {
|
||||
location: expr.location,
|
||||
custom: self.infer_slice(lower, upper, step)?,
|
||||
node: expr.node
|
||||
}), */
|
||||
|
||||
ast::ExprKind::Subscript {value, slice, ctx: _} =>
|
||||
Ok(ast::Expr {
|
||||
location: expr.location,
|
||||
@ -485,20 +114,369 @@ impl<'a> ast::fold::Fold<Option<Type>> for ExpressionTypeInferencer<'a> {
|
||||
node: expr.node
|
||||
}),
|
||||
|
||||
ast::ExprKind::ListComp {elt, generators} => {
|
||||
|
||||
ast::ExprKind::ListComp {elt, generators} =>
|
||||
Ok(ast::Expr {
|
||||
location: expr.location,
|
||||
custom: self.infer_list_comprehesion(elt, generators)?,
|
||||
node: expr.node
|
||||
})
|
||||
}),
|
||||
|
||||
// not supported
|
||||
_ => Err("not supported yet".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InferenceContext<'a> {
|
||||
fn infer_constant(&self, constant: &ast::Constant) -> Result<Option<Type>, String> {
|
||||
match constant {
|
||||
ast::Constant::Bool(_) =>
|
||||
Ok(Some(self.get_primitive(primitives::BOOL_TYPE))),
|
||||
|
||||
ast::Constant::Int(val) => {
|
||||
let int32: Result<i32, _> = val.try_into();
|
||||
let int64: Result<i64, _> = val.try_into();
|
||||
|
||||
if int32.is_ok() {
|
||||
Ok(Some(self.get_primitive(primitives::INT32_TYPE)))
|
||||
} else if int64.is_ok() {
|
||||
Ok(Some(self.get_primitive(primitives::INT64_TYPE)))
|
||||
} else {
|
||||
Err("Integer out of bound".into())
|
||||
}
|
||||
},
|
||||
|
||||
ast::Constant::Float(_) =>
|
||||
Ok(Some(self.get_primitive(primitives::FLOAT_TYPE))),
|
||||
|
||||
ast::Constant::Tuple(vals) => {
|
||||
let result = vals
|
||||
.into_iter()
|
||||
.map(|x| self.infer_constant(x))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if result.iter().all(|x| x.is_ok()) {
|
||||
Ok(Some(TypeEnum::ParametricType(
|
||||
primitives::TUPLE_TYPE,
|
||||
result
|
||||
.into_iter()
|
||||
.map(|x| x.unwrap().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
).into()))
|
||||
} else {
|
||||
Err("Some elements in tuple cannot be typed".into())
|
||||
}
|
||||
}
|
||||
|
||||
_ => { // not supported
|
||||
Err("not supported yet".into())
|
||||
_ => Err("not supported".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_list(&self, elts: &Vec<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if elts.is_empty() {
|
||||
Ok(Some(TypeEnum::ParametricType(primitives::LIST_TYPE, vec![TypeEnum::BotType.into()]).into()))
|
||||
} else {
|
||||
let types = elts
|
||||
.iter()
|
||||
.map(|x| &x.custom)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if types.iter().all(|x| x.is_some()) {
|
||||
let head = types.iter().next().unwrap(); // here unwrap alone should be fine after the previous check
|
||||
if types.iter().all(|x| x.eq(head)) {
|
||||
Ok(Some(TypeEnum::ParametricType(primitives::LIST_TYPE, vec![(*head).clone().unwrap()]).into()))
|
||||
} else {
|
||||
Err("inhomogeneous list is not allowed".into())
|
||||
}
|
||||
} else {
|
||||
Err("list elements must have some type".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_tuple(&self, elts: &Vec<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
let types = elts
|
||||
.iter()
|
||||
.map(|x| (x.custom).clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if types.iter().all(|x| x.is_some()) {
|
||||
Ok(Some(TypeEnum::ParametricType(
|
||||
primitives::TUPLE_TYPE,
|
||||
types.into_iter().map(|x| x.unwrap()).collect()).into())) // unwrap alone should be fine after the previous check
|
||||
} else {
|
||||
Err("tuple elements must have some type".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_arrtibute(&self, value: &Box<ast::Expr<Option<Type>>>, attr: &str) -> Result<Option<Type>, String> {
|
||||
let ty = value.custom.clone().ok_or_else(|| "no value".to_string())?;
|
||||
if let TypeEnum::TypeVariable(id) = ty.as_ref() {
|
||||
let v = self.get_variable_def(*id);
|
||||
if v.bound.is_empty() {
|
||||
return Err("no fields on unbounded type variable".into());
|
||||
}
|
||||
let ty = v.bound[0].get_base(&self).and_then(|v| v.fields.get(attr));
|
||||
if ty.is_none() {
|
||||
return Err("unknown field".into());
|
||||
}
|
||||
for x in v.bound[1..].iter() {
|
||||
let ty1 = x.get_base(&self).and_then(|v| v.fields.get(attr));
|
||||
if ty1 != ty {
|
||||
return Err("unknown field (type mismatch between variants)".into());
|
||||
}
|
||||
}
|
||||
return Ok(Some(ty.unwrap().clone()));
|
||||
}
|
||||
|
||||
match ty.get_base(&self) {
|
||||
Some(b) => match b.fields.get(attr) {
|
||||
Some(t) => Ok(Some(t.clone())),
|
||||
None => Err("no such field".into()),
|
||||
},
|
||||
None => Err("this object has no fields".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_bool_ops(&self, values: &Vec<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
assert_eq!(values.len(), 2);
|
||||
let left = values[0].custom.clone().ok_or_else(|| "no value".to_string())?;
|
||||
let right = values[1].custom.clone().ok_or_else(|| "no value".to_string())?;
|
||||
let b = self.get_primitive(primitives::BOOL_TYPE);
|
||||
if left == b && right == b {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Err("bool operands must be bool".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn _infer_bin_ops(&self, _left: &Box<ast::Expr<Option<Type>>>, _op: &ast::Operator, _right: &Box<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
Err("no need this function".into())
|
||||
}
|
||||
|
||||
fn infer_unary_ops(&self, op: &ast::Unaryop, operand: &Box<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if let ast::Unaryop::Not = op {
|
||||
if (**operand).custom == Some(self.get_primitive(primitives::BOOL_TYPE)) {
|
||||
Ok(Some(self.get_primitive(primitives::BOOL_TYPE)))
|
||||
} else {
|
||||
Err("logical not must be applied to bool".into())
|
||||
}
|
||||
} else {
|
||||
inference_core::resolve_call(&self, (**operand).custom.clone(), magic_methods::unaryop_name(op), &[])
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_compare(&self, left: &Box<ast::Expr<Option<Type>>>, ops: &Vec<ast::Cmpop>, comparators: &Vec<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
assert!(comparators.len() > 0);
|
||||
if left.custom.is_none() || (!comparators.iter().all(|x| x.custom.is_some())) {
|
||||
Err("comparison operands must have type".into())
|
||||
} else {
|
||||
let bool_type = Some(self.get_primitive(primitives::BOOL_TYPE));
|
||||
let ty_first = inference_core::resolve_call(
|
||||
&self,
|
||||
Some(left.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?.clone()),
|
||||
magic_methods::comparison_name(&ops[0]).ok_or_else(|| "unsupported comparison".to_string())?,
|
||||
&[comparators[0].custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?])?;
|
||||
if ty_first != bool_type {
|
||||
return Err("comparison result must be boolean".into());
|
||||
}
|
||||
|
||||
for ((a, b), op)
|
||||
in comparators[..(comparators.len() - 1)]
|
||||
.iter()
|
||||
.zip(comparators[1..].iter())
|
||||
.zip(ops[1..].iter()) {
|
||||
let ty = inference_core::resolve_call(
|
||||
&self,
|
||||
Some(a.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?.clone()),
|
||||
magic_methods::comparison_name(op).ok_or_else(|| "unsupported comparison".to_string())?,
|
||||
&[b.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?.clone()])?;
|
||||
if ty != bool_type {
|
||||
return Err("comparison result must be boolean".into());
|
||||
}
|
||||
}
|
||||
Ok(bool_type)
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_call(&self, func: &Box<ast::Expr<Option<Type>>>, args: &Vec<ast::Expr<Option<Type>>>, _keywords: &Vec<ast::Keyword<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if args.iter().all(|x| x.custom.is_some()) {
|
||||
match &func.node {
|
||||
ast::ExprKind::Name {id, ctx: _}
|
||||
=> inference_core::resolve_call(
|
||||
&self,
|
||||
None,
|
||||
id,
|
||||
&args.iter().map(|x| x.custom.clone().unwrap()).collect::<Vec<_>>()),
|
||||
|
||||
ast::ExprKind::Attribute {value, attr, ctx: _}
|
||||
=> inference_core::resolve_call(
|
||||
&self,
|
||||
Some(value.custom.clone().ok_or_else(|| "no value".to_string())?),
|
||||
&attr,
|
||||
&args.iter().map(|x| x.custom.clone().unwrap()).collect::<Vec<_>>()),
|
||||
|
||||
_ => Err("not supported".into())
|
||||
}
|
||||
} else {
|
||||
Err("function params must have type".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_subscript(&self, value: &Box<ast::Expr<Option<Type>>>, slice: &Box<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
let t = if let TypeEnum::ParametricType(primitives::LIST_TYPE, ls) = value.custom.as_ref().ok_or_else(|| "no value".to_string())?.as_ref() {
|
||||
ls[0].clone()
|
||||
} else {
|
||||
return Err("subscript is not supported for types other than list".into());
|
||||
};
|
||||
|
||||
if let ast::ExprKind::Slice {lower, upper, step} = &slice.node {
|
||||
let int32_type = self.get_primitive(primitives::INT32_TYPE);
|
||||
let l = lower.as_ref().map_or(
|
||||
Ok(&int32_type),
|
||||
|x| x.custom.as_ref().ok_or("lower bound cannot be typped".to_string()))?;
|
||||
let u = upper.as_ref().map_or(
|
||||
Ok(&int32_type),
|
||||
|x| x.custom.as_ref().ok_or("upper bound cannot be typped".to_string()))?;
|
||||
let s = step.as_ref().map_or(
|
||||
Ok(&int32_type),
|
||||
|x| x.custom.as_ref().ok_or("step cannot be typped".to_string()))?;
|
||||
|
||||
if l == &int32_type && u == &int32_type && s == &int32_type {
|
||||
Ok(value.custom.clone())
|
||||
} else {
|
||||
Err("slice must be int32 type".into())
|
||||
}
|
||||
} else if slice.custom == Some(self.get_primitive(primitives::INT32_TYPE)) {
|
||||
Ok(Some(t))
|
||||
} else {
|
||||
Err("slice or index must be int32 type".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_if_expr(&self, test: &Box<ast::Expr<Option<Type>>>, body: &Box<ast::Expr<Option<Type>>>, orelse: &Box<ast::Expr<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if test.custom != Some(self.get_primitive(primitives::BOOL_TYPE)) {
|
||||
Err("test should be bool".into())
|
||||
} else {
|
||||
if body.custom == orelse.custom {
|
||||
Ok(body.custom.clone())
|
||||
} else {
|
||||
Err("divergent type at if expression".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_list_comprehesion(&self, elt: &Box<ast::Expr<Option<Type>>>, generators: &Vec<ast::Comprehension<Option<Type>>>) -> Result<Option<Type>, String> {
|
||||
if generators[0]
|
||||
.ifs
|
||||
.iter()
|
||||
.all(|x| x.custom == Some(self.get_primitive(primitives::BOOL_TYPE))) {
|
||||
Ok(Some(TypeEnum::ParametricType(
|
||||
primitives::LIST_TYPE,
|
||||
vec![elt.custom.clone().ok_or_else(|| "elements should have value".to_string())?]).into()))
|
||||
} else {
|
||||
Err("test must be bool".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn prefold_list_comprehension(&mut self, expr: ast::Expr<Option<Type>>) -> Result<ast::Expr<Option<Type>>, String> {
|
||||
if let ast::Expr {
|
||||
location,
|
||||
custom,
|
||||
node: ast::ExprKind::ListComp {
|
||||
elt,
|
||||
generators}} = expr {
|
||||
// if is list comprehension, need special pre-fold
|
||||
if generators.len() != 1 {
|
||||
return Err("only 1 generator statement is supported".into());
|
||||
}
|
||||
if generators[0].is_async {
|
||||
return Err("async is not supported".into());
|
||||
}
|
||||
|
||||
// fold iter first since it does not contain new identifiers
|
||||
let generators_first_folded = generators
|
||||
.into_iter()
|
||||
.map(|x| -> Result<ast::Comprehension<Option<Type>>, String> {Ok(ast::Comprehension {
|
||||
target: x.target,
|
||||
iter: Box::new(self.fold_expr(*x.iter)?), // fold here
|
||||
ifs: x.ifs,
|
||||
is_async: x.is_async
|
||||
})})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
if let TypeEnum::ParametricType(
|
||||
primitives::LIST_TYPE,
|
||||
ls) = generators_first_folded[0]
|
||||
.iter
|
||||
.custom
|
||||
.as_ref()
|
||||
.ok_or_else(|| "no value".to_string())?
|
||||
.as_ref()
|
||||
.clone() {
|
||||
self.with_scope(|ctx| -> Result<ast::Expr<Option<Type>>, String> {
|
||||
ctx.infer_simple_binding(
|
||||
&generators_first_folded[0].target,
|
||||
ls[0].clone())?;
|
||||
Ok(ast::Expr {
|
||||
location,
|
||||
custom,
|
||||
node: ast::ExprKind::ListComp { // now fold things with new name
|
||||
elt: Box::new(ctx.fold_expr(*elt)?),
|
||||
generators: generators_first_folded
|
||||
.into_iter()
|
||||
.map(|x| -> Result<ast::Comprehension<Option<Type>>, String> {Ok(ast::Comprehension {
|
||||
target: Box::new(ctx.fold_expr(*x.target)?),
|
||||
iter: x.iter,
|
||||
ifs: x
|
||||
.ifs
|
||||
.into_iter()
|
||||
.map(|x| ctx.fold_expr(x))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
is_async: x.is_async
|
||||
})})
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
}
|
||||
})
|
||||
}).1
|
||||
} else {
|
||||
Err("iteration is supported for list only".into())
|
||||
}
|
||||
} else {
|
||||
panic!("this function is for list comprehensions only!");
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_simple_binding(&mut self, name: &ast::Expr<Option<Type>>, ty: Type) -> Result<(), String> {
|
||||
match &name.node {
|
||||
ast::ExprKind::Name {id, ctx: _} => {
|
||||
if id == "_" {
|
||||
Ok(())
|
||||
} else if self.defined(id) {
|
||||
Err("duplicated naming".into())
|
||||
} else {
|
||||
self.assign(id.clone(), ty, name.location)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
ast::ExprKind::Tuple {elts, ctx: _} => {
|
||||
if let TypeEnum::ParametricType(primitives::TUPLE_TYPE, ls) = ty.as_ref() {
|
||||
if elts.len() == ls.len() {
|
||||
for (a, b) in elts.iter().zip(ls.iter()) {
|
||||
self.infer_simple_binding(a, b.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err("different length".into())
|
||||
}
|
||||
} else {
|
||||
Err("not supported".into())
|
||||
}
|
||||
}
|
||||
_ => Err("not supported".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod test {
|
||||
@ -507,7 +485,7 @@ pub mod test {
|
||||
use rustpython_parser::ast::{self, Expr, fold::Fold};
|
||||
use super::*;
|
||||
|
||||
pub fn new_ctx<'a>() -> ExpressionTypeInferencer<'a>{
|
||||
pub fn new_ctx<'a>() -> InferenceContext<'a>{
|
||||
struct S;
|
||||
|
||||
impl SymbolResolver for S {
|
||||
@ -524,9 +502,7 @@ pub mod test {
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionTypeInferencer {
|
||||
ctx: InferenceContext::new(primitives::basic_ctx(), Box::new(S{}), FileID(3)),
|
||||
}
|
||||
InferenceContext::new(primitives::basic_ctx(), Box::new(S{}), FileID(3))
|
||||
}
|
||||
|
||||
|
||||
@ -552,7 +528,7 @@ pub mod test {
|
||||
new_ast,
|
||||
Expr {
|
||||
location: location,
|
||||
custom: Some(inferencer.ctx.get_primitive(primitives::INT64_TYPE)),
|
||||
custom: Some(inferencer.get_primitive(primitives::INT64_TYPE)),
|
||||
node: ast::ExprKind::Constant {
|
||||
value: ast::Constant::Int(num.into()),
|
||||
kind: None,
|
||||
@ -598,13 +574,13 @@ pub mod test {
|
||||
new_ast,
|
||||
Expr {
|
||||
location,
|
||||
custom: Some(TypeEnum::ParametricType(primitives::LIST_TYPE, vec![inferencer.ctx.get_primitive(primitives::INT32_TYPE).into()]).into()),
|
||||
custom: Some(TypeEnum::ParametricType(primitives::LIST_TYPE, vec![inferencer.get_primitive(primitives::INT32_TYPE).into()]).into()),
|
||||
node: ast::ExprKind::List {
|
||||
ctx: ast::ExprContext::Load,
|
||||
elts: vec![
|
||||
Expr {
|
||||
location,
|
||||
custom: Some(inferencer.ctx.get_primitive(primitives::INT32_TYPE)),
|
||||
custom: Some(inferencer.get_primitive(primitives::INT32_TYPE)),
|
||||
node: ast::ExprKind::Constant {
|
||||
value: ast::Constant::Int(1.into()),
|
||||
kind: None,
|
||||
@ -613,7 +589,8 @@ pub mod test {
|
||||
|
||||
Expr {
|
||||
location,
|
||||
custom: Some(inferencer.ctx.get_primitive(primitives::INT32_TYPE)),
|
||||
custom: Some(inferencer.get_primitive(primitives::INT32_TYPE)),
|
||||
// custom: None,
|
||||
node: ast::ExprKind::Constant {
|
||||
value: ast::Constant::Int(2.into()),
|
||||
kind: None,
|
||||
@ -625,4 +602,4 @@ pub mod test {
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user