start statement check, fix some error message

This commit is contained in:
ychenfo 2021-07-27 16:06:13 +08:00
parent 9603aa644a
commit eb5c029414
4 changed files with 124 additions and 42 deletions

View File

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

View File

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

View File

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

View File

@ -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,7 +426,9 @@ 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())?; self.infer_simple_binding(&target, ls[0].clone())?;
let elt_folded = Box::new(self.fold_expr(*elt)?); let elt_folded = Box::new(self.fold_expr(*elt)?);
let target_folded = Box::new(self.fold_expr(*target)?); let target_folded = Box::new(self.fold_expr(*target)?);
@ -363,10 +437,13 @@ impl<'a> TypeInferencer<'a> {
.map(|x| self.fold_expr(x)) .map(|x| self.fold_expr(x))
.collect::<Result<Vec<ast::Expr<Option<Type>>>, _>>()?; .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))) {
// only pop the error stack when return Ok(..)
self.error_stack.pop();
Ok(ast::Expr { Ok(ast::Expr {
location, location,
custom: Some(TypeEnum::ParametricType( custom: Some(TypeEnum::ParametricType(
@ -388,12 +465,12 @@ impl<'a> TypeInferencer<'a> {
} else { } else {
Err("test must be bool".into()) 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();