forked from M-Labs/nac3
start statement check, fix some error message
This commit is contained in:
parent
9603aa644a
commit
eb5c029414
|
@ -173,7 +173,8 @@ mod tests {
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
fn get_inference_context(ctx: GlobalContext) -> InferenceContext {
|
fn get_inference_context(ctx: GlobalContext) -> InferenceContext {
|
||||||
InferenceContext::new(ctx, Box::new(|_| Err("unbounded identifier".into())))
|
// InferenceContext::new(ctx, Box::new(|_| Err("unbounded identifier".into())))
|
||||||
|
crate::typecheck::type_check::test::new_ctx().ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use rustpython_parser::ast;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub struct FileID(u32);
|
pub struct FileID(pub u32);
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum Location {
|
pub enum Location {
|
||||||
|
|
|
@ -5,4 +5,4 @@ pub mod magic_methods;
|
||||||
pub mod primitives;
|
pub mod primitives;
|
||||||
pub mod symbol_resolver;
|
pub mod symbol_resolver;
|
||||||
pub mod typedef;
|
pub mod typedef;
|
||||||
pub mod expression_inference;
|
pub mod type_check;
|
||||||
|
|
|
@ -7,6 +7,15 @@ use crate::typecheck::typedef::{Type, TypeEnum};
|
||||||
use crate::typecheck::primitives;
|
use crate::typecheck::primitives;
|
||||||
use rustpython_parser::ast;
|
use rustpython_parser::ast;
|
||||||
|
|
||||||
|
struct NaiveFolder;
|
||||||
|
impl ast::fold::Fold<()> for NaiveFolder {
|
||||||
|
type TargetU = Option<Type>;
|
||||||
|
type Error = String;
|
||||||
|
fn map_user(&mut self, _user: ()) -> Result<Self::TargetU, Self::Error> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TypeInferencer<'a> {
|
pub struct TypeInferencer<'a> {
|
||||||
pub ctx: InferenceContext<'a>,
|
pub ctx: InferenceContext<'a>,
|
||||||
pub error_stack: Vec<(String, ast::Location)>
|
pub error_stack: Vec<(String, ast::Location)>
|
||||||
|
@ -44,7 +53,7 @@ impl<'a> ast::fold::Fold<()> for TypeInferencer<'a> {
|
||||||
ast::ExprKind::Call {func, args, keywords} => self.infer_call(func, args, keywords),
|
ast::ExprKind::Call {func, args, keywords} => self.infer_call(func, args, keywords),
|
||||||
ast::ExprKind::Subscript {value, slice, ctx: _} => self.infer_subscript(value, slice),
|
ast::ExprKind::Subscript {value, slice, ctx: _} => self.infer_subscript(value, slice),
|
||||||
ast::ExprKind::IfExp {test, body, orelse} => self.infer_if_expr(test, body, orelse),
|
ast::ExprKind::IfExp {test, body, orelse} => self.infer_if_expr(test, body, orelse),
|
||||||
ast::ExprKind::ListComp {elt: _, generators: _} => panic!("should not earch here, the list comp should be folded before"), // already folded
|
ast::ExprKind::ListComp {elt: _, generators: _} => unreachable!("should not earch here, the list comp should have been folded before"), // already folded
|
||||||
ast::ExprKind::Slice { .. } => Ok(None), // special handling for slice, which is supported
|
ast::ExprKind::Slice { .. } => Ok(None), // special handling for slice, which is supported
|
||||||
_ => Err("not supported yet".into())
|
_ => Err("not supported yet".into())
|
||||||
}?,
|
}?,
|
||||||
|
@ -54,6 +63,65 @@ impl<'a> ast::fold::Fold<()> for TypeInferencer<'a> {
|
||||||
self.error_stack.pop();
|
self.error_stack.pop();
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fold_stmt(&mut self, node: ast::Stmt<()>) -> Result<ast::Stmt<Self::TargetU>, Self::Error> {
|
||||||
|
let stmt = match node.node {
|
||||||
|
ast::StmtKind::AnnAssign {target, annotation, value, simple} => {
|
||||||
|
let target_folded = Box::new(self.fold_expr( *target)?);
|
||||||
|
let value = if let Some(v) = value {
|
||||||
|
let value_folded = Box::new(self.fold_expr(*v)?);
|
||||||
|
if target_folded.custom == value_folded.custom {
|
||||||
|
Some(value_folded)
|
||||||
|
} else {
|
||||||
|
return Err("Assignment LHF does not have the same type as RHS".into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
// TODO check consistency with type annotation
|
||||||
|
ast::Located {
|
||||||
|
location: node.location,
|
||||||
|
custom: None,
|
||||||
|
node: ast::StmtKind::AnnAssign {
|
||||||
|
target: target_folded,
|
||||||
|
annotation: Box::new(NaiveFolder.fold_expr(*annotation)?),
|
||||||
|
value,
|
||||||
|
simple
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ast::fold::fold_stmt(self, node)?
|
||||||
|
};
|
||||||
|
|
||||||
|
match &stmt.node {
|
||||||
|
ast::StmtKind::For { target, iter, .. } => {
|
||||||
|
if let Some(TypeEnum::ParametricType(primitives::LIST_TYPE, ls)) = iter.custom.as_deref() {
|
||||||
|
unimplemented!()
|
||||||
|
// TODO
|
||||||
|
} else {
|
||||||
|
return Err("can only iterate over list".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::StmtKind::If { test, .. } | ast::StmtKind::While { test, .. } => {
|
||||||
|
if test.custom != Some(self.ctx.get_primitive(primitives::BOOL_TYPE)) {
|
||||||
|
return Err("Test should be bool".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::StmtKind::Assign { targets, value, .. } => {
|
||||||
|
unimplemented!();
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
ast::StmtKind::AnnAssign { .. } | ast::StmtKind::Expr { .. } => {}
|
||||||
|
ast::StmtKind::Break | ast::StmtKind::Continue => {}
|
||||||
|
ast::StmtKind::Return { value } => {
|
||||||
|
unimplemented!()
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
_ => return Err("Unsupported statement type".to_string()),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(stmt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TypeInferencer<'a> {
|
impl<'a> TypeInferencer<'a> {
|
||||||
|
@ -185,6 +253,7 @@ impl<'a> TypeInferencer<'a> {
|
||||||
Some(left.custom.clone().ok_or_else(|| "no value".to_string())?),
|
Some(left.custom.clone().ok_or_else(|| "no value".to_string())?),
|
||||||
magic_methods::binop_name(op),
|
magic_methods::binop_name(op),
|
||||||
&[right.custom.clone().ok_or_else(|| "no value".to_string())?])
|
&[right.custom.clone().ok_or_else(|| "no value".to_string())?])
|
||||||
|
.map_err(|_| "unsupported binary operator between the oprands".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_unary_ops(&self, op: &ast::Unaryop, operand: &ast::Expr<Option<Type>>) -> Result<Option<Type>, String> {
|
fn infer_unary_ops(&self, op: &ast::Unaryop, operand: &ast::Expr<Option<Type>>) -> Result<Option<Type>, String> {
|
||||||
|
@ -196,6 +265,7 @@ impl<'a> TypeInferencer<'a> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inference_core::resolve_call(&self.ctx, operand.custom.clone(), magic_methods::unaryop_name(op), &[])
|
inference_core::resolve_call(&self.ctx, operand.custom.clone(), magic_methods::unaryop_name(op), &[])
|
||||||
|
.map_err(|_| "unsupported unary operator".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +278,8 @@ impl<'a> TypeInferencer<'a> {
|
||||||
&self.ctx,
|
&self.ctx,
|
||||||
Some(left.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?),
|
Some(left.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?),
|
||||||
magic_methods::comparison_name(&ops[0]).ok_or_else(|| "unsupported comparison".to_string())?,
|
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())?])?;
|
&[comparators[0].custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?])
|
||||||
|
.map_err(|_| "Comparison between the comparators are not supportes".to_string())?;
|
||||||
if ty_first != bool_type {
|
if ty_first != bool_type {
|
||||||
return Err("comparison result must be boolean".into());
|
return Err("comparison result must be boolean".into());
|
||||||
}
|
}
|
||||||
|
@ -222,7 +293,8 @@ impl<'a> TypeInferencer<'a> {
|
||||||
&self.ctx,
|
&self.ctx,
|
||||||
Some(a.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?.clone()),
|
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())?,
|
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()])?;
|
&[b.custom.clone().ok_or_else(|| "comparator must be able to be typed".to_string())?.clone()])
|
||||||
|
.map_err(|_| "Comparison between the comparators are not supportes".to_string())?;
|
||||||
if ty != bool_type {
|
if ty != bool_type {
|
||||||
return Err("comparison result must be boolean".into());
|
return Err("comparison result must be boolean".into());
|
||||||
}
|
}
|
||||||
|
@ -354,46 +426,51 @@ impl<'a> TypeInferencer<'a> {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.clone() {
|
.clone() {
|
||||||
|
|
||||||
|
let result: Result<ast::Expr<Option<Type>>, String>;
|
||||||
self.ctx.start_scope();
|
self.ctx.start_scope();
|
||||||
self.infer_simple_binding(&target, ls[0].clone())?;
|
{
|
||||||
let elt_folded = Box::new(self.fold_expr(*elt)?);
|
self.infer_simple_binding(&target, ls[0].clone())?;
|
||||||
let target_folded = Box::new(self.fold_expr(*target)?);
|
let elt_folded = Box::new(self.fold_expr(*elt)?);
|
||||||
let ifs_folded = ifs
|
let target_folded = Box::new(self.fold_expr(*target)?);
|
||||||
.into_iter()
|
let ifs_folded = ifs
|
||||||
.map(|x| self.fold_expr(x))
|
.into_iter()
|
||||||
.collect::<Result<Vec<ast::Expr<Option<Type>>>, _>>()?;
|
.map(|x| self.fold_expr(x))
|
||||||
|
.collect::<Result<Vec<ast::Expr<Option<Type>>>, _>>()?;
|
||||||
|
|
||||||
let result =
|
result =
|
||||||
if ifs_folded
|
if ifs_folded
|
||||||
.iter()
|
.iter()
|
||||||
.all(|x| x.custom == Some(self.ctx.get_primitive(primitives::BOOL_TYPE))) {
|
.all(|x| x.custom == Some(self.ctx.get_primitive(primitives::BOOL_TYPE))) {
|
||||||
Ok(ast::Expr {
|
// only pop the error stack when return Ok(..)
|
||||||
location,
|
self.error_stack.pop();
|
||||||
custom: Some(TypeEnum::ParametricType(
|
|
||||||
primitives::LIST_TYPE,
|
Ok(ast::Expr {
|
||||||
vec![elt_folded
|
location,
|
||||||
.custom
|
custom: Some(TypeEnum::ParametricType(
|
||||||
.clone()
|
primitives::LIST_TYPE,
|
||||||
.ok_or_else(|| "elements cannot be typped".to_string())?]).into()),
|
vec![elt_folded
|
||||||
node: ast::ExprKind::ListComp {
|
.custom
|
||||||
elt: elt_folded,
|
.clone()
|
||||||
generators: vec![ast::Comprehension {
|
.ok_or_else(|| "elements cannot be typped".to_string())?]).into()),
|
||||||
target: target_folded,
|
node: ast::ExprKind::ListComp {
|
||||||
ifs: ifs_folded,
|
elt: elt_folded,
|
||||||
iter: iter_folded,
|
generators: vec![ast::Comprehension {
|
||||||
is_async
|
target: target_folded,
|
||||||
}]
|
ifs: ifs_folded,
|
||||||
}
|
iter: iter_folded,
|
||||||
})
|
is_async
|
||||||
} else {
|
}]
|
||||||
Err("test must be bool".into())
|
}
|
||||||
};
|
})
|
||||||
|
} else {
|
||||||
|
Err("test must be bool".into())
|
||||||
|
};
|
||||||
|
}
|
||||||
self.ctx.end_scope();
|
self.ctx.end_scope();
|
||||||
result
|
result
|
||||||
} else {
|
} else {
|
||||||
Err("iteration is supported for list only".into())
|
Err("iteration is supported for list only".into())
|
||||||
};
|
};
|
||||||
self.error_stack.pop();
|
|
||||||
ret
|
ret
|
||||||
} else {
|
} else {
|
||||||
panic!("this function is for list comprehensions only!");
|
panic!("this function is for list comprehensions only!");
|
||||||
|
@ -405,11 +482,13 @@ impl<'a> TypeInferencer<'a> {
|
||||||
let ret = match &name.node {
|
let ret = match &name.node {
|
||||||
ast::ExprKind::Name {id, ctx: _} => {
|
ast::ExprKind::Name {id, ctx: _} => {
|
||||||
if id == "_" {
|
if id == "_" {
|
||||||
|
self.error_stack.pop();
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if self.ctx.defined(id) {
|
} else if self.ctx.defined(id) {
|
||||||
Err("duplicated naming".into())
|
Err("duplicated naming".into())
|
||||||
} else {
|
} else {
|
||||||
self.ctx.assign(id.clone(), ty, name.location)?;
|
self.ctx.assign(id.clone(), ty, name.location)?;
|
||||||
|
self.error_stack.pop();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -420,6 +499,7 @@ impl<'a> TypeInferencer<'a> {
|
||||||
for (a, b) in elts.iter().zip(ls.iter()) {
|
for (a, b) in elts.iter().zip(ls.iter()) {
|
||||||
self.infer_simple_binding(a, b.clone())?;
|
self.infer_simple_binding(a, b.clone())?;
|
||||||
}
|
}
|
||||||
|
self.error_stack.pop();
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("different length".into())
|
Err("different length".into())
|
||||||
|
@ -430,7 +510,7 @@ impl<'a> TypeInferencer<'a> {
|
||||||
}
|
}
|
||||||
_ => Err("not supported".into())
|
_ => Err("not supported".into())
|
||||||
};
|
};
|
||||||
self.error_stack.pop();
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,6 +742,7 @@ pub mod test {
|
||||||
#[test_case("1 + False")]
|
#[test_case("1 + False")]
|
||||||
#[test_case("1 < 2 > False")]
|
#[test_case("1 < 2 > False")]
|
||||||
#[test_case("not 2")]
|
#[test_case("not 2")]
|
||||||
|
#[test_case("-True")]
|
||||||
fn test_err_msg(prog: &'static str) {
|
fn test_err_msg(prog: &'static str) {
|
||||||
let mut inf = new_ctx();
|
let mut inf = new_ctx();
|
||||||
let ast = rustpython_parser::parser::parse_expression(prog).unwrap();
|
let ast = rustpython_parser::parser::parse_expression(prog).unwrap();
|
Loading…
Reference in New Issue