artiq, core: Return HashSet of error messages for analysis
This commit is contained in:
parent
7e4dab15ae
commit
0fd659b0bd
|
@ -630,6 +630,7 @@ name = "nac3artiq"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"inkwell",
|
||||
"itertools 0.11.0",
|
||||
"nac3core",
|
||||
"nac3ld",
|
||||
"nac3parser",
|
||||
|
|
|
@ -9,6 +9,7 @@ name = "nac3artiq"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.11"
|
||||
pyo3 = { version = "0.20", features = ["extension-module"] }
|
||||
parking_lot = "0.12"
|
||||
tempfile = "3.8"
|
||||
|
|
|
@ -13,6 +13,7 @@ use inkwell::{
|
|||
targets::*,
|
||||
OptimizationLevel,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nac3core::codegen::{CodeGenLLVMOptions, CodeGenTargetMachineOptions, gen_func_impl};
|
||||
use nac3core::toplevel::builtins::get_exn_constructor;
|
||||
use nac3core::typecheck::typedef::{TypeEnum, Unifier};
|
||||
|
@ -474,11 +475,11 @@ impl Nac3 {
|
|||
|
||||
if let Err(e) = composer.start_analysis(true) {
|
||||
// report error of __modinit__ separately
|
||||
if !e.contains("<nac3_synthesized_modinit>") {
|
||||
return Err(CompileError::new_err(format!(
|
||||
return if !e.iter().any(|err| err.contains("<nac3_synthesized_modinit>")) {
|
||||
Err(CompileError::new_err(format!(
|
||||
"compilation failed\n----------\n{}",
|
||||
e
|
||||
)));
|
||||
e.into_iter().sorted().join("\n----------\n")
|
||||
)))
|
||||
} else {
|
||||
let msg = Self::report_modinit(
|
||||
&arg_names,
|
||||
|
@ -488,10 +489,10 @@ impl Nac3 {
|
|||
&mut composer.unifier,
|
||||
&self.primitive,
|
||||
);
|
||||
return Err(CompileError::new_err(format!(
|
||||
Err(CompileError::new_err(format!(
|
||||
"compilation failed\n----------\n{}",
|
||||
msg.unwrap_or(e)
|
||||
)));
|
||||
msg.unwrap_or(e.into_iter().sorted().join("\n----------\n"))
|
||||
)))
|
||||
}
|
||||
}
|
||||
let top_level = Arc::new(composer.make_top_level_context());
|
||||
|
|
|
@ -144,7 +144,9 @@ fn test_primitives() {
|
|||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
inferencer.check_block(&statements, &mut identifiers).unwrap();
|
||||
let (_, errs) = inferencer.check_block(&statements, &mut identifiers);
|
||||
assert_eq!(errs.is_empty(), true);
|
||||
|
||||
let top_level = Arc::new(TopLevelContext {
|
||||
definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))),
|
||||
unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])),
|
||||
|
@ -346,7 +348,9 @@ fn test_simple_call() {
|
|||
unreachable!()
|
||||
}
|
||||
|
||||
inferencer.check_block(&statements_1, &mut identifiers).unwrap();
|
||||
let (_, errs) = inferencer.check_block(&statements_1, &mut identifiers);
|
||||
assert_eq!(errs.is_empty(), true);
|
||||
|
||||
let top_level = Arc::new(TopLevelContext {
|
||||
definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))),
|
||||
unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])),
|
||||
|
|
|
@ -22,19 +22,19 @@ impl Default for ComposerConfig {
|
|||
|
||||
type DefAst = (Arc<RwLock<TopLevelDef>>, Option<ast::Stmt<()>>);
|
||||
pub struct TopLevelComposer {
|
||||
// list of top level definitions, same as top level context
|
||||
/// List of top level definitions, same as top level context
|
||||
pub definition_ast_list: Vec<DefAst>,
|
||||
// start as a primitive unifier, will add more top_level defs inside
|
||||
/// Start as a primitive unifier, will add more top_level defs inside
|
||||
pub unifier: Unifier,
|
||||
// primitive store
|
||||
/// Primitive store
|
||||
pub primitives_ty: PrimitiveStore,
|
||||
// keyword list to prevent same user-defined name
|
||||
/// Keyword list to prevent same user-defined name
|
||||
pub keyword_list: HashSet<StrRef>,
|
||||
// to prevent duplicate definition
|
||||
/// To prevent duplicate definition
|
||||
pub defined_names: HashSet<String>,
|
||||
// get the class def id of a class method
|
||||
/// Get the class def id of a class method
|
||||
pub method_class: HashMap<DefinitionId, DefinitionId>,
|
||||
// number of built-in function and classes in the definition list, later skip
|
||||
/// Number of built-in function and classes in the definition list, later skip
|
||||
pub builtin_num: usize,
|
||||
pub core_config: ComposerConfig,
|
||||
}
|
||||
|
@ -227,8 +227,8 @@ impl TopLevelComposer {
|
|||
// TODO: Fix this hack. We will generate constructor for classes that inherit
|
||||
// from Exception class (directly or indirectly), but this code cannot handle
|
||||
// subclass of other exception classes.
|
||||
let mut contains_constructor = bases
|
||||
.iter().any(|base| matches!(base.node, ast::ExprKind::Name { id, .. } if id == exception_id));
|
||||
let mut contains_constructor = bases.iter()
|
||||
.any(|base| matches!(base.node, ast::ExprKind::Name { id, .. } if id == exception_id));
|
||||
for b in body {
|
||||
if let ast::StmtKind::FunctionDef { name: method_name, .. } = &b.node {
|
||||
if method_name == &init_id {
|
||||
|
@ -348,25 +348,44 @@ impl TopLevelComposer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn start_analysis(&mut self, inference: bool) -> Result<(), String> {
|
||||
self.analyze_top_level_class_type_var()?;
|
||||
self.analyze_top_level_class_bases()?;
|
||||
self.analyze_top_level_class_fields_methods()?;
|
||||
self.analyze_top_level_function()?;
|
||||
if inference {
|
||||
self.analyze_function_instance()?;
|
||||
pub fn start_analysis(&mut self, inference: bool) -> Result<(), HashSet<String>> {
|
||||
let mut errors = HashSet::new();
|
||||
|
||||
if let Err(errs) = self.analyze_top_level_class_type_var() {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(errs) = self.analyze_top_level_class_bases() {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(errs) = self.analyze_top_level_class_fields_methods() {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(errs) = self.analyze_top_level_function() {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if inference {
|
||||
if let Err(errs) = self.analyze_function_instance() {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// step 1, analyze the type vars associated with top level class
|
||||
fn analyze_top_level_class_type_var(&mut self) -> Result<(), String> {
|
||||
fn analyze_top_level_class_type_var(&mut self) -> Result<(), HashSet<String>> {
|
||||
let def_list = &self.definition_ast_list;
|
||||
let temp_def_list = self.extract_def_list();
|
||||
let unifier = self.unifier.borrow_mut();
|
||||
let primitives_store = &self.primitives_ty;
|
||||
|
||||
let mut analyze = |class_def: &Arc<RwLock<TopLevelDef>>, class_ast: &Option<Stmt>| {
|
||||
let mut errors = HashSet::new();
|
||||
|
||||
// only deal with class def here
|
||||
let mut class_def = class_def.write();
|
||||
let (class_bases_ast, class_def_type_vars, class_resolver) = {
|
||||
|
@ -405,7 +424,7 @@ impl TopLevelComposer {
|
|||
if !is_generic {
|
||||
is_generic = true;
|
||||
} else {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"only single Generic[...] is allowed (at {})",
|
||||
b.location
|
||||
));
|
||||
|
@ -421,7 +440,7 @@ impl TopLevelComposer {
|
|||
}
|
||||
|
||||
// parse the type vars
|
||||
let type_vars = type_var_list
|
||||
let type_vars = match type_var_list
|
||||
.into_iter()
|
||||
.map(|e| {
|
||||
class_resolver.parse_type_annotation(
|
||||
|
@ -431,7 +450,13 @@ impl TopLevelComposer {
|
|||
e,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
.collect::<Result<Vec<_>, _>>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
errors.insert(e);
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
// check if all are unique type vars
|
||||
let all_unique_type_var = {
|
||||
|
@ -445,11 +470,13 @@ impl TopLevelComposer {
|
|||
}
|
||||
})
|
||||
};
|
||||
|
||||
if !all_unique_type_var {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"duplicate type variable occurs (at {})",
|
||||
slice.location
|
||||
));
|
||||
continue
|
||||
}
|
||||
|
||||
// add to TopLevelDef
|
||||
|
@ -460,27 +487,35 @@ impl TopLevelComposer {
|
|||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
if !errors.is_empty() {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
let mut errors = HashSet::new();
|
||||
for (class_def, class_ast) in def_list.iter().skip(self.builtin_num) {
|
||||
if class_ast.is_none() {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = analyze(class_def, class_ast) {
|
||||
errors.insert(e);
|
||||
if let Err(errs) = analyze(class_def, class_ast) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.into_iter().sorted().join("\n----------\n"));
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// step 2, base classes.
|
||||
/// now that the type vars of all classes are done, handle base classes and
|
||||
/// put Self class into the ancestors list. We only allow single inheritance
|
||||
fn analyze_top_level_class_bases(&mut self) -> Result<(), String> {
|
||||
fn analyze_top_level_class_bases(&mut self) -> Result<(), HashSet<String>> {
|
||||
if self.unifier.top_level.is_none() {
|
||||
let ctx = Arc::new(self.make_top_level_context());
|
||||
self.unifier.top_level = Some(ctx);
|
||||
|
@ -492,6 +527,7 @@ impl TopLevelComposer {
|
|||
|
||||
let mut get_direct_parents =
|
||||
|class_def: &Arc<RwLock<TopLevelDef>>, class_ast: &Option<Stmt>| {
|
||||
let mut errors = HashSet::new();
|
||||
let mut class_def = class_def.write();
|
||||
let (class_def_id, class_bases, class_ancestors, class_resolver, class_type_vars) = {
|
||||
if let TopLevelDef::Class {
|
||||
|
@ -529,35 +565,47 @@ impl TopLevelComposer {
|
|||
}
|
||||
|
||||
if has_base {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"a class definition can only have at most one base class \
|
||||
declaration and one generic declaration (at {})",
|
||||
b.location
|
||||
));
|
||||
continue
|
||||
}
|
||||
has_base = true;
|
||||
|
||||
// the function parse_ast_to make sure that no type var occurred in
|
||||
// bast_ty if it is a CustomClassKind
|
||||
let base_ty = parse_ast_to_type_annotation_kinds(
|
||||
let base_ty = match parse_ast_to_type_annotation_kinds(
|
||||
class_resolver,
|
||||
&temp_def_list,
|
||||
unifier,
|
||||
&primitive_types,
|
||||
b,
|
||||
vec![(*class_def_id, class_type_vars.clone())].into_iter().collect(),
|
||||
)?;
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
errors.insert(e);
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
if let TypeAnnotation::CustomClass { .. } = &base_ty {
|
||||
class_ancestors.push(base_ty);
|
||||
} else {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"class base declaration can only be custom class (at {})",
|
||||
b.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
if !errors.is_empty() {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
// first, only push direct parent into the list
|
||||
|
@ -566,12 +614,13 @@ impl TopLevelComposer {
|
|||
if class_ast.is_none() {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = get_direct_parents(class_def, class_ast) {
|
||||
errors.insert(e);
|
||||
if let Err(errs) = get_direct_parents(class_def, class_ast) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.into_iter().sorted().join("\n----------\n"));
|
||||
return Err(errors);
|
||||
}
|
||||
|
||||
// second, get all ancestors
|
||||
|
@ -585,6 +634,7 @@ impl TopLevelComposer {
|
|||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
ancestors_store.insert(
|
||||
class_id,
|
||||
// if class has direct parents, get all ancestors of its parents. Else just empty
|
||||
|
@ -594,18 +644,22 @@ impl TopLevelComposer {
|
|||
Self::get_all_ancestors_helper(&class_ancestors[0], temp_def_list.as_slice())?
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (class_def, ast) in self.definition_ast_list.iter().skip(self.builtin_num) {
|
||||
if ast.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = get_all_ancestors(class_def) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.into_iter().sorted().join("\n----------\n"));
|
||||
return Err(errors);
|
||||
}
|
||||
|
||||
// insert the ancestors to the def list
|
||||
|
@ -643,7 +697,9 @@ impl TopLevelComposer {
|
|||
stmt.node,
|
||||
ast::StmtKind::FunctionDef { .. } | ast::StmtKind::AnnAssign { .. }
|
||||
) {
|
||||
return Err("Classes inherited from exception should have no custom fields/methods".into());
|
||||
return Err(
|
||||
HashSet::from(["Classes inherited from exception should have no custom fields/methods".into()])
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -666,7 +722,7 @@ impl TopLevelComposer {
|
|||
}
|
||||
|
||||
/// step 3, class fields and methods
|
||||
fn analyze_top_level_class_fields_methods(&mut self) -> Result<(), String> {
|
||||
fn analyze_top_level_class_fields_methods(&mut self) -> Result<(), HashSet<String>> {
|
||||
let temp_def_list = self.extract_def_list();
|
||||
let primitives = &self.primitives_ty;
|
||||
let def_ast_list = &self.definition_ast_list;
|
||||
|
@ -689,12 +745,12 @@ impl TopLevelComposer {
|
|||
&mut type_var_to_concrete_def,
|
||||
(&self.keyword_list, &self.core_config),
|
||||
) {
|
||||
errors.insert(e);
|
||||
errors.extend(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.into_iter().sorted().join("\n----------\n"));
|
||||
return Err(errors);
|
||||
}
|
||||
|
||||
// handle the inherited methods and fields
|
||||
|
@ -709,19 +765,26 @@ impl TopLevelComposer {
|
|||
if class_ast.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut class_def = class_def.write();
|
||||
if let TopLevelDef::Class { ancestors, .. } = class_def.deref() {
|
||||
// if the length of the ancestor is equal to the current depth
|
||||
// it means that all the ancestors of the class is handled
|
||||
if ancestors.len() == current_ancestor_depth {
|
||||
finished = false;
|
||||
Self::analyze_single_class_ancestors(
|
||||
match Self::analyze_single_class_ancestors(
|
||||
class_def.deref_mut(),
|
||||
&temp_def_list,
|
||||
unifier,
|
||||
primitives,
|
||||
&mut type_var_to_concrete_def,
|
||||
)?;
|
||||
) {
|
||||
Ok(()) => (),
|
||||
Err(errs) => {
|
||||
errors.extend(errs);
|
||||
return Err(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -743,28 +806,33 @@ impl TopLevelComposer {
|
|||
let target_ty =
|
||||
get_type_from_type_annotation_kinds(&temp_def_list, unifier, primitives, &def, &mut subst_list)?;
|
||||
unifier.unify(ty, target_ty).map_err(|e| e.to_display(unifier).to_string())?;
|
||||
Ok(()) as Result<(), String>
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (ty, def) in type_var_to_concrete_def {
|
||||
if let Err(e) = unification_helper(ty, def) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
for ty in subst_list.unwrap().into_iter() {
|
||||
if let TypeEnum::TObj { obj_id, params, fields } = &*unifier.get_ty(ty) {
|
||||
let mut new_fields = HashMap::new();
|
||||
let mut need_subst = false;
|
||||
|
||||
for (name, (ty, mutable)) in fields.iter() {
|
||||
let substituted = unifier.subst(*ty, params);
|
||||
need_subst |= substituted.is_some();
|
||||
new_fields.insert(*name, (substituted.unwrap_or(*ty), *mutable));
|
||||
}
|
||||
|
||||
if need_subst {
|
||||
let new_ty = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: *obj_id,
|
||||
params: params.clone(),
|
||||
fields: new_fields,
|
||||
});
|
||||
|
||||
if let Err(e) = unifier.unify(ty, new_ty) {
|
||||
errors.insert(e.to_display(unifier).to_string());
|
||||
}
|
||||
|
@ -773,8 +841,9 @@ impl TopLevelComposer {
|
|||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.into_iter().sorted().join("\n----------\n"));
|
||||
return Err(errors);
|
||||
}
|
||||
|
||||
for (def, _) in def_ast_list.iter().skip(self.builtin_num) {
|
||||
|
@ -793,7 +862,7 @@ impl TopLevelComposer {
|
|||
}
|
||||
|
||||
/// step 4, after class methods are done, top level functions have nothing unknown
|
||||
fn analyze_top_level_function(&mut self) -> Result<(), String> {
|
||||
fn analyze_top_level_function(&mut self) -> Result<(), HashSet<String>> {
|
||||
let def_list = &self.definition_ast_list;
|
||||
let keyword_list = &self.keyword_list;
|
||||
let temp_def_list = self.extract_def_list();
|
||||
|
@ -802,6 +871,8 @@ impl TopLevelComposer {
|
|||
|
||||
let mut errors = HashSet::new();
|
||||
let mut analyze = |function_def: &Arc<RwLock<TopLevelDef>>, function_ast: &Option<Stmt>| {
|
||||
let mut errors = HashSet::new();
|
||||
|
||||
let mut function_def = function_def.write();
|
||||
let function_def = function_def.deref_mut();
|
||||
let function_ast = if let Some(x) = function_ast.as_ref() {
|
||||
|
@ -818,6 +889,7 @@ impl TopLevelComposer {
|
|||
// already have a function type, is class method, skip
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let ast::StmtKind::FunctionDef { args, returns, .. } = &function_ast.node {
|
||||
let resolver = resolver.as_ref();
|
||||
let resolver = resolver.unwrap();
|
||||
|
@ -825,13 +897,15 @@ impl TopLevelComposer {
|
|||
|
||||
let mut function_var_map: HashMap<u32, Type> = HashMap::new();
|
||||
let arg_types = {
|
||||
let mut errors = HashSet::new();
|
||||
|
||||
// make sure no duplicate parameter
|
||||
let mut defined_parameter_name: HashSet<_> = HashSet::new();
|
||||
for x in args.args.iter() {
|
||||
if !defined_parameter_name.insert(x.node.arg)
|
||||
|| keyword_list.contains(&x.node.arg)
|
||||
{
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"top level function must have unique parameter names \
|
||||
and names should not be the same as the keywords (at {})",
|
||||
x.location
|
||||
|
@ -839,6 +913,10 @@ impl TopLevelComposer {
|
|||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors)
|
||||
}
|
||||
|
||||
let arg_with_default: Vec<(
|
||||
&ast::Located<ast::ArgData<()>>,
|
||||
Option<&ast::Expr>,
|
||||
|
@ -855,7 +933,7 @@ impl TopLevelComposer {
|
|||
)
|
||||
.collect_vec();
|
||||
|
||||
arg_with_default
|
||||
match arg_with_default
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|(x, default)| -> Result<FuncArg, String> {
|
||||
|
@ -923,22 +1001,29 @@ impl TopLevelComposer {
|
|||
primitives_store,
|
||||
unifier,
|
||||
)
|
||||
.map_err(
|
||||
|err| format!("{} (at {})", err, x.location),
|
||||
)?;
|
||||
.map_err(
|
||||
|err| format!("{} (at {})", err, x.location),
|
||||
)?;
|
||||
v
|
||||
}),
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
};
|
||||
.collect::<Result<Vec<_>, _>>() {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => {
|
||||
errors.insert(e);
|
||||
Err(errors)
|
||||
},
|
||||
}
|
||||
}?;
|
||||
|
||||
let return_ty = {
|
||||
|
||||
if let Some(returns) = returns {
|
||||
let return_ty_annotation = {
|
||||
let return_annotation = returns.as_ref();
|
||||
parse_ast_to_type_annotation_kinds(
|
||||
match parse_ast_to_type_annotation_kinds(
|
||||
resolver,
|
||||
&temp_def_list,
|
||||
unifier,
|
||||
|
@ -947,11 +1032,13 @@ impl TopLevelComposer {
|
|||
// NOTE: since only class need this, for function
|
||||
// it should be fine to be empty map
|
||||
HashMap::new(),
|
||||
)?
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(HashSet::from([e])),
|
||||
}
|
||||
};
|
||||
|
||||
let type_vars_within =
|
||||
get_type_var_contained_in_type_annotation(&return_ty_annotation)
|
||||
let type_vars_within = match get_type_var_contained_in_type_annotation(&return_ty_annotation)
|
||||
.into_iter()
|
||||
.map(|x| -> Result<(u32, Type), String> {
|
||||
if let TypeAnnotation::TypeVar(ty) = x {
|
||||
|
@ -960,7 +1047,11 @@ impl TopLevelComposer {
|
|||
unreachable!("must be type var here")
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
.collect::<Result<Vec<_>, _>>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(HashSet::from([e])),
|
||||
};
|
||||
|
||||
for (id, ty) in type_vars_within {
|
||||
if let Some(prev_ty) = function_var_map.insert(id, ty) {
|
||||
// if already have the type inserted, make sure they are the same thing
|
||||
|
@ -968,17 +1059,21 @@ impl TopLevelComposer {
|
|||
}
|
||||
}
|
||||
|
||||
get_type_from_type_annotation_kinds(
|
||||
match get_type_from_type_annotation_kinds(
|
||||
&temp_def_list,
|
||||
unifier,
|
||||
primitives_store,
|
||||
&return_ty_annotation,
|
||||
&mut None
|
||||
)?
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(HashSet::from([e])),
|
||||
}
|
||||
} else {
|
||||
primitives_store.none
|
||||
}
|
||||
};
|
||||
|
||||
var_id.extend_from_slice(function_var_map
|
||||
.iter()
|
||||
.filter_map(|(id, ty)| {
|
||||
|
@ -991,35 +1086,46 @@ impl TopLevelComposer {
|
|||
.collect_vec()
|
||||
.as_slice()
|
||||
);
|
||||
|
||||
let function_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: arg_types,
|
||||
ret: return_ty,
|
||||
vars: function_var_map,
|
||||
}));
|
||||
unifier.unify(*dummy_ty, function_ty).map_err(|e| {
|
||||
|
||||
match unifier.unify(*dummy_ty, function_ty).map_err(|e| {
|
||||
e.at(Some(function_ast.location)).to_display(unifier).to_string()
|
||||
})?;
|
||||
}) {
|
||||
Err(e) => {
|
||||
errors.insert(e);
|
||||
Err(errors)
|
||||
}
|
||||
_ => Ok(())
|
||||
}
|
||||
} else {
|
||||
unreachable!("must be both function");
|
||||
}
|
||||
} else {
|
||||
// not top level function def, skip
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (function_def, function_ast) in def_list.iter().skip(self.builtin_num) {
|
||||
if function_ast.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = analyze(function_def, function_ast) {
|
||||
errors.insert(e);
|
||||
errors.extend(e);
|
||||
}
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.into_iter().sorted().join("\n----------\n"));
|
||||
|
||||
return if !errors.is_empty() {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_single_class_methods_fields(
|
||||
|
@ -1030,7 +1136,8 @@ impl TopLevelComposer {
|
|||
primitives: &PrimitiveStore,
|
||||
type_var_to_concrete_def: &mut HashMap<Type, TypeAnnotation>,
|
||||
core_info: (&HashSet<StrRef>, &ComposerConfig),
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), HashSet<String>> {
|
||||
let mut errors = HashSet::new();
|
||||
let (keyword_list, core_config) = core_info;
|
||||
let mut class_def = class_def.write();
|
||||
let (
|
||||
|
@ -1061,6 +1168,7 @@ impl TopLevelComposer {
|
|||
} else {
|
||||
unreachable!("here must be toplevel class def");
|
||||
};
|
||||
|
||||
let class_resolver = class_resolver.as_ref().unwrap();
|
||||
let class_resolver = class_resolver.as_ref();
|
||||
|
||||
|
@ -1068,8 +1176,10 @@ impl TopLevelComposer {
|
|||
for b in class_body_ast {
|
||||
match &b.node {
|
||||
ast::StmtKind::FunctionDef { args, returns, name, .. } => {
|
||||
let (method_dummy_ty, method_id) =
|
||||
Self::get_class_method_def_info(class_methods_def, *name)?;
|
||||
let (method_dummy_ty, method_id) = match Self::get_class_method_def_info(class_methods_def, *name) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(HashSet::from([e])),
|
||||
};
|
||||
|
||||
let mut method_var_map: HashMap<u32, Type> = HashMap::new();
|
||||
|
||||
|
@ -1081,7 +1191,7 @@ impl TopLevelComposer {
|
|||
if !defined_parameter_name.insert(x.node.arg)
|
||||
|| (keyword_list.contains(&x.node.arg) && x.node.arg != zelf)
|
||||
{
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"top level function must have unique parameter names \
|
||||
and names should not be the same as the keywords (at {})",
|
||||
x.location
|
||||
|
@ -1090,13 +1200,13 @@ impl TopLevelComposer {
|
|||
}
|
||||
|
||||
if name == &"__init__".into() && !defined_parameter_name.contains(&zelf) {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"__init__ method must have a `self` parameter (at {})",
|
||||
b.location
|
||||
));
|
||||
}
|
||||
if !defined_parameter_name.contains(&zelf) {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"class method must have a `self` parameter (at {})",
|
||||
b.location
|
||||
));
|
||||
|
@ -1124,18 +1234,19 @@ impl TopLevelComposer {
|
|||
let name = x.node.arg;
|
||||
if name != zelf {
|
||||
let type_ann = {
|
||||
let annotation_expr = x
|
||||
.node
|
||||
.annotation
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
let annotation_expr = match x.node.annotation.as_ref() {
|
||||
Some(v) => v.as_ref(),
|
||||
None => {
|
||||
errors.insert(format!(
|
||||
"type annotation needed for `{}` at {}",
|
||||
x.node.arg, x.location
|
||||
)
|
||||
})?
|
||||
.as_ref();
|
||||
parse_ast_to_type_annotation_kinds(
|
||||
));
|
||||
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
match parse_ast_to_type_annotation_kinds(
|
||||
class_resolver,
|
||||
temp_def_list,
|
||||
unifier,
|
||||
|
@ -1144,15 +1255,23 @@ impl TopLevelComposer {
|
|||
vec![(class_id, class_type_vars_def.clone())]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)?
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(HashSet::from([e])),
|
||||
}
|
||||
};
|
||||
|
||||
// find type vars within this method parameter type annotation
|
||||
let type_vars_within =
|
||||
get_type_var_contained_in_type_annotation(&type_ann);
|
||||
// handle the class type var and the method type var
|
||||
for type_var_within in type_vars_within {
|
||||
if let TypeAnnotation::TypeVar(ty) = type_var_within {
|
||||
let id = Self::get_var_id(ty, unifier)?;
|
||||
let id = match Self::get_var_id(ty, unifier) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(HashSet::from([e])),
|
||||
};
|
||||
|
||||
if let Some(prev_ty) = method_var_map.insert(id, ty) {
|
||||
// if already in the list, make sure they are the same?
|
||||
assert_eq!(prev_ty, ty);
|
||||
|
@ -1169,24 +1288,39 @@ impl TopLevelComposer {
|
|||
None => None,
|
||||
Some(default) => {
|
||||
if name == "self".into() {
|
||||
return Err(format!("`self` parameter cannot take default value (at {})", x.location));
|
||||
errors.insert(format!(
|
||||
"`self` parameter cannot take default value (at {})", x.location
|
||||
));
|
||||
continue
|
||||
}
|
||||
|
||||
let v = match Self::parse_parameter_default_value(
|
||||
default,
|
||||
class_resolver,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
errors.insert(e);
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
match Self::check_default_param_type(
|
||||
&v, &type_ann, primitives, unifier,
|
||||
) {
|
||||
Ok(_) => Some(v),
|
||||
Err(e) => {
|
||||
errors.insert(
|
||||
format!("{} (at {})", e, x.location)
|
||||
);
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
Some({
|
||||
let v = Self::parse_parameter_default_value(
|
||||
default,
|
||||
class_resolver,
|
||||
)?;
|
||||
Self::check_default_param_type(
|
||||
&v, &type_ann, primitives, unifier,
|
||||
)
|
||||
.map_err(|err| {
|
||||
format!("{} (at {})", err, x.location)
|
||||
})?;
|
||||
v
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// push the dummy type and the type annotation
|
||||
// into the list for later unification
|
||||
type_var_to_concrete_def
|
||||
|
@ -1194,27 +1328,40 @@ impl TopLevelComposer {
|
|||
result.push(dummy_func_arg)
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
let ret_type = {
|
||||
if let Some(result) = returns {
|
||||
let result = result.as_ref();
|
||||
let annotation = parse_ast_to_type_annotation_kinds(
|
||||
let annotation = match parse_ast_to_type_annotation_kinds(
|
||||
class_resolver,
|
||||
temp_def_list,
|
||||
unifier,
|
||||
primitives,
|
||||
result,
|
||||
vec![(class_id, class_type_vars_def.clone())].into_iter().collect(),
|
||||
)?;
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
errors.insert(e);
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
// find type vars within this return type annotation
|
||||
let type_vars_within =
|
||||
get_type_var_contained_in_type_annotation(&annotation);
|
||||
|
||||
// handle the class type var and the method type var
|
||||
for type_var_within in type_vars_within {
|
||||
if let TypeAnnotation::TypeVar(ty) = type_var_within {
|
||||
let id = Self::get_var_id(ty, unifier)?;
|
||||
let id = match Self::get_var_id(ty, unifier) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(HashSet::from([e])),
|
||||
};
|
||||
|
||||
if let Some(prev_ty) = method_var_map.insert(id, ty) {
|
||||
// if already in the list, make sure they are the same?
|
||||
assert_eq!(prev_ty, ty);
|
||||
|
@ -1264,10 +1411,15 @@ impl TopLevelComposer {
|
|||
|
||||
// unify now since function type is not in type annotation define
|
||||
// which should be fine since type within method_type will be subst later
|
||||
unifier
|
||||
.unify(method_dummy_ty, method_type)
|
||||
.map_err(|e| e.to_display(unifier).to_string())?;
|
||||
match unifier.unify(method_dummy_ty, method_type) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
errors.insert(e.to_display(unifier).to_string());
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ast::StmtKind::AnnAssign { target, annotation, value: None, .. } => {
|
||||
if let ast::ExprKind::Name { id: attr, .. } = &target.node {
|
||||
if defined_fields.insert(attr.to_string()) {
|
||||
|
@ -1296,26 +1448,35 @@ impl TopLevelComposer {
|
|||
};
|
||||
class_fields_def.push((*attr, dummy_field_type, mutable));
|
||||
|
||||
let parsed_annotation = parse_ast_to_type_annotation_kinds(
|
||||
let parsed_annotation = match parse_ast_to_type_annotation_kinds(
|
||||
class_resolver,
|
||||
temp_def_list,
|
||||
unifier,
|
||||
primitives,
|
||||
annotation.as_ref(),
|
||||
vec![(class_id, class_type_vars_def.clone())].into_iter().collect(),
|
||||
)?;
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
errors.insert(e);
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
// find type vars within this return type annotation
|
||||
let type_vars_within =
|
||||
get_type_var_contained_in_type_annotation(&parsed_annotation);
|
||||
|
||||
// handle the class type var and the method type var
|
||||
for type_var_within in type_vars_within {
|
||||
if let TypeAnnotation::TypeVar(t) = type_var_within {
|
||||
if !class_type_vars_def.contains(&t) {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"class fields can only use type \
|
||||
vars over which the class is generic (at {})",
|
||||
annotation.location
|
||||
));
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
unreachable!("must be type var annotation");
|
||||
|
@ -1323,30 +1484,37 @@ impl TopLevelComposer {
|
|||
}
|
||||
type_var_to_concrete_def.insert(dummy_field_type, parsed_annotation);
|
||||
} else {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"same class fields `{}` defined twice (at {})",
|
||||
attr, target.location
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"unsupported statement type in class definition body (at {})",
|
||||
target.location
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
ast::StmtKind::Assign { .. } => {}, // we don't class attributes
|
||||
ast::StmtKind::Pass { .. } => {}
|
||||
ast::StmtKind::Expr { value: _, .. } => {} // typically a docstring; ignoring all expressions matches CPython behavior
|
||||
|
||||
_ => {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"unsupported statement in class definition body (at {})",
|
||||
b.location
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
if !errors.is_empty() {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze_single_class_ancestors(
|
||||
|
@ -1355,7 +1523,7 @@ impl TopLevelComposer {
|
|||
unifier: &mut Unifier,
|
||||
_primitives: &PrimitiveStore,
|
||||
type_var_to_concrete_def: &mut HashMap<Type, TypeAnnotation>,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), HashSet<String>> {
|
||||
let (
|
||||
_class_id,
|
||||
class_ancestor_def,
|
||||
|
@ -1406,10 +1574,12 @@ impl TopLevelComposer {
|
|||
type_var_to_concrete_def,
|
||||
);
|
||||
if !ok {
|
||||
return Err(format!(
|
||||
"method {} has same name as ancestors' method, but incompatible type",
|
||||
class_method_name
|
||||
));
|
||||
return Err(HashSet::from([
|
||||
format!(
|
||||
"method {} has same name as ancestors' method, but incompatible type",
|
||||
class_method_name
|
||||
)
|
||||
]));
|
||||
}
|
||||
// mark it as added
|
||||
is_override.insert(*class_method_name);
|
||||
|
@ -1444,10 +1614,12 @@ impl TopLevelComposer {
|
|||
// find if there is a fields with the same name in the child class
|
||||
for (class_field_name, ..) in class_fields_def.iter() {
|
||||
if class_field_name == anc_field_name {
|
||||
return Err(format!(
|
||||
"field `{}` has already declared in the ancestor classes",
|
||||
class_field_name
|
||||
));
|
||||
return Err(HashSet::from([
|
||||
format!(
|
||||
"field `{}` has already declared in the ancestor classes",
|
||||
class_field_name
|
||||
)
|
||||
]));
|
||||
}
|
||||
}
|
||||
new_child_fields.push(to_be_added);
|
||||
|
@ -1470,7 +1642,7 @@ impl TopLevelComposer {
|
|||
}
|
||||
|
||||
/// step 5, analyze and call type inferencer to fill the `instance_to_stmt` of topleveldef::function
|
||||
fn analyze_function_instance(&mut self) -> Result<(), String> {
|
||||
fn analyze_function_instance(&mut self) -> Result<(), HashSet<String>> {
|
||||
// first get the class constructor type correct for the following type check in function body
|
||||
// also do class field instantiation check
|
||||
let init_str_id = "__init__".into();
|
||||
|
@ -1521,8 +1693,7 @@ impl TopLevelComposer {
|
|||
object_id,
|
||||
resolver: _,
|
||||
..
|
||||
} = &*class_def
|
||||
{
|
||||
} = &*class_def {
|
||||
let self_type = get_type_from_type_annotation_kinds(
|
||||
&def_list,
|
||||
unifier,
|
||||
|
@ -1642,7 +1813,7 @@ impl TopLevelComposer {
|
|||
}
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.into_iter().sorted().join("\n---------\n"));
|
||||
return Err(errors);
|
||||
}
|
||||
|
||||
for (i, signature, id) in constructors.into_iter() {
|
||||
|
@ -1847,8 +2018,11 @@ impl TopLevelComposer {
|
|||
.map(|b| inferencer.fold_stmt(b))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let returned =
|
||||
inferencer.check_block(fun_body.as_slice(), &mut identifiers)?;
|
||||
let (returned, errs) = inferencer.check_block(fun_body.as_slice(), &mut identifiers);
|
||||
if !errs.is_empty() {
|
||||
return Err(errs.into_iter().sorted().join("\n----------\n"))
|
||||
}
|
||||
|
||||
{
|
||||
// check virtuals
|
||||
let defs = ctx.definitions.read();
|
||||
|
@ -1934,6 +2108,7 @@ impl TopLevelComposer {
|
|||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (id, (def, ast)) in self.definition_ast_list.iter().enumerate().skip(self.builtin_num) {
|
||||
if ast.is_none() {
|
||||
continue;
|
||||
|
@ -1942,9 +2117,11 @@ impl TopLevelComposer {
|
|||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.into_iter().sorted().join("\n----------\n"));
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -551,9 +551,9 @@ fn test_analyze(source: Vec<&str>, res: Vec<&str>) {
|
|||
|
||||
if let Err(msg) = composer.start_analysis(false) {
|
||||
if print {
|
||||
println!("{}", msg);
|
||||
println!("{}", msg.into_iter().sorted().join("\n----------\n"));
|
||||
} else {
|
||||
assert_eq!(res[0], msg);
|
||||
assert_eq!(res[0], msg.into_iter().sorted().next().unwrap());
|
||||
}
|
||||
} else {
|
||||
// skip 5 to skip primitives
|
||||
|
@ -735,9 +735,9 @@ fn test_inference(source: Vec<&str>, res: Vec<&str>) {
|
|||
|
||||
if let Err(msg) = composer.start_analysis(true) {
|
||||
if print {
|
||||
println!("{}", msg);
|
||||
println!("{}", msg.into_iter().sorted().join("\n----------\n"));
|
||||
} else {
|
||||
assert_eq!(res[0], msg);
|
||||
assert_eq!(res[0], msg.into_iter().sorted().next().unwrap());
|
||||
}
|
||||
} else {
|
||||
// skip 5 to skip primitives
|
||||
|
|
|
@ -18,40 +18,68 @@ impl<'a> Inferencer<'a> {
|
|||
&mut self,
|
||||
pattern: &Expr<Option<Type>>,
|
||||
defined_identifiers: &mut HashSet<StrRef>,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), HashSet<String>> {
|
||||
let mut errors = HashSet::new();
|
||||
match &pattern.node {
|
||||
ast::ExprKind::Name { id, .. } if id == &"none".into() =>
|
||||
Err(format!("cannot assign to a `none` (at {})", pattern.location)),
|
||||
ExprKind::Name { id, .. } if id == &"none".into() => {
|
||||
errors.insert(format!("cannot assign to a `none` (at {})", pattern.location));
|
||||
}
|
||||
|
||||
ExprKind::Name { id, .. } => {
|
||||
if !defined_identifiers.contains(id) {
|
||||
defined_identifiers.insert(*id);
|
||||
}
|
||||
self.should_have_value(pattern)?;
|
||||
Ok(())
|
||||
|
||||
if let Err(e) = self.should_have_value(pattern) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
for elt in elts.iter() {
|
||||
self.check_pattern(elt, defined_identifiers)?;
|
||||
self.should_have_value(elt)?;
|
||||
if let Err(e) = self.check_pattern(elt, defined_identifiers) {
|
||||
errors.extend(e);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(elt) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
self.check_expr(slice, defined_identifiers)?;
|
||||
if let Err(e) = self.check_expr(value, defined_identifiers) {
|
||||
errors.extend(e);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(value) {
|
||||
errors.insert(e);
|
||||
}
|
||||
if let Err(e) = self.check_expr(slice, defined_identifiers) {
|
||||
errors.extend(e);
|
||||
}
|
||||
|
||||
if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty(value.custom.unwrap()) {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"Error at {}: cannot assign to tuple element",
|
||||
value.location
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
ExprKind::Constant { .. } => {
|
||||
Err(format!("cannot assign to a constant (at {})", pattern.location))
|
||||
errors.insert(format!("cannot assign to a constant (at {})", pattern.location));
|
||||
}
|
||||
_ => self.check_expr(pattern, defined_identifiers),
|
||||
|
||||
_ => {
|
||||
if let Err(e) = self.check_expr(pattern, defined_identifiers) {
|
||||
errors.extend(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if !errors.is_empty() {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,86 +87,141 @@ impl<'a> Inferencer<'a> {
|
|||
&mut self,
|
||||
expr: &Expr<Option<Type>>,
|
||||
defined_identifiers: &mut HashSet<StrRef>,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), HashSet<String>> {
|
||||
let mut errors = HashSet::new();
|
||||
|
||||
// there are some cases where the custom field is None
|
||||
if let Some(ty) = &expr.custom {
|
||||
if !self.unifier.is_concrete(*ty, &self.function_data.bound_variables) {
|
||||
return Err(format!(
|
||||
errors.insert(format!(
|
||||
"expected concrete type at {} but got {}",
|
||||
expr.location,
|
||||
self.unifier.get_ty(*ty).get_type_name()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
match &expr.node {
|
||||
ExprKind::Name { id, .. } => {
|
||||
if id == &"none".into() {
|
||||
return Ok(());
|
||||
}
|
||||
self.should_have_value(expr)?;
|
||||
if !defined_identifiers.contains(id) {
|
||||
match self.function_data.resolver.get_symbol_type(
|
||||
self.unifier,
|
||||
&self.top_level.definitions.read(),
|
||||
self.primitives,
|
||||
*id,
|
||||
) {
|
||||
Ok(_) => {
|
||||
self.defined_identifiers.insert(*id);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"type error at identifier `{}` ({}) at {}",
|
||||
id, e, expr.location
|
||||
));
|
||||
if id != &"none".into() {
|
||||
if let Err(e) = self.should_have_value(expr) {
|
||||
errors.insert(e);
|
||||
}
|
||||
|
||||
if !defined_identifiers.contains(id) {
|
||||
match self.function_data.resolver.get_symbol_type(
|
||||
self.unifier,
|
||||
&self.top_level.definitions.read(),
|
||||
self.primitives,
|
||||
*id,
|
||||
) {
|
||||
Ok(_) => {
|
||||
self.defined_identifiers.insert(*id);
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
errors.insert(format!(
|
||||
"type error at identifier `{}` ({}) at {}",
|
||||
id, e, expr.location
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::List { elts, .. }
|
||||
| ExprKind::Tuple { elts, .. }
|
||||
| ExprKind::BoolOp { values: elts, .. } => {
|
||||
for elt in elts.iter() {
|
||||
self.check_expr(elt, defined_identifiers)?;
|
||||
self.should_have_value(elt)?;
|
||||
if let Err(errs) = self.check_expr(elt, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(elt) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Attribute { value, .. } => {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
if let Err(errs) = self.check_expr(value, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(value) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
self.check_expr(left, defined_identifiers)?;
|
||||
self.check_expr(right, defined_identifiers)?;
|
||||
self.should_have_value(left)?;
|
||||
self.should_have_value(right)?;
|
||||
if let Err(errs) = self.check_expr(left, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(errs) = self.check_expr(right, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(left) {
|
||||
errors.insert(e);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(right) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::UnaryOp { operand, .. } => {
|
||||
self.check_expr(operand, defined_identifiers)?;
|
||||
self.should_have_value(operand)?;
|
||||
if let Err(errs) = self.check_expr(operand, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(operand) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Compare { left, comparators, .. } => {
|
||||
for elt in once(left.as_ref()).chain(comparators.iter()) {
|
||||
self.check_expr(elt, defined_identifiers)?;
|
||||
self.should_have_value(elt)?;
|
||||
if let Err(errs) = self.check_expr(elt, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(elt) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
self.should_have_value(value)?;
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.check_expr(slice, defined_identifiers)?;
|
||||
if let Err(e) = self.should_have_value(value) {
|
||||
errors.insert(e);
|
||||
}
|
||||
if let Err(errs) = self.check_expr(value, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(errs) = self.check_expr(slice, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::IfExp { test, body, orelse } => {
|
||||
self.check_expr(test, defined_identifiers)?;
|
||||
self.check_expr(body, defined_identifiers)?;
|
||||
self.check_expr(orelse, defined_identifiers)?;
|
||||
if let Err(errs) = self.check_expr(test, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(errs) = self.check_expr(body, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(errs) = self.check_expr(orelse, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Slice { lower, upper, step } => {
|
||||
for elt in [lower.as_ref(), upper.as_ref(), step.as_ref()].iter().flatten() {
|
||||
self.should_have_value(elt)?;
|
||||
self.check_expr(elt, defined_identifiers)?;
|
||||
if let Err(e) = self.should_have_value(elt) {
|
||||
errors.insert(e);
|
||||
}
|
||||
if let Err(errs) = self.check_expr(elt, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Lambda { args, body } => {
|
||||
let mut defined_identifiers = defined_identifiers.clone();
|
||||
for arg in args.args.iter() {
|
||||
|
@ -147,160 +230,286 @@ impl<'a> Inferencer<'a> {
|
|||
defined_identifiers.insert(arg.node.arg);
|
||||
}
|
||||
}
|
||||
self.check_expr(body, &mut defined_identifiers)?;
|
||||
|
||||
if let Err(errs) = self.check_expr(body, &mut defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::ListComp { elt, generators, .. } => {
|
||||
// in our type inference stage, we already make sure that there is only 1 generator
|
||||
let ast::Comprehension { target, iter, ifs, .. } = &generators[0];
|
||||
self.check_expr(iter, defined_identifiers)?;
|
||||
self.should_have_value(iter)?;
|
||||
if let Err(errs) = self.check_expr(iter, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(iter) {
|
||||
errors.insert(e);
|
||||
}
|
||||
|
||||
let mut defined_identifiers = defined_identifiers.clone();
|
||||
self.check_pattern(target, &mut defined_identifiers)?;
|
||||
self.should_have_value(target)?;
|
||||
if let Err(errs) = self.check_pattern(target, &mut defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(target) {
|
||||
errors.insert(e);
|
||||
}
|
||||
|
||||
for term in once(elt.as_ref()).chain(ifs.iter()) {
|
||||
self.check_expr(term, &mut defined_identifiers)?;
|
||||
self.should_have_value(term)?;
|
||||
if let Err(errs) = self.check_expr(term, &mut defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(term) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Call { func, args, keywords } => {
|
||||
for expr in once(func.as_ref())
|
||||
.chain(args.iter())
|
||||
.chain(keywords.iter().map(|v| v.node.value.as_ref()))
|
||||
{
|
||||
self.check_expr(expr, defined_identifiers)?;
|
||||
self.should_have_value(expr)?;
|
||||
if let Err(errs) = self.check_expr(expr, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(expr) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Constant { .. } => {}
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
return if !errors.is_empty() {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// check statements for proper identifier def-use and return on all paths
|
||||
/// Check statements for proper identifier def-use and return on all paths.
|
||||
///
|
||||
/// Returns a tuple containing a `bool` representing whether the statement returns on all paths,
|
||||
/// and a [HashSet] of all collected errors.
|
||||
fn check_stmt(
|
||||
&mut self,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
defined_identifiers: &mut HashSet<StrRef>,
|
||||
) -> Result<bool, String> {
|
||||
match &stmt.node {
|
||||
StmtKind::For { target, iter, body, orelse, .. } => {
|
||||
self.check_expr(iter, defined_identifiers)?;
|
||||
self.should_have_value(iter)?;
|
||||
) -> (bool, HashSet<String>) {
|
||||
let mut errors = HashSet::new();
|
||||
let stmt_returns = match &stmt.node {
|
||||
StmtKind::For {
|
||||
target,
|
||||
iter,
|
||||
body,
|
||||
orelse,
|
||||
..
|
||||
} => {
|
||||
if let Err(errs) = self.check_expr(iter, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(iter) {
|
||||
errors.insert(e);
|
||||
}
|
||||
|
||||
let mut local_defined_identifiers = defined_identifiers.clone();
|
||||
for stmt in orelse.iter() {
|
||||
self.check_stmt(stmt, &mut local_defined_identifiers)?;
|
||||
let (_, errs) = self.check_stmt(stmt, &mut local_defined_identifiers);
|
||||
errors.extend(errs);
|
||||
}
|
||||
|
||||
let mut local_defined_identifiers = defined_identifiers.clone();
|
||||
self.check_pattern(target, &mut local_defined_identifiers)?;
|
||||
self.should_have_value(target)?;
|
||||
for stmt in body.iter() {
|
||||
self.check_stmt(stmt, &mut local_defined_identifiers)?;
|
||||
if let Err(errs) = self.check_pattern(target, &mut local_defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
Ok(false)
|
||||
if let Err(e) = self.should_have_value(target) {
|
||||
errors.insert(e);
|
||||
}
|
||||
|
||||
for stmt in body.iter() {
|
||||
let (_, errs) = self.check_stmt(stmt, &mut local_defined_identifiers);
|
||||
errors.extend(errs);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
StmtKind::If { test, body, orelse, .. } => {
|
||||
self.check_expr(test, defined_identifiers)?;
|
||||
self.should_have_value(test)?;
|
||||
if let Err(errs) = self.check_expr(test, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(test) {
|
||||
errors.insert(e);
|
||||
}
|
||||
|
||||
let mut body_identifiers = defined_identifiers.clone();
|
||||
let mut orelse_identifiers = defined_identifiers.clone();
|
||||
let body_returned = self.check_block(body, &mut body_identifiers)?;
|
||||
let orelse_returned = self.check_block(orelse, &mut orelse_identifiers)?;
|
||||
let (body_returned, errs) = self.check_block(body, &mut body_identifiers);
|
||||
errors.extend(errs);
|
||||
let (orelse_returned, errs) = self.check_block(orelse, &mut orelse_identifiers);
|
||||
errors.extend(errs);
|
||||
|
||||
for ident in body_identifiers.iter() {
|
||||
if !defined_identifiers.contains(ident) && orelse_identifiers.contains(ident) {
|
||||
defined_identifiers.insert(*ident);
|
||||
}
|
||||
}
|
||||
Ok(body_returned && orelse_returned)
|
||||
|
||||
body_returned && orelse_returned
|
||||
}
|
||||
|
||||
StmtKind::While { test, body, orelse, .. } => {
|
||||
self.check_expr(test, defined_identifiers)?;
|
||||
self.should_have_value(test)?;
|
||||
if let Err(errs) = self.check_expr(test, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(test) {
|
||||
errors.insert(e);
|
||||
}
|
||||
|
||||
let mut defined_identifiers = defined_identifiers.clone();
|
||||
self.check_block(body, &mut defined_identifiers)?;
|
||||
self.check_block(orelse, &mut defined_identifiers)?;
|
||||
Ok(false)
|
||||
let (_, errs) = self.check_block(body, &mut defined_identifiers);
|
||||
errors.extend(errs);
|
||||
let (_, errs) = self.check_block(orelse, &mut defined_identifiers);
|
||||
errors.extend(errs);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
StmtKind::With { items, body, .. } => {
|
||||
let mut new_defined_identifiers = defined_identifiers.clone();
|
||||
for item in items.iter() {
|
||||
self.check_expr(&item.context_expr, defined_identifiers)?;
|
||||
if let Err(errs) = self.check_expr(&item.context_expr, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
|
||||
if let Some(var) = item.optional_vars.as_ref() {
|
||||
self.check_pattern(var, &mut new_defined_identifiers)?;
|
||||
if let Err(errs) = self.check_pattern(var, &mut new_defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.check_block(body, &mut new_defined_identifiers)?;
|
||||
Ok(false)
|
||||
|
||||
let (_, errs) = self.check_block(body, &mut new_defined_identifiers);
|
||||
errors.extend(errs);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
StmtKind::Try { body, handlers, orelse, finalbody, .. } => {
|
||||
self.check_block(body, &mut defined_identifiers.clone())?;
|
||||
self.check_block(orelse, &mut defined_identifiers.clone())?;
|
||||
let (_, errs) = self.check_block(body, &mut defined_identifiers.clone());
|
||||
errors.extend(errs);
|
||||
let (_, errs) = self.check_block(orelse, &mut defined_identifiers.clone());
|
||||
errors.extend(errs);
|
||||
|
||||
for handler in handlers.iter() {
|
||||
let mut defined_identifiers = defined_identifiers.clone();
|
||||
let ast::ExcepthandlerKind::ExceptHandler { name, body, .. } = &handler.node;
|
||||
if let Some(name) = name {
|
||||
defined_identifiers.insert(*name);
|
||||
}
|
||||
self.check_block(body, &mut defined_identifiers)?;
|
||||
|
||||
let (_, errs) = self.check_block(body, &mut defined_identifiers);
|
||||
errors.extend(errs);
|
||||
}
|
||||
self.check_block(finalbody, defined_identifiers)?;
|
||||
Ok(false)
|
||||
|
||||
let (_, errs) = self.check_block(finalbody, defined_identifiers);
|
||||
errors.extend(errs);
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
StmtKind::Expr { value, .. } => {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
Ok(false)
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
for target in targets {
|
||||
self.check_pattern(target, defined_identifiers)?;
|
||||
if let Err(e) = self.check_expr(value, defined_identifiers) {
|
||||
errors.extend(e);
|
||||
}
|
||||
Ok(false)
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if let Err(errs) = self.check_expr(value, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(value) {
|
||||
errors.insert(e);
|
||||
}
|
||||
|
||||
for target in targets {
|
||||
if let Err(errs) = self.check_pattern(target, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
StmtKind::AnnAssign { target, value, .. } => {
|
||||
if let Some(value) = value {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
self.check_pattern(target, defined_identifiers)?;
|
||||
if let Err(errs) = self.check_expr(value, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(value) {
|
||||
errors.insert(e);
|
||||
}
|
||||
if let Err(errs) = self.check_pattern(target, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
StmtKind::Return { value, .. } => {
|
||||
if let Some(value) = value {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
if let Err(errs) = self.check_expr(value, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
if let Err(e) = self.should_have_value(value) {
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
StmtKind::Raise { exc, .. } => {
|
||||
if let Some(value) = exc {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
if let Err(errs) = self.check_expr(value, defined_identifiers) {
|
||||
errors.extend(errs);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// break, raise, etc.
|
||||
_ => Ok(false),
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
(stmt_returns, errors)
|
||||
}
|
||||
|
||||
pub fn check_block(
|
||||
&mut self,
|
||||
block: &[Stmt<Option<Type>>],
|
||||
defined_identifiers: &mut HashSet<StrRef>,
|
||||
) -> Result<bool, String> {
|
||||
) -> (bool, HashSet<String>) {
|
||||
let mut errors = HashSet::new();
|
||||
let mut ret = false;
|
||||
for stmt in block {
|
||||
if ret {
|
||||
println!("warning: dead code at {:?}\n", stmt.location)
|
||||
}
|
||||
if self.check_stmt(stmt, defined_identifiers)? {
|
||||
ret = true;
|
||||
}
|
||||
|
||||
let (stmt_rets, stmt_errs) = self.check_stmt(stmt, defined_identifiers);
|
||||
ret |= stmt_rets;
|
||||
errors.extend(stmt_errs);
|
||||
}
|
||||
Ok(ret)
|
||||
|
||||
(ret, errors)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -543,7 +543,8 @@ fn test_basic(source: &str, mapping: HashMap<&str, &str>, virtuals: &[(&str, &st
|
|||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
inferencer.check_block(&statements, &mut defined_identifiers).unwrap();
|
||||
let (_, errs) = inferencer.check_block(&statements, &mut defined_identifiers);
|
||||
assert_eq!(errs.is_empty(), true);
|
||||
|
||||
for (k, v) in inferencer.variable_mapping.iter() {
|
||||
let name = inferencer.unifier.internal_stringify(
|
||||
|
@ -689,7 +690,8 @@ fn test_primitive_magic_methods(source: &str, mapping: HashMap<&str, &str>) {
|
|||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
inferencer.check_block(&statements, &mut defined_identifiers).unwrap();
|
||||
let (_, errs) = inferencer.check_block(&statements, &mut defined_identifiers);
|
||||
assert_eq!(errs.is_empty(), true);
|
||||
|
||||
for (k, v) in inferencer.variable_mapping.iter() {
|
||||
let name = inferencer.unifier.internal_stringify(
|
||||
|
|
Loading…
Reference in New Issue