Refactor to use Diagnostic Class

This commit is contained in:
David Mak 2023-11-15 17:30:26 +08:00
parent 1c071a294c
commit 316c16d5a6
18 changed files with 2051 additions and 979 deletions

2
Cargo.lock generated
View File

@ -641,6 +641,7 @@ name = "nac3artiq"
version = "0.1.0"
dependencies = [
"inkwell",
"itertools 0.11.0",
"nac3core",
"nac3ld",
"nac3parser",
@ -663,6 +664,7 @@ dependencies = [
name = "nac3core"
version = "0.1.0"
dependencies = [
"clap",
"crossbeam",
"indoc",
"inkwell",

View File

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

View File

@ -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.message().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());

View File

@ -1,14 +1,9 @@
use inkwell::{types::BasicType, values::BasicValueEnum, AddressSpace};
use nac3core::{
codegen::{CodeGenContext, CodeGenerator},
symbol_resolver::{StaticValue, SymbolResolver, SymbolValue, ValueEnum},
toplevel::{DefinitionId, TopLevelDef},
typecheck::{
use nac3core::{codegen::{CodeGenContext, CodeGenerator}, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, symbol_resolver::{StaticValue, SymbolResolver, SymbolValue, ValueEnum}, toplevel::{DefinitionId, TopLevelDef}, typecheck::{
type_inferencer::PrimitiveStore,
typedef::{Type, TypeEnum, Unifier},
},
};
use nac3parser::ast::{self, StrRef};
}};
use nac3parser::ast::{self, Location, StrRef};
use parking_lot::{Mutex, RwLock};
use pyo3::{
types::{PyDict, PyTuple},
@ -21,6 +16,7 @@ use std::{
atomic::{AtomicBool, Ordering::Relaxed}
}
};
use nac3core::Diagnostic::DiagErr;
use crate::PrimitivePythonId;
@ -1169,20 +1165,28 @@ impl SymbolResolver for Resolver {
})
}
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
fn get_identifier_def(&self, id: StrRef, id_loc: Option<Location>) -> ResultOrDiagnostics<DefinitionId> {
{
let id_to_def = self.0.id_to_def.read();
id_to_def.get(&id).cloned().ok_or_else(|| "".to_string())
id_to_def.get(&id).cloned()
.map(|res| DiagnosticsResult::ok(res))
.ok_or_else(|| "".to_string())
}
.or_else(|_| {
let py_id =
self.0.name_to_pyid.get(&id).ok_or(format!("Undefined identifier `{}`", id))?;
let result = self.0.pyid_to_def.read().get(py_id).copied().ok_or(format!(
"`{}` is not registered with NAC3 (@nac3 decorator missing?)",
id
))?;
let py_id = self.0.name_to_pyid.get(&id)
.ok_or(DiagnosticEngine::single(DiagErr(
format!("undefined identifier `{id}`"),
id_loc.unwrap()
)))?;
let result = self.0.pyid_to_def.read()
.get(py_id)
.copied()
.ok_or(DiagnosticEngine::single(DiagErr(
format!("`{}` is not registered with NAC3 (@nac3 decorator missing?)", id),
id_loc.unwrap(),
)))?;
self.0.id_to_def.write().insert(id, result);
Ok(result)
Ok(DiagnosticsResult::ok(result))
})
}

View File

@ -10,6 +10,7 @@ crossbeam = "0.8"
parking_lot = "0.12"
rayon = "1.5"
nac3parser = { path = "../nac3parser" }
clap = { version = "4.4.7", features = ["derive"] }
[dependencies.inkwell]
version = "0.2"

View File

@ -1,7 +1,6 @@
use std::{collections::HashMap, convert::TryInto, iter::once, iter::zip};
use crate::{
codegen::{
use crate::{codegen::{
concrete_type::{ConcreteFuncArg, ConcreteTypeEnum, ConcreteTypeStore},
gen_in_range_check,
get_llvm_type,
@ -9,14 +8,10 @@ use crate::{
irrt::*,
stmt::{gen_raise, gen_var},
CodeGenContext, CodeGenTask,
},
symbol_resolver::{SymbolValue, ValueEnum},
toplevel::{DefinitionId, TopLevelDef},
typecheck::{
}, DiagnosticsResult, symbol_resolver::{SymbolValue, ValueEnum}, toplevel::{DefinitionId, TopLevelDef}, typecheck::{
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
magic_methods::{binop_name, binop_assign_name},
},
};
}};
use inkwell::{
AddressSpace,
attributes::{Attribute, AttributeLoc},
@ -1584,10 +1579,16 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
match &func.node {
ExprKind::Name { id, .. } => {
// TODO: handle primitive casts and function pointers
let fun = ctx
// TODO(Derppening): Add fatal error and convert this (and all `gen_*` to fatal)
let DiagnosticsResult { value: fun, engine: diags } = ctx
.resolver
.get_identifier_def(*id)
.map_err(|e| format!("{} (at {})", e, func.location))?;
.get_identifier_def(*id, Some(func.location))
.map_err(|e| e.iter().join("\n"))?;
if !diags.is_empty() {
todo!("Emitting warnings in gen_expr is not supported yet");
}
return Ok(generator
.gen_call(ctx, None, (&signature, fun), params)?
.map(|v| v.into()));

View File

@ -1,17 +1,12 @@
use crate::{
codegen::{
use crate::{codegen::{
concrete_type::ConcreteTypeStore, CodeGenContext, CodeGenLLVMOptions,
CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry,
},
symbol_resolver::{SymbolResolver, ValueEnum},
toplevel::{
}, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, symbol_resolver::{SymbolResolver, ValueEnum}, toplevel::{
composer::TopLevelComposer, DefinitionId, FunInstance, TopLevelContext, TopLevelDef,
},
typecheck::{
}, typecheck::{
type_inferencer::{FunctionData, Inferencer, PrimitiveStore},
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
},
};
}};
use indoc::indoc;
use inkwell::{
targets::{InitializationConfig, Target},
@ -24,6 +19,8 @@ use nac3parser::{
use parking_lot::RwLock;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use nac3parser::ast::Location;
use crate::Diagnostic::DiagErr;
struct Resolver {
id_to_type: HashMap<StrRef, Type>,
@ -63,12 +60,13 @@ impl SymbolResolver for Resolver {
unimplemented!()
}
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
fn get_identifier_def(&self, id: StrRef, loc: Option<Location>) -> ResultOrDiagnostics<DefinitionId> {
self.id_to_def
.read()
.get(&id)
.cloned()
.ok_or_else(|| format!("cannot find symbol `{}`", id))
.map(|res| DiagnosticsResult::ok(res))
.ok_or_else(|| DiagnosticEngine::single(DiagErr(format!("cannot find symbol `{id}`"), loc.unwrap())))
}
fn get_string_id(&self, _: &str) -> i32 {
@ -144,7 +142,9 @@ fn test_primitives() {
.collect::<Result<Vec<_>, _>>()
.unwrap();
inferencer.check_block(&statements, &mut identifiers).unwrap();
let DiagnosticsResult { engine: 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 +346,9 @@ fn test_simple_call() {
unreachable!()
}
inferencer.check_block(&statements_1, &mut identifiers).unwrap();
let DiagnosticsResult { engine: 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)])),

View File

@ -7,7 +7,9 @@ use std::collections::HashSet;
use self::Diagnostic::*;
use std::fmt;
use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};
use itertools::Itertools;
use nac3parser::ast::Location;
pub mod codegen;
@ -39,7 +41,7 @@ impl Diagnostic {
}
/// Returns the message of this [Diagnostic].
fn message(&self) -> &str {
pub fn message(&self) -> &str {
match self {
DiagErr(msg, _) => msg,
DiagWarn(msg, _) => msg,
@ -47,7 +49,7 @@ impl Diagnostic {
}
/// Returns the location of where this [Diagnostic] is created from.
fn location(&self) -> &Location {
pub fn location(&self) -> &Location {
match self {
DiagErr(_, loc) => loc,
DiagWarn(_, loc) => loc,
@ -121,37 +123,52 @@ impl Hash for Diagnostic {
}
}
#[deprecated]
pub fn contains_errors(diagnostics: &HashSet<Diagnostic>) -> bool {
diagnostics.iter().any(|d| match d {
DiagErr(_, _) => true,
_ => false,
})
}
/// Type alias for [Result] encapsulating a success value of type [T], and any diagnostics emitted
/// during the generation of the result.
///
/// This type should be used where a result cannot be meaningfully generated when a subset of the
/// possible diagnostics is emitted.
pub type DiagnosticsResult<T> = Result<(T, DiagnosticEngine), DiagnosticEngine>;
pub type ResultOrDiagnostics<T> = Result<DiagnosticsResult<T>, DiagnosticEngine>;
/// Type representing a result of type `T` with any diagnostics emitted during the generation of the
/// result.
///
/// This type should be used where, even when diagnostics are generated, a meaningful result can
/// still be generated.
pub struct DiagnosticsPartialResult<T> {
#[derive(Clone)]
pub struct DiagnosticsResult<T> {
value: T,
engine: DiagnosticEngine,
}
impl <T> Into<DiagnosticsResult<T>> for DiagnosticsPartialResult<T> {
fn into(self) -> DiagnosticsResult<T> {
impl <T> DiagnosticsResult<T> {
pub fn ok(value: T) -> DiagnosticsResult<T> {
DiagnosticsResult {
value,
engine: DiagnosticEngine::default(),
}
}
pub fn err(value: T, engine: DiagnosticEngine) -> DiagnosticsResult<T> {
DiagnosticsResult {
value,
engine
}
}
}
impl <T> Into<(T, DiagnosticEngine)> for DiagnosticsResult<T> {
fn into(self) -> (T, DiagnosticEngine) {
(self.value, self.engine)
}
}
impl <T> Into<ResultOrDiagnostics<T>> for DiagnosticsResult<T> {
fn into(self) -> ResultOrDiagnostics<T> {
if self.engine.has_errors() {
Err(self.engine)
} else {
Ok((self.value, self.engine))
Ok(self)
}
}
}
@ -200,16 +217,16 @@ impl DiagnosticEngine {
self.extend(other.diagnostics)
}
pub fn consume_partial_result<T>(&mut self, result: DiagnosticsPartialResult<T>) -> T {
pub fn consume_partial_result<T>(&mut self, result: DiagnosticsResult<T>) -> T {
self.merge_with(result.engine);
result.value
}
pub fn consume_result<T>(&mut self, result: DiagnosticsResult<T>) -> Result<T, Self> {
pub fn consume_result<T>(&mut self, result: ResultOrDiagnostics<T>) -> Result<T, Self> {
match result {
Ok((v, diags)) => {
self.merge_with(diags);
Ok(v)
Ok(DiagnosticsResult { value, engine }) => {
self.merge_with(engine);
Ok(value)
}
Err(diags) => {
@ -219,11 +236,11 @@ impl DiagnosticEngine {
}
}
pub fn consume_result_to_option<T>(&mut self, result: DiagnosticsResult<T>) -> Option<T> {
pub fn consume_result_to_option<T>(&mut self, result: ResultOrDiagnostics<T>) -> Option<T> {
match result {
Ok((v, diags)) => {
self.merge_with(diags);
Some(v)
Ok(DiagnosticsResult { value, engine }) => {
self.merge_with(engine);
Some(value)
}
Err(diags) => {
@ -241,18 +258,36 @@ impl DiagnosticEngine {
self.diagnostics.into_iter()
}
pub fn into_partial_result<T>(self, value: T) -> DiagnosticsPartialResult<T> {
DiagnosticsPartialResult {
pub fn into_partial_result<T>(self, value: T) -> DiagnosticsResult<T> {
DiagnosticsResult {
value,
engine: self
}
}
pub fn into_result<T>(self, value: T) -> DiagnosticsResult<T> {
pub fn into_result<T>(self, value: T) -> ResultOrDiagnostics<T> {
self.into_result_with(|| value)
}
pub fn into_result_with<T, F>(self, value: F) -> ResultOrDiagnostics<T>
where F: FnOnce() -> T,
{
if self.has_errors() {
Err(self)
} else {
Ok((value, self))
Ok(DiagnosticsResult { value: value(), engine: self })
}
}
pub fn as_string(&self) -> Vec<String> {
self.diagnostics.iter()
.map(|diag| diag.to_string())
.collect_vec()
}
}
impl Debug for DiagnosticEngine {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_string().join("\n----------\n"))
}
}

View File

@ -3,10 +3,7 @@ use std::sync::Arc;
use std::{collections::HashMap, fmt::Display};
use crate::typecheck::typedef::TypeEnum;
use crate::{
codegen::CodeGenContext,
toplevel::{DefinitionId, TopLevelDef},
};
use crate::{codegen::CodeGenContext, Diagnostic::DiagErr, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, toplevel::{DefinitionId, TopLevelDef}};
use crate::{
codegen::CodeGenerator,
typecheck::{
@ -147,8 +144,14 @@ pub trait SymbolResolver {
str: StrRef,
) -> Result<Type, String>;
// get the top-level definition of identifiers
fn get_identifier_def(&self, str: StrRef) -> Result<DefinitionId, String>;
/// Returns the top-level definition of identifiers.
///
/// * `id_src`: The location where the identifier is used in.
fn get_identifier_def(
&self,
str: StrRef,
id_src: Option<Location>
) -> ResultOrDiagnostics<DefinitionId>;
fn get_symbol_value<'ctx, 'a>(
&self,
@ -186,15 +189,19 @@ thread_local! {
];
}
// convert type annotation into type
/// Converts type annotation into [Type].
///
/// Returns [Type] of this `expr` if the operation is successful, and a [HashSet] containing all
/// emitted [Diagnostic] messages.
pub fn parse_type_annotation<T>(
resolver: &dyn SymbolResolver,
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
unifier: &mut Unifier,
primitives: &PrimitiveStore,
expr: &Expr<T>,
) -> Result<Type, String> {
) -> ResultOrDiagnostics<Type> {
use nac3parser::ast::ExprKind::*;
let ids = IDENTIFIER_ID.with(|ids| *ids);
let int32_id = ids[0];
let int64_id = ids[1];
@ -209,56 +216,87 @@ pub fn parse_type_annotation<T>(
let uint64_id = ids[10];
let name_handling = |id: &StrRef, loc: Location, unifier: &mut Unifier| {
let mut diagnostics = DiagnosticEngine::new();
if *id == int32_id {
Ok(primitives.int32)
Ok(DiagnosticsResult::ok(primitives.int32))
} else if *id == int64_id {
Ok(primitives.int64)
Ok(DiagnosticsResult::ok(primitives.int64))
} else if *id == uint32_id {
Ok(primitives.uint32)
Ok(DiagnosticsResult::ok(primitives.uint32))
} else if *id == uint64_id {
Ok(primitives.uint64)
Ok(DiagnosticsResult::ok(primitives.uint64))
} else if *id == float_id {
Ok(primitives.float)
Ok(DiagnosticsResult::ok(primitives.float))
} else if *id == bool_id {
Ok(primitives.bool)
Ok(DiagnosticsResult::ok(primitives.bool))
} else if *id == str_id {
Ok(primitives.str)
Ok(DiagnosticsResult::ok(primitives.str))
} else if *id == exn_id {
Ok(primitives.exception)
Ok(DiagnosticsResult::ok(primitives.exception))
} else {
let obj_id = resolver.get_identifier_def(*id);
let obj_id = resolver.get_identifier_def(*id, Some(expr.location));
match obj_id {
Ok(obj_id) => {
Ok(DiagnosticsResult { value: obj_id, engine: diags }) => {
diagnostics.merge_with(diags);
let def = top_level_defs[obj_id.0].read();
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
if !type_vars.is_empty() {
return Err(format!(
"Unexpected number of type parameters: expected {} but got 0",
type_vars.len()
diagnostics.insert(DiagErr(
format!("expected {} type parameters but got 0", type_vars.len()),
loc,
));
return Err(diagnostics)
}
let fields = chain(
fields.iter().map(|(k, v, m)| (*k, (*v, *m))),
methods.iter().map(|(k, v, _)| (*k, (*v, false))),
)
.collect();
Ok(unifier.add_ty(TypeEnum::TObj {
Ok(DiagnosticsResult::err(
unifier.add_ty(TypeEnum::TObj {
obj_id,
fields,
params: Default::default(),
}))
}),
diagnostics,
))
} else {
Err(format!("Cannot use function name as type at {}", loc))
diagnostics.insert(DiagErr(
"cannot use function name as type".into(),
loc,
));
Err(diagnostics)
}
}
Err(_) => {
let ty = resolver
.get_symbol_type(unifier, top_level_defs, primitives, *id)
.map_err(|e| format!("Unknown type annotation at {}: {}", loc, e))?;
let ty = match resolver.get_symbol_type(
unifier,
top_level_defs,
primitives,
*id,
) {
Ok(ty) => ty,
Err(e) => {
diagnostics.insert(DiagErr(
format!("Unknown type annotation {e}"),
loc,
));
return Err(diagnostics)
}
};
if let TypeEnum::TVar { .. } = &*unifier.get_ty(ty) {
Ok(ty)
Ok(DiagnosticsResult::err(ty, diagnostics))
} else {
Err(format!("Unknown type annotation {} at {}", id, loc))
diagnostics.insert(DiagErr(
format!("unknown type annotation {}", id),
loc,
));
Err(diagnostics)
}
}
}
@ -266,23 +304,37 @@ pub fn parse_type_annotation<T>(
};
let subscript_name_handle = |id: &StrRef, slice: &Expr<T>, unifier: &mut Unifier| {
let mut diagnostics = DiagnosticEngine::new();
if *id == virtual_id {
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
Ok(unifier.add_ty(TypeEnum::TVirtual { ty }))
let DiagnosticsResult { value: ty, engine: diag } =
parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
diagnostics.merge_with(diag);
Ok(DiagnosticsResult::err(unifier.add_ty(TypeEnum::TVirtual { ty }), diagnostics))
} else if *id == list_id {
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
Ok(unifier.add_ty(TypeEnum::TList { ty }))
let DiagnosticsResult { value: ty, engine: diag } =
parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
diagnostics.merge_with(diag);
Ok(DiagnosticsResult::err(unifier.add_ty(TypeEnum::TList { ty }), diagnostics))
} else if *id == tuple_id {
if let Tuple { elts, .. } = &slice.node {
let ty = elts
let (ty, diags) = elts
.iter()
.map(|elt| {
parse_type_annotation(resolver, top_level_defs, unifier, primitives, elt)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(unifier.add_ty(TypeEnum::TTuple { ty }))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(|res| res.into())
.unzip::<_, _, Vec<_>, Vec<_>>();
diagnostics.extend(diags.into_iter().flat_map(|d| d.into_iter()));
Ok(DiagnosticsResult::err(unifier.add_ty(TypeEnum::TTuple { ty }), diagnostics))
} else {
Err("Expected multiple elements for tuple".into())
diagnostics.insert(DiagErr(
"expected multiple elements for tuple".into(),
slice.location,
));
Err(diagnostics)
}
} else {
let types = if let Tuple { elts, .. } = &slice.node {
@ -295,25 +347,38 @@ pub fn parse_type_annotation<T>(
vec![parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?]
};
let obj_id = resolver.get_identifier_def(*id)?;
let DiagnosticsResult { value: obj_id, engine: diags } = resolver.get_identifier_def(*id, Some(slice.location))?;
diagnostics.merge_with(diags);
let def = top_level_defs[obj_id.0].read();
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
if types.len() != type_vars.len() {
return Err(format!(
"Unexpected number of type parameters: expected {} but got {}",
type_vars.len(),
types.len()
diagnostics.insert(DiagErr(
format!("expected {} type parameters but got {}", type_vars.len(), types.len()),
slice.location,
));
return Err(diagnostics)
}
let mut subst = HashMap::new();
for (var, ty) in izip!(type_vars.iter(), types.iter()) {
let mut subst_diags = DiagnosticEngine::new();
for (var, DiagnosticsResult { value: ty, engine: diag }) in izip!(type_vars.iter(), types.iter()) {
let id = if let TypeEnum::TVar { id, .. } = &*unifier.get_ty(*var) {
*id
} else {
unreachable!()
};
subst.insert(id, *ty);
subst_diags.extend(diag.iter().cloned());
}
let subst_has_errors = subst_diags.has_errors();
diagnostics.merge_with(subst_diags);
if subst_has_errors {
return Err(diagnostics)
}
let mut fields = fields
.iter()
.map(|(attr, ty, is_mutable)| {
@ -325,9 +390,14 @@ pub fn parse_type_annotation<T>(
let ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
(*attr, (ty, false))
}));
Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: subst }))
Ok(DiagnosticsResult::err(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: subst }), diagnostics))
} else {
Err("Cannot use function name as type".into())
diagnostics.insert(DiagErr(
"cannot use function name as type".into(),
slice.location,
));
Err(diagnostics)
}
}
};
@ -338,10 +408,17 @@ pub fn parse_type_annotation<T>(
if let Name { id, .. } = &value.node {
subscript_name_handle(id, slice, unifier)
} else {
Err(format!("unsupported type expression at {}", expr.location))
Err(DiagnosticEngine::single(DiagErr(
"unsupported type expression".into(),
expr.location,
)))
}
}
_ => Err(format!("unsupported type expression at {}", expr.location)),
_ => Err(DiagnosticEngine::single(DiagErr(
"unsupported type expression".into(),
expr.location,
))),
}
}
@ -352,7 +429,7 @@ impl dyn SymbolResolver + Send + Sync {
unifier: &mut Unifier,
primitives: &PrimitiveStore,
expr: &Expr<T>,
) -> Result<Type, String> {
) -> ResultOrDiagnostics<Type> {
parse_type_annotation(self, top_level_defs, unifier, primitives, expr)
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,8 @@ use std::convert::TryInto;
use crate::symbol_resolver::SymbolValue;
use nac3parser::ast::{Constant, Location};
use crate::Diagnostic::DiagErr;
use crate::{Diagnostic, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics};
use super::*;
@ -200,13 +202,17 @@ impl TopLevelComposer {
pub fn get_class_method_def_info(
class_methods_def: &[(StrRef, Type, DefinitionId)],
method_name: StrRef,
) -> Result<(Type, DefinitionId), String> {
loc: Location,
) -> ResultOrDiagnostics<(Type, DefinitionId)> {
for (name, ty, def_id) in class_methods_def {
if name == &method_name {
return Ok((*ty, *def_id));
return Ok(DiagnosticsResult::ok((*ty, *def_id)));
}
}
Err(format!("no method {} in the current class", method_name))
Err(DiagnosticEngine::single(
DiagErr(format!("no method {method_name} in the current class"), loc)
))
}
/// get all base class def id of a class, excluding itself. \
@ -217,7 +223,10 @@ impl TopLevelComposer {
pub fn get_all_ancestors_helper(
child: &TypeAnnotation,
temp_def_list: &[Arc<RwLock<TopLevelDef>>],
) -> Result<Vec<TypeAnnotation>, String> {
unifier: &mut Unifier,
loc: &Option<Location>,
) -> ResultOrDiagnostics<Vec<TypeAnnotation>> {
let mut diagnostics = DiagnosticEngine::new();
let mut result: Vec<TypeAnnotation> = Vec::new();
let mut parent = Self::get_parent(child, temp_def_list);
while let Some(p) = parent {
@ -238,10 +247,25 @@ impl TopLevelComposer {
if no_cycle {
result.push(p);
} else {
return Err("cyclic inheritance detected".into());
let child_name = child.stringify(unifier);
let parent_name = p.stringify(unifier);
diagnostics.insert(DiagErr(
format!(
"cyclic inheritance detected - child class `{}` is an ancestor of class `{}`",
child_name,
parent_name,
),
loc.expect(format!(
"Unexpected error: Cyclic Inheriance detected in intrinsic class! Child Class `{}` is an ancestor of class `{}`",
child_name,
parent_name,
).as_str())
));
}
}
Ok(result)
diagnostics.into_result(result)
}
/// should only be called when finding all ancestors, so panic when wrong
@ -334,8 +358,10 @@ impl TopLevelComposer {
)
}
pub fn get_all_assigned_field(stmts: &[ast::Stmt<()>]) -> Result<HashSet<StrRef>, String> {
pub fn get_all_assigned_field(stmts: &[ast::Stmt<()>]) -> ResultOrDiagnostics<HashSet<StrRef>> {
let mut result = HashSet::new();
let mut diagnostics = DiagnosticEngine::new();
for s in stmts {
match &s.node {
ast::StmtKind::AnnAssign { target, .. }
@ -351,10 +377,11 @@ impl TopLevelComposer {
}
} =>
{
return Err(format!(
"redundant type annotation for class fields at {}",
s.location
))
diagnostics.insert(DiagErr(
"redundant type annotation for class fields".into(),
s.location,
));
return Err(diagnostics)
}
ast::StmtKind::Assign { targets, .. } => {
for t in targets {
@ -370,26 +397,26 @@ impl TopLevelComposer {
// TODO: do not check for For and While?
ast::StmtKind::For { body, orelse, .. }
| ast::StmtKind::While { body, orelse, .. } => {
result.extend(Self::get_all_assigned_field(body.as_slice())?);
result.extend(Self::get_all_assigned_field(orelse.as_slice())?);
result.extend(diagnostics.consume_result(Self::get_all_assigned_field(body.as_slice()))?);
result.extend(diagnostics.consume_result(Self::get_all_assigned_field(orelse.as_slice()))?);
}
ast::StmtKind::If { body, orelse, .. } => {
let inited_for_sure = Self::get_all_assigned_field(body.as_slice())?
.intersection(&Self::get_all_assigned_field(orelse.as_slice())?)
let inited_for_sure = diagnostics.consume_result(Self::get_all_assigned_field(body.as_slice()))?
.intersection(&diagnostics.consume_result(Self::get_all_assigned_field(orelse.as_slice()))?)
.cloned()
.collect::<HashSet<_>>();
result.extend(inited_for_sure);
}
ast::StmtKind::Try { body, orelse, finalbody, .. } => {
let inited_for_sure = Self::get_all_assigned_field(body.as_slice())?
.intersection(&Self::get_all_assigned_field(orelse.as_slice())?)
let inited_for_sure = diagnostics.consume_result(Self::get_all_assigned_field(body.as_slice()))?
.intersection(&diagnostics.consume_result(Self::get_all_assigned_field(orelse.as_slice()))?)
.cloned()
.collect::<HashSet<_>>();
result.extend(inited_for_sure);
result.extend(Self::get_all_assigned_field(finalbody.as_slice())?);
result.extend(diagnostics.consume_result(Self::get_all_assigned_field(finalbody.as_slice()))?);
}
ast::StmtKind::With { body, .. } => {
result.extend(Self::get_all_assigned_field(body.as_slice())?);
result.extend(diagnostics.consume_result(Self::get_all_assigned_field(body.as_slice()))?);
}
ast::StmtKind::Pass { .. } => {}
ast::StmtKind::Assert { .. } => {}
@ -400,13 +427,14 @@ impl TopLevelComposer {
}
}
}
Ok(result)
Ok(DiagnosticsResult::err(result, diagnostics))
}
pub fn parse_parameter_default_value(
default: &ast::Expr,
resolver: &(dyn SymbolResolver + Send + Sync),
) -> Result<SymbolValue, String> {
) -> ResultOrDiagnostics<SymbolValue> {
parse_parameter_default_value(default, resolver)
}
@ -497,25 +525,35 @@ impl TopLevelComposer {
pub fn parse_parameter_default_value(
default: &ast::Expr,
resolver: &(dyn SymbolResolver + Send + Sync),
) -> Result<SymbolValue, String> {
fn handle_constant(val: &Constant, loc: &Location) -> Result<SymbolValue, String> {
) -> ResultOrDiagnostics<SymbolValue> {
fn handle_constant(val: &Constant, loc: &Location) -> ResultOrDiagnostics<SymbolValue> {
match val {
Constant::Int(v) => {
if let Ok(v) = (*v).try_into() {
Ok(SymbolValue::I32(v))
Ok(DiagnosticsResult::ok(SymbolValue::I32(v)))
} else {
Err(format!("integer value out of range at {}", loc))
Err(DiagnosticEngine::single(DiagErr(
"integer value out of range".into(),
*loc,
)))
}
}
Constant::Float(v) => Ok(SymbolValue::Double(*v)),
Constant::Bool(v) => Ok(SymbolValue::Bool(*v)),
Constant::Tuple(tuple) => Ok(SymbolValue::Tuple(
tuple.iter().map(|x| handle_constant(x, loc)).collect::<Result<Vec<_>, _>>()?,
)),
Constant::None => Err(format!(
"`None` is not supported, use `none` for option type instead ({})",
loc
)),
Constant::Float(v) => Ok(DiagnosticsResult::ok(SymbolValue::Double(*v))),
Constant::Bool(v) => Ok(DiagnosticsResult::ok(SymbolValue::Bool(*v))),
Constant::Tuple(tuple) => {
let val = tuple.iter()
.map(|x| handle_constant(x, loc))
.collect::<Result<Vec<_>, _>>()?;
let diagnostics = DiagnosticEngine::from(val.iter().cloned()
.flat_map(|res| res.engine.into_iter())
.collect());
diagnostics.into_result_with(|| SymbolValue::Tuple(val.into_iter().map(|res| res.value).collect_vec()))
}
Constant::None => Err(DiagnosticEngine::single(DiagErr(
"`None` is not supported, use `none` for option type instead".into(),
*loc,
))),
_ => unimplemented!("this constant is not supported at {}", loc),
}
}
@ -527,59 +565,101 @@ pub fn parse_parameter_default_value(
ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
let v: Result<i64, _> = (*v).try_into();
match v {
Ok(v) => Ok(SymbolValue::I64(v)),
_ => Err(format!("default param value out of range at {}", default.location)),
Ok(v) => Ok(DiagnosticsResult::ok(SymbolValue::I64(v))),
_ => Err(DiagnosticEngine::single(DiagErr(
"default param value out of range".into(),
default.location,
))),
}
}
_ => Err(format!("only allow constant integer here at {}", default.location))
_ => Err(DiagnosticEngine::single(DiagErr(
"only allow constant integer here".into(),
default.location
)))
}
ast::ExprKind::Name { id, .. } if *id == "uint32".into() => match &args[0].node {
ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
let v: Result<u32, _> = (*v).try_into();
match v {
Ok(v) => Ok(SymbolValue::U32(v)),
_ => Err(format!("default param value out of range at {}", default.location)),
Ok(v) => Ok(DiagnosticsResult::ok(SymbolValue::U32(v))),
_ => Err(DiagnosticEngine::single(DiagErr(
"default param value out of range".into(),
default.location,
))),
}
}
_ => Err(format!("only allow constant integer here at {}", default.location))
_ => Err(DiagnosticEngine::single(DiagErr(
"only allow constant integer here".into(),
default.location,
))),
}
ast::ExprKind::Name { id, .. } if *id == "uint64".into() => match &args[0].node {
ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
let v: Result<u64, _> = (*v).try_into();
match v {
Ok(v) => Ok(SymbolValue::U64(v)),
_ => Err(format!("default param value out of range at {}", default.location)),
Ok(v) => Ok(DiagnosticsResult::ok(SymbolValue::U64(v))),
_ => Err(DiagnosticEngine::single(DiagErr(
"default param value out of range".into(),
default.location
))),
}
}
_ => Err(format!("only allow constant integer here at {}", default.location))
_ => Err(DiagnosticEngine::single(DiagErr(
"only allow constant integer here".into(),
default.location
)))
}
ast::ExprKind::Name { id, .. } if *id == "Some".into() => Ok(
SymbolValue::OptionSome(
Box::new(parse_parameter_default_value(&args[0], resolver)?)
ast::ExprKind::Name { id, .. } if *id == "Some".into() => {
let param_def_value = parse_parameter_default_value(&args[0], resolver)?;
let errs = param_def_value.engine;
Ok(
DiagnosticsResult::err(SymbolValue::OptionSome(
Box::new(param_def_value.value),
), errs)
)
),
_ => Err(format!("unsupported default parameter at {}", default.location)),
}
_ => Err(DiagnosticEngine::single(DiagErr(
"unsupported default parameter".into(),
default.location,
))),
}
}
ast::ExprKind::Tuple { elts, .. } => Ok(SymbolValue::Tuple(elts
ast::ExprKind::Tuple { elts, .. } => {
let param_def_values = elts
.iter()
.map(|x| parse_parameter_default_value(x, resolver))
.collect::<Result<Vec<_>, _>>()?
)),
ast::ExprKind::Name { id, .. } if id == &"none".into() => Ok(SymbolValue::OptionNone),
ast::ExprKind::Name { id, .. } => {
resolver.get_default_param_value(default).ok_or_else(
|| format!(
"`{}` cannot be used as a default parameter at {} \
(not primitive type, option or tuple / not defined?)",
id,
default.location
)
)
}
_ => Err(format!(
"unsupported default parameter (not primitive type, option or tuple) at {}",
default.location
.collect::<Result<Vec<_>, _>>()?;
let diagnostics = DiagnosticEngine::from(
param_def_values.iter().cloned()
.flat_map(|res| res.engine.into_iter())
.collect::<HashSet<Diagnostic>>()
);
let param_def_values = param_def_values.into_iter()
.map(|res| res.value)
.collect_vec();
Ok(DiagnosticsResult::err(
SymbolValue::Tuple(param_def_values),
diagnostics,
))
}
ast::ExprKind::Name { id, .. } if id == &"none".into() => Ok(DiagnosticsResult::ok(SymbolValue::OptionNone)),
ast::ExprKind::Name { id, .. } => {
resolver.get_default_param_value(default)
.map(|res| DiagnosticsResult::ok(res))
.ok_or_else(||
DiagnosticEngine::single(DiagErr(
format!("`{}` cannot be used as a default parameter (not primitive type, \
option or tuple / not defined?)", id),
default.location,
))
)
}
_ => Err(DiagnosticEngine::single(DiagErr(
"unsupported default parameter (not primitive type, option or tuple)".into(),
default.location,
)))
}
}

View File

@ -1,17 +1,13 @@
use crate::{
codegen::CodeGenContext,
symbol_resolver::{SymbolResolver, ValueEnum},
toplevel::DefinitionId,
typecheck::{
use crate::{codegen::CodeGenContext, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, symbol_resolver::{SymbolResolver, ValueEnum}, toplevel::DefinitionId, typecheck::{
type_inferencer::PrimitiveStore,
typedef::{Type, Unifier},
},
};
}};
use indoc::indoc;
use nac3parser::{ast::fold::Fold, parser::parse_program};
use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc};
use test_case::test_case;
use crate::Diagnostic::DiagErr;
use super::*;
@ -64,8 +60,10 @@ impl SymbolResolver for Resolver {
unimplemented!()
}
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
self.0.id_to_def.lock().get(&id).cloned().ok_or_else(|| "Unknown identifier".to_string())
fn get_identifier_def(&self, id: StrRef, loc: Option<Location>) -> ResultOrDiagnostics<DefinitionId> {
self.0.id_to_def.lock().get(&id).cloned()
.map(|res| DiagnosticsResult::ok(res))
.ok_or_else(|| DiagnosticEngine::single(DiagErr("undefined identifier".into(), loc.unwrap())))
}
fn get_string_id(&self, _: &str) -> i32 {
@ -551,9 +549,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().message());
}
} else {
// skip 5 to skip primitives
@ -735,9 +733,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().message());
}
} else {
// skip 5 to skip primitives

View File

@ -1,3 +1,4 @@
use crate::{Diagnostic::DiagErr, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics};
use super::*;
#[derive(Clone, Debug)]
@ -64,74 +65,103 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
expr: &ast::Expr<T>,
// the key stores the type_var of this topleveldef::class, we only need this field here
locked: HashMap<DefinitionId, Vec<Type>>,
) -> Result<TypeAnnotation, String> {
) -> ResultOrDiagnostics<TypeAnnotation> {
let mut diagnostics = DiagnosticEngine::new();
let name_handle = |id: &StrRef,
id_loc: Location,
unifier: &mut Unifier,
locked: HashMap<DefinitionId, Vec<Type>>| {
let mut diagnostics = DiagnosticEngine::new();
if id == &"int32".into() {
Ok(TypeAnnotation::Primitive(primitives.int32))
Ok(DiagnosticsResult::ok(TypeAnnotation::Primitive(primitives.int32)))
} else if id == &"int64".into() {
Ok(TypeAnnotation::Primitive(primitives.int64))
Ok(DiagnosticsResult::ok(TypeAnnotation::Primitive(primitives.int64)))
} else if id == &"uint32".into() {
Ok(TypeAnnotation::Primitive(primitives.uint32))
Ok(DiagnosticsResult::ok(TypeAnnotation::Primitive(primitives.uint32)))
} else if id == &"uint64".into() {
Ok(TypeAnnotation::Primitive(primitives.uint64))
Ok(DiagnosticsResult::ok(TypeAnnotation::Primitive(primitives.uint64)))
} else if id == &"float".into() {
Ok(TypeAnnotation::Primitive(primitives.float))
Ok(DiagnosticsResult::ok(TypeAnnotation::Primitive(primitives.float)))
} else if id == &"bool".into() {
Ok(TypeAnnotation::Primitive(primitives.bool))
Ok(DiagnosticsResult::ok(TypeAnnotation::Primitive(primitives.bool)))
} else if id == &"str".into() {
Ok(TypeAnnotation::Primitive(primitives.str))
Ok(DiagnosticsResult::ok(TypeAnnotation::Primitive(primitives.str)))
} else if id == &"Exception".into() {
Ok(TypeAnnotation::CustomClass { id: DefinitionId(7), params: Default::default() })
} else if let Ok(obj_id) = resolver.get_identifier_def(*id) {
Ok(DiagnosticsResult::ok(TypeAnnotation::CustomClass { id: DefinitionId(7), params: Default::default() }))
} else if let Ok(DiagnosticsResult { value: obj_id, engine: diags }) = resolver.get_identifier_def(*id, Some(id_loc)) {
diagnostics.merge_with(diags);
let type_vars = {
let def_read = top_level_defs[obj_id.0].try_read();
if let Some(def_read) = def_read {
if let TopLevelDef::Class { type_vars, .. } = &*def_read {
type_vars.clone()
} else {
return Err(format!(
"function cannot be used as a type (at {})",
expr.location
diagnostics.insert(DiagErr(
"function cannot be used as a type".into(),
expr.location,
));
return Err(diagnostics)
}
} else {
locked.get(&obj_id).unwrap().clone()
}
};
// check param number here
if !type_vars.is_empty() {
return Err(format!(
"expect {} type variable parameter but got 0 (at {})",
type_vars.len(),
diagnostics.insert(DiagErr(
format!("expect {} type variable parameters but got 0", type_vars.len()),
expr.location,
));
return Err(diagnostics)
}
Ok(TypeAnnotation::CustomClass { id: obj_id, params: vec![] })
Ok(DiagnosticsResult::err(TypeAnnotation::CustomClass { id: obj_id, params: vec![] }, diagnostics))
} else if let Ok(ty) = resolver.get_symbol_type(unifier, top_level_defs, primitives, *id) {
if let TypeEnum::TVar { .. } = unifier.get_ty(ty).as_ref() {
let var = unifier.get_fresh_var(Some(*id), Some(expr.location)).0;
unifier.unify(var, ty).unwrap();
Ok(TypeAnnotation::TypeVar(ty))
Ok(DiagnosticsResult::err(TypeAnnotation::TypeVar(ty), diagnostics))
} else {
Err(format!("`{}` is not a valid type annotation (at {})", id, expr.location))
diagnostics.insert(DiagErr(
format!("`{}` is not a valid type annotation", id),
expr.location,
));
Err(diagnostics)
}
} else {
Err(format!("`{}` is not a valid type annotation (at {})", id, expr.location))
diagnostics.insert(DiagErr(
format!("`{}` is not a valid type annotation", id),
expr.location,
));
Err(diagnostics)
}
};
let class_name_handle =
|id: &StrRef,
id_loc: Location,
slice: &ast::Expr<T>,
unifier: &mut Unifier,
mut locked: HashMap<DefinitionId, Vec<Type>>| {
let mut diagnostics = DiagnosticEngine::new();
if vec!["virtual".into(), "Generic".into(), "list".into(), "tuple".into()].contains(id)
{
return Err(format!("keywords cannot be class name (at {})", expr.location));
diagnostics.insert(DiagErr(
"keywords cannot be class name".into(),
expr.location,
));
return Err(diagnostics)
}
let obj_id = resolver.get_identifier_def(*id)?;
let DiagnosticsResult { value: obj_id, engine: diags } =
resolver.get_identifier_def(*id, Some(id_loc))?;
diagnostics.merge_with(diags);
let type_vars = {
let def_read = top_level_defs[obj_id.0].try_read();
if let Some(def_read) = def_read {
@ -144,6 +174,7 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
locked.get(&obj_id).unwrap().clone()
}
};
// we do not check whether the application of type variables are compatible here
let param_type_infos = {
let params_ast = if let ast::ExprKind::Tuple { elts, .. } = &slice.node {
@ -151,14 +182,18 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
} else {
vec![slice]
};
if type_vars.len() != params_ast.len() {
return Err(format!(
"expect {} type parameters but got {} (at {})",
diagnostics.insert(DiagErr(
format!(
"expect {} type parameters but got {}",
type_vars.len(),
params_ast.len(),
),
params_ast[0].location,
));
}
let result = params_ast
.iter()
.map(|x| {
@ -174,31 +209,47 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
},
)
})
.collect::<Result<Vec<_>, _>>()?;
.collect_vec();
diagnostics.extend(result.iter().cloned().flat_map(|res| match res {
Ok(DiagnosticsResult { engine: diags, .. }) => diags,
Err(diags) => diags,
}.into_iter()));
if diagnostics.has_errors() {
return Err(diagnostics)
}
let result = result.into_iter()
.map(|res| res.ok().unwrap().value)
.collect_vec();
// make sure the result do not contain any type vars
let no_type_var =
result.iter().all(|x| get_type_var_contained_in_type_annotation(x).is_empty());
let no_type_var = result.iter()
.all(|x| get_type_var_contained_in_type_annotation(x).is_empty());
if no_type_var {
result
} else {
return Err(format!(
"application of type vars to generic class \
is not currently supported (at {})",
params_ast[0].location
diagnostics.insert(DiagErr(
"application of type vars to generic class is not currently supported".into(),
params_ast[0].location,
));
return Err(diagnostics)
}
};
Ok(TypeAnnotation::CustomClass { id: obj_id, params: param_type_infos })
diagnostics.into_result(TypeAnnotation::CustomClass { id: obj_id, params: param_type_infos })
};
match &expr.node {
ast::ExprKind::Name { id, .. } => name_handle(id, unifier, locked),
ast::ExprKind::Name { id, .. } => name_handle(id, expr.location, unifier, locked),
// virtual
ast::ExprKind::Subscript { value, slice, .. }
if {
matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"virtual".into())
} =>
{
let def = parse_ast_to_type_annotation_kinds(
let DiagnosticsResult { value: def, engine: diags } = parse_ast_to_type_annotation_kinds(
resolver,
top_level_defs,
unifier,
@ -206,10 +257,13 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
slice.as_ref(),
locked,
)?;
diagnostics.merge_with(diags);
if !matches!(def, TypeAnnotation::CustomClass { .. }) {
unreachable!("must be concretized custom class kind in the virtual")
}
Ok(TypeAnnotation::Virtual(def.into()))
Ok(DiagnosticsResult::err(TypeAnnotation::Virtual(def.into()), diagnostics))
}
// list
@ -218,7 +272,7 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"list".into())
} =>
{
let def_ann = parse_ast_to_type_annotation_kinds(
let DiagnosticsResult { value: def_ann, engine: diags } = parse_ast_to_type_annotation_kinds(
resolver,
top_level_defs,
unifier,
@ -226,7 +280,9 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
slice.as_ref(),
locked,
)?;
Ok(TypeAnnotation::List(def_ann.into()))
diagnostics.merge_with(diags);
Ok(DiagnosticsResult::err(TypeAnnotation::List(def_ann.into()), diagnostics))
}
// option
@ -242,14 +298,14 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
primitives,
slice.as_ref(),
locked,
)?;
)?.value;
let id =
if let TypeEnum::TObj { obj_id, .. } = unifier.get_ty(primitives.option).as_ref() {
*obj_id
} else {
unreachable!()
};
Ok(TypeAnnotation::CustomClass { id, params: vec![def_ann] })
Ok(DiagnosticsResult::err(TypeAnnotation::CustomClass { id, params: vec![def_ann] }, diagnostics))
}
// tuple
@ -277,20 +333,43 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
locked.clone(),
)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(TypeAnnotation::Tuple(type_annotations))
.collect_vec();
diagnostics.extend(type_annotations.iter().cloned().flat_map(|res| match res {
Ok(DiagnosticsResult { engine: diags, .. }) => diags,
Err(diags) => diags,
}.into_iter()));
if diagnostics.has_errors() {
return Err(diagnostics)
}
let type_annotations = type_annotations.into_iter()
.map(|res| res.ok().unwrap().value)
.collect_vec();
Ok(DiagnosticsResult::err(TypeAnnotation::Tuple(type_annotations), diagnostics))
}
// custom class
ast::ExprKind::Subscript { value, slice, .. } => {
if let ast::ExprKind::Name { id, .. } = &value.node {
class_name_handle(id, slice, unifier, locked)
class_name_handle(id, value.location, slice, unifier, locked)
} else {
Err(format!("unsupported expression type for class name (at {})", value.location))
diagnostics.insert(DiagErr(
"unsupported expression type for class name".into(),
value.location
));
return Err(diagnostics)
}
}
_ => Err(format!("unsupported expression for type annotation (at {})", expr.location)),
_ => {
diagnostics.insert(DiagErr(
"unsupported expression for type annotation".into(),
expr.location
));
Err(diagnostics)
}
}
}
@ -302,19 +381,27 @@ pub fn get_type_from_type_annotation_kinds(
unifier: &mut Unifier,
primitives: &PrimitiveStore,
ann: &TypeAnnotation,
subst_list: &mut Option<Vec<Type>>
) -> Result<Type, String> {
subst_list: &mut Option<Vec<Type>>,
loc: &Option<Location>,
) -> ResultOrDiagnostics<Type> {
let mut diagnostics = DiagnosticEngine::new();
match ann {
TypeAnnotation::CustomClass { id: obj_id, params } => {
let def_read = top_level_defs[obj_id.0].read();
let class_def: &TopLevelDef = def_read.deref();
if let TopLevelDef::Class { fields, methods, type_vars, .. } = class_def {
if let TopLevelDef::Class { name, fields, methods, type_vars, .. } = class_def {
if type_vars.len() != params.len() {
Err(format!(
"unexpected number of type parameters: expected {} but got {}",
diagnostics.insert(DiagErr(
format!(
"expected {} type parameters for class `{}` but got {}",
type_vars.len(),
params.len()
))
name,
params.len(),
),
loc.unwrap(),
));
Err(diagnostics)
} else {
let param_ty = params
.iter()
@ -324,10 +411,16 @@ pub fn get_type_from_type_annotation_kinds(
unifier,
primitives,
x,
subst_list
subst_list,
loc,
)
})
.collect::<Result<Vec<_>, _>>()?;
diagnostics.extend(param_ty.iter().cloned().flat_map(|res| res.engine.into_iter()));
let param_ty = param_ty.into_iter()
.map(|res| res.value)
.collect_vec();
let subst = {
// check for compatible range
@ -351,7 +444,8 @@ pub fn get_type_from_type_annotation_kinds(
if ok {
result.insert(*id, p);
} else {
return Err(format!(
diagnostics.insert(DiagErr(
format!(
"cannot apply type {} to type variable with id {:?}",
unifier.internal_stringify(
p,
@ -360,7 +454,10 @@ pub fn get_type_from_type_annotation_kinds(
&mut None
),
*id
),
loc.unwrap(),
));
return Err(diagnostics)
}
} else {
unreachable!("must be generic type var")
@ -389,22 +486,25 @@ pub fn get_type_from_type_annotation_kinds(
if need_subst {
subst_list.as_mut().map(|wl| wl.push(ty));
}
Ok(ty)
Ok(DiagnosticsResult::err(ty, diagnostics))
}
} else {
unreachable!("should be class def here")
}
}
TypeAnnotation::Primitive(ty) | TypeAnnotation::TypeVar(ty) => Ok(*ty),
TypeAnnotation::Primitive(ty) | TypeAnnotation::TypeVar(ty) => Ok(DiagnosticsResult::err(*ty, diagnostics)),
TypeAnnotation::Virtual(ty) => {
let ty = get_type_from_type_annotation_kinds(
top_level_defs,
unifier,
primitives,
ty.as_ref(),
subst_list
subst_list,
loc,
)?;
Ok(unifier.add_ty(TypeEnum::TVirtual { ty }))
let ty = diagnostics.consume_partial_result(ty);
Ok(DiagnosticsResult::err(unifier.add_ty(TypeEnum::TVirtual { ty }), diagnostics))
}
TypeAnnotation::List(ty) => {
let ty = get_type_from_type_annotation_kinds(
@ -412,18 +512,26 @@ pub fn get_type_from_type_annotation_kinds(
unifier,
primitives,
ty.as_ref(),
subst_list
subst_list,
loc,
)?;
Ok(unifier.add_ty(TypeEnum::TList { ty }))
let ty = diagnostics.consume_partial_result(ty);
Ok(DiagnosticsResult::err(unifier.add_ty(TypeEnum::TList { ty }), diagnostics))
}
TypeAnnotation::Tuple(tys) => {
let tys = tys
.iter()
.map(|x| {
get_type_from_type_annotation_kinds(top_level_defs, unifier, primitives, x, subst_list)
get_type_from_type_annotation_kinds(top_level_defs, unifier, primitives, x, subst_list, loc)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(unifier.add_ty(TypeEnum::TTuple { ty: tys }))
diagnostics.extend(tys.iter().cloned().flat_map(|res| res.engine.into_iter()));
let tys = tys.into_iter()
.map(|res| res.value)
.collect_vec();
Ok(DiagnosticsResult::err(unifier.add_ty(TypeEnum::TTuple { ty: tys }), diagnostics))
}
}
}

View File

@ -1,4 +1,5 @@
use crate::typecheck::typedef::TypeEnum;
use crate::{Diagnostic::*, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics};
use super::type_inferencer::Inferencer;
use super::typedef::Type;
@ -6,11 +7,14 @@ use nac3parser::ast::{self, Constant, Expr, ExprKind, Operator::{LShift, RShift}
use std::{collections::HashSet, iter::once};
impl<'a> Inferencer<'a> {
fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> Result<(), String> {
fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> ResultOrDiagnostics<()> {
if matches!(expr.custom, Some(ty) if self.unifier.unioned(ty, self.primitives.none)) {
Err(format!("Error at {}: cannot have value none", expr.location))
Err(DiagnosticEngine::single(DiagErr(
"cannot have value none".into(),
expr.location,
)))
} else {
Ok(())
Ok(DiagnosticsResult::ok(()))
}
}
@ -18,64 +22,97 @@ impl<'a> Inferencer<'a> {
&mut self,
pattern: &Expr<Option<Type>>,
defined_identifiers: &mut HashSet<StrRef>,
) -> Result<(), String> {
) -> ResultOrDiagnostics<()> {
let mut diagnostics = DiagnosticEngine::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() => {
diagnostics.insert(DiagErr(
"cannot assign to a `none`".into(),
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) {
diagnostics.merge_with(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) {
diagnostics.merge_with(e);
}
Ok(())
if let Err(e) = self.should_have_value(elt) {
diagnostics.merge_with(e);
}
}
}
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) {
diagnostics.merge_with(e);
}
if let Err(e) = self.should_have_value(value) {
diagnostics.merge_with(e);
}
if let Err(e) = self.check_expr(slice, defined_identifiers) {
diagnostics.merge_with(e);
}
if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty(value.custom.unwrap()) {
return Err(format!(
"Error at {}: cannot assign to tuple element",
value.location
diagnostics.insert(DiagErr(
"cannot assign to tuple element".into(),
value.location,
));
}
Ok(())
}
ExprKind::Constant { .. } => {
Err(format!("cannot assign to a constant (at {})", pattern.location))
diagnostics.insert(DiagErr(
"cannot assign to a constant".into(),
pattern.location,
));
}
_ => self.check_expr(pattern, defined_identifiers),
_ => {
if let Err(e) = self.check_expr(pattern, defined_identifiers) {
diagnostics.merge_with(e);
}
}
}
diagnostics.into_result(())
}
fn check_expr(
&mut self,
expr: &Expr<Option<Type>>,
defined_identifiers: &mut HashSet<StrRef>,
) -> Result<(), String> {
) -> ResultOrDiagnostics<()> {
let mut diagnostics = DiagnosticEngine::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!(
"expected concrete type at {} but got {}",
diagnostics.insert(DiagErr(
format!("expected concrete type but got {}", self.unifier.get_ty(*ty).get_type_name()),
expr.location,
self.unifier.get_ty(*ty).get_type_name()
));
}
}
match &expr.node {
ExprKind::Name { id, .. } => {
if id == &"none".into() {
return Ok(());
if id != &"none".into() {
if let Err(e) = self.should_have_value(expr) {
diagnostics.merge_with(e);
}
self.should_have_value(expr)?;
if !defined_identifiers.contains(id) {
match self.function_data.resolver.get_symbol_type(
self.unifier,
@ -86,32 +123,49 @@ impl<'a> Inferencer<'a> {
Ok(_) => {
self.defined_identifiers.insert(*id);
}
Err(e) => {
return Err(format!(
"type error at identifier `{}` ({}) at {}",
id, e, expr.location
diagnostics.insert(DiagErr(
format!("type error at identifier `{}` ({})", 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)?;
diagnostics.consume_result_to_option(self.check_expr(elt, defined_identifiers));
diagnostics.consume_result_to_option(self.should_have_value(elt));
}
}
ExprKind::Attribute { value, .. } => {
self.check_expr(value, defined_identifiers)?;
self.should_have_value(value)?;
if let Err(errs) = self.check_expr(value, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(value) {
diagnostics.merge_with(e);
}
}
ExprKind::BinOp { left, op, right, .. } => {
if let Err(errs) = self.check_expr(left, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(errs) = self.check_expr(right, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(left) {
diagnostics.merge_with(e);
}
if let Err(e) = self.should_have_value(right) {
diagnostics.merge_with(e);
}
ExprKind::BinOp { left, op, right } => {
self.check_expr(left, defined_identifiers)?;
self.check_expr(right, defined_identifiers)?;
self.should_have_value(left)?;
self.should_have_value(right)?;
// Check whether a bitwise shift has a negative RHS constant value
if *op == LShift || *op == RShift {
@ -122,40 +176,70 @@ impl<'a> Inferencer<'a> {
};
if *rhs_val < 0 {
return Err(format!(
"shift count is negative at {}",
right.location
diagnostics.insert(DiagErr(
"shift count is negative".into(),
right.location,
));
}
}
}
}
ExprKind::UnaryOp { operand, .. } => {
self.check_expr(operand, defined_identifiers)?;
self.should_have_value(operand)?;
if let Err(errs) = self.check_expr(operand, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(operand) {
diagnostics.merge_with(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) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(elt) {
diagnostics.merge_with(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) {
diagnostics.merge_with(e);
}
if let Err(errs) = self.check_expr(value, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(errs) = self.check_expr(slice, defined_identifiers) {
diagnostics.merge_with(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) {
diagnostics.merge_with(errs);
}
if let Err(errs) = self.check_expr(body, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(errs) = self.check_expr(orelse, defined_identifiers) {
diagnostics.merge_with(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) {
diagnostics.merge_with(e);
}
if let Err(errs) = self.check_expr(elt, defined_identifiers) {
diagnostics.merge_with(errs);
}
}
}
ExprKind::Lambda { args, body } => {
let mut defined_identifiers = defined_identifiers.clone();
for arg in args.args.iter() {
@ -164,160 +248,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) {
diagnostics.merge_with(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) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(iter) {
diagnostics.merge_with(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) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(target) {
diagnostics.merge_with(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) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(term) {
diagnostics.merge_with(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) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(expr) {
diagnostics.merge_with(e);
}
}
}
ExprKind::Constant { .. } => {}
_ => {
unimplemented!()
}
}
Ok(())
diagnostics.into_result(())
}
// 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)?;
) -> DiagnosticsResult<bool> {
let mut diagnostics = DiagnosticEngine::new();
let stmt_returns = match &stmt.node {
StmtKind::For {
target,
iter,
body,
orelse,
..
} => {
if let Err(errs) = self.check_expr(iter, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(iter) {
diagnostics.merge_with(e);
}
let mut local_defined_identifiers = defined_identifiers.clone();
for stmt in orelse.iter() {
self.check_stmt(stmt, &mut local_defined_identifiers)?;
let DiagnosticsResult { engine: errs, .. } = self.check_stmt(stmt, &mut local_defined_identifiers);
diagnostics.merge_with(errs);
}
let mut local_defined_identifiers = defined_identifiers.clone();
self.check_pattern(target, &mut local_defined_identifiers)?;
self.should_have_value(target)?;
if let Err(errs) = self.check_pattern(target, &mut local_defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(target) {
diagnostics.merge_with(e);
}
for stmt in body.iter() {
self.check_stmt(stmt, &mut local_defined_identifiers)?;
let DiagnosticsResult { engine: errs, .. } = self.check_stmt(stmt, &mut local_defined_identifiers);
diagnostics.merge_with(errs);
}
Ok(false)
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) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(test) {
diagnostics.merge_with(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 DiagnosticsResult { value: body_returned, engine: errs } = self.check_block(body, &mut body_identifiers);
diagnostics.merge_with(errs);
let DiagnosticsResult { value: orelse_returned, engine: errs } = self.check_block(orelse, &mut orelse_identifiers);
diagnostics.merge_with(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)?;
let mut defined_identifiers = defined_identifiers.clone();
self.check_block(body, &mut defined_identifiers)?;
self.check_block(orelse, &mut defined_identifiers)?;
Ok(false)
if let Err(errs) = self.check_expr(test, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(test) {
diagnostics.merge_with(e);
}
let mut defined_identifiers = defined_identifiers.clone();
let DiagnosticsResult { engine: errs, .. } = self.check_block(body, &mut defined_identifiers);
diagnostics.merge_with(errs);
let DiagnosticsResult { engine: errs, .. } = self.check_block(orelse, &mut defined_identifiers);
diagnostics.merge_with(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) {
diagnostics.merge_with(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) {
diagnostics.merge_with(errs);
}
}
self.check_block(body, &mut new_defined_identifiers)?;
Ok(false)
}
let DiagnosticsResult { engine: errs, .. } = self.check_block(body, &mut new_defined_identifiers);
diagnostics.merge_with(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 DiagnosticsResult { engine: errs, .. } = self.check_block(body, &mut defined_identifiers.clone());
diagnostics.merge_with(errs);
let DiagnosticsResult { engine: errs, .. } = self.check_block(orelse, &mut defined_identifiers.clone());
diagnostics.merge_with(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 DiagnosticsResult { engine: errs, .. } = self.check_block(body, &mut defined_identifiers);
diagnostics.merge_with(errs);
}
self.check_block(finalbody, defined_identifiers)?;
Ok(false)
let DiagnosticsResult { engine: errs, .. } = self.check_block(finalbody, defined_identifiers);
diagnostics.merge_with(errs);
false
}
StmtKind::Expr { value, .. } => {
self.check_expr(value, defined_identifiers)?;
Ok(false)
if let Err(e) = self.check_expr(value, defined_identifiers) {
diagnostics.merge_with(e);
}
false
}
StmtKind::Assign { targets, value, .. } => {
self.check_expr(value, defined_identifiers)?;
self.should_have_value(value)?;
if let Err(errs) = self.check_expr(value, defined_identifiers) {
diagnostics.merge_with(errs);
}
if let Err(e) = self.should_have_value(value) {
diagnostics.merge_with(e);
}
for target in targets {
self.check_pattern(target, defined_identifiers)?;
if let Err(errs) = self.check_pattern(target, defined_identifiers) {
diagnostics.merge_with(errs);
}
Ok(false)
}
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) {
diagnostics.merge_with(errs);
}
Ok(false)
if let Err(e) = self.should_have_value(value) {
diagnostics.merge_with(e);
}
if let Err(errs) = self.check_pattern(target, defined_identifiers) {
diagnostics.merge_with(errs);
}
}
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) {
diagnostics.merge_with(errs);
}
Ok(true)
if let Err(e) = self.should_have_value(value) {
diagnostics.merge_with(e);
}
}
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) {
diagnostics.merge_with(errs);
}
Ok(true)
}
true
}
// break, raise, etc.
_ => Ok(false),
}
_ => false,
};
DiagnosticsResult::err(stmt_returns, diagnostics)
}
pub fn check_block(
&mut self,
block: &[Stmt<Option<Type>>],
defined_identifiers: &mut HashSet<StrRef>,
) -> Result<bool, String> {
) -> DiagnosticsResult<bool> {
let mut diagnostics = DiagnosticEngine::new();
let mut ret = false;
for stmt in block {
if ret {
println!("warning: dead code at {:?}\n", stmt.location)
diagnostics.insert(DiagWarn(
"unreachable statement".into(),
stmt.location,
));
}
if self.check_stmt(stmt, defined_identifiers)? {
ret = true;
let DiagnosticsResult { value: stmt_rets, engine: stmt_errs } =
self.check_stmt(stmt, defined_identifiers);
ret |= stmt_rets;
diagnostics.merge_with(stmt_errs);
}
}
Ok(ret)
DiagnosticsResult::err(ret, diagnostics)
}
}

View File

@ -5,8 +5,8 @@ use std::{cell::RefCell, sync::Arc};
use super::typedef::{Call, FunSignature, FuncArg, RecordField, Type, TypeEnum, Unifier};
use super::{magic_methods::*, typedef::CallId};
use crate::{symbol_resolver::SymbolResolver, toplevel::TopLevelContext};
use itertools::izip;
use crate::{DiagErr, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, symbol_resolver::SymbolResolver, toplevel::TopLevelContext};
use itertools::{Itertools, izip};
use nac3parser::ast::{
self,
fold::{self, Fold},
@ -64,19 +64,15 @@ pub struct Inferencer<'a> {
struct NaiveFolder();
impl fold::Fold<()> for NaiveFolder {
type TargetU = Option<Type>;
type Error = String;
type Error = DiagnosticEngine;
fn map_user(&mut self, _: ()) -> Result<Self::TargetU, Self::Error> {
Ok(None)
}
}
fn report_error<T>(msg: &str, location: Location) -> Result<T, String> {
Err(format!("{} at {}", msg, location))
}
impl<'a> fold::Fold<()> for Inferencer<'a> {
type TargetU = Option<Type>;
type Error = String;
type Error = DiagnosticEngine;
fn map_user(&mut self, _: ()) -> Result<Self::TargetU, Self::Error> {
Ok(None)
@ -86,6 +82,8 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
&mut self,
mut node: ast::Stmt<()>,
) -> Result<ast::Stmt<Self::TargetU>, Self::Error> {
let mut diagnostics = DiagnosticEngine::new();
let stmt = match node.node {
// we don't want fold over type annotation
ast::StmtKind::AnnAssign { mut target, annotation, value, simple, config_comment } => {
@ -101,19 +99,24 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
self.unify(target.custom.unwrap(), ty.custom.unwrap(), &node.location)?;
Some(ty)
} else {
return report_error(
"declaration without definition is not yet supported",
diagnostics.insert(DiagErr(
"declaration without definition is not yet supported".into(),
node.location,
);
));
return Err(diagnostics)
};
let top_level_defs = self.top_level.definitions.read();
let annotation_type = self.function_data.resolver.parse_type_annotation(
let DiagnosticsResult { value: annotation_type, engine: diags } = self.function_data.resolver
.parse_type_annotation(
top_level_defs.as_slice(),
self.unifier,
self.primitives,
annotation.as_ref(),
)?;
diagnostics.merge_with(diags);
self.unify(annotation_type, target.custom.unwrap(), &node.location)?;
let annotation = Box::new(NaiveFolder().fold_expr(*annotation)?);
Located {
location: node.location,
@ -127,6 +130,7 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
},
}
}
ast::StmtKind::Try { body, handlers, orelse, finalbody, config_comment } => {
let body = body
.into_iter()
@ -142,28 +146,38 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
let ast::ExcepthandlerKind::ExceptHandler { type_, name, body } =
handler.node;
let type_ = if let Some(type_) = type_ {
let typ = self.function_data.resolver.parse_type_annotation(
let DiagnosticsResult { value: typ, engine: diags } = self.function_data.resolver
.parse_type_annotation(
top_level_defs.as_slice(),
self.unifier,
self.primitives,
&type_,
)?;
diagnostics.merge_with(diags);
self.virtual_checks.push((
typ,
self.primitives.exception,
handler.location,
));
if let Some(name) = name {
if !self.defined_identifiers.contains(&name) {
self.defined_identifiers.insert(name);
}
if let Some(old_typ) = self.variable_mapping.insert(name, typ) {
let loc = handler.location;
self.unifier.unify(old_typ, typ).map_err(|e| {
e.at(Some(loc)).to_display(self.unifier).to_string()
// TODO
DiagnosticEngine::single(DiagErr(
e.at(Some(loc)).to_display(self.unifier).to_string(),
loc,
))
})?;
}
}
let mut type_ = naive_folder.fold_expr(*type_)?;
type_.custom = Some(typ);
Some(Box::new(type_))
@ -270,8 +284,12 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
})
.collect();
let loc = node.location;
let targets = targets
.map_err(|e| e.at(Some(loc)).to_display(self.unifier).to_string())?;
let targets = targets.map_err(|e|
DiagnosticEngine::single(DiagErr(
e.to_display(self.unifier).to_string(),
loc
))
)?;
return Ok(Located {
location: node.location,
node: ast::StmtKind::Assign {
@ -307,19 +325,25 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
ast::StmtKind::If { test, .. } | ast::StmtKind::While { test, .. } => {
self.unify(test.custom.unwrap(), self.primitives.bool, &test.location)?;
}
ast::StmtKind::Assign { targets, value, .. } => {
for target in targets.iter() {
self.unify(target.custom.unwrap(), value.custom.unwrap(), &target.location)?;
}
}
ast::StmtKind::AnnAssign { .. } | ast::StmtKind::Expr { .. } => {}
ast::StmtKind::Break { .. }
| ast::StmtKind::Continue { .. }
| ast::StmtKind::Pass { .. } => {}
ast::StmtKind::Raise { exc, cause, .. } => {
if let Some(cause) = cause {
return report_error("raise ... from cause is not supported", cause.location);
diagnostics.insert(DiagErr(
"raise ... from cause is not supported".into(),
cause.location,
));
}
if let Some(exc) = exc {
self.virtual_checks.push((
exc.custom.unwrap(),
@ -327,12 +351,17 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
exc.location,
));
} else if !self.in_handler {
return report_error(
"cannot reraise outside exception handlers",
diagnostics.insert(DiagErr(
"cannot reraise outside exception handlers".into(),
stmt.location,
);
));
}
if diagnostics.has_errors() {
return Err(diagnostics)
}
}
ast::StmtKind::With { items, .. } => {
for item in items.iter() {
let ty = item.context_expr.custom.unwrap();
@ -340,14 +369,16 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
let mut fast_path = false;
if let TypeEnum::TObj { fields, .. } = &*self.unifier.get_ty(ty) {
fast_path = true;
if let Some(enter) = fields.get(&"__enter__".into()).cloned() {
if let TypeEnum::TFunc(signature) = &*self.unifier.get_ty(enter.0) {
if !signature.args.is_empty() {
return report_error(
"__enter__ method should take no argument other than self",
diagnostics.insert(DiagErr(
"__enter__ method should take no argument other than self".into(),
stmt.location,
);
));
}
if let Some(var) = &item.optional_vars {
if signature.vars.is_empty() {
self.unify(
@ -363,29 +394,31 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
fast_path = false;
}
} else {
return report_error(
"__enter__ method is required for context manager",
diagnostics.insert(DiagErr(
"__enter__ method is required for context manager".into(),
stmt.location,
);
));
}
if let Some(exit) = fields.get(&"__exit__".into()).cloned() {
if let TypeEnum::TFunc(signature) = &*self.unifier.get_ty(exit.0) {
if !signature.args.is_empty() {
return report_error(
"__exit__ method should take no argument other than self",
diagnostics.insert(DiagErr(
"__exit__ method should take no argument other than self".into(),
stmt.location,
);
));
}
} else {
fast_path = false;
}
} else {
return report_error(
"__exit__ method is required for context manager",
diagnostics.insert(DiagErr(
"__exit__ method is required for context manager".into(),
stmt.location,
);
));
}
}
if !fast_path {
let enter = TypeEnum::TFunc(FunSignature {
args: vec![],
@ -409,51 +442,80 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
self.unify(ty, record, &stmt.location)?;
}
}
if diagnostics.has_errors() {
return Err(diagnostics)
}
}
ast::StmtKind::Return { value, .. } => match (value, self.function_data.return_type) {
(Some(v), Some(v1)) => {
self.unify(v.custom.unwrap(), v1, &v.location)?;
}
(Some(_), None) => {
return report_error("Unexpected return value", stmt.location);
diagnostics.insert(
DiagErr("Unexpected return value".into(), stmt.location),
);
return Err(diagnostics)
}
(None, Some(_)) => {
return report_error("Expected return value", stmt.location);
diagnostics.insert(
DiagErr("Expected return value".into(), stmt.location),
);
return Err(diagnostics)
}
(None, None) => {}
},
ast::StmtKind::AugAssign { target, op, value, .. } => {
let res_ty = self.infer_bin_ops(stmt.location, target, op, value, true)?;
let res_ty = diagnostics.consume_partial_result(res_ty);
self.unify(res_ty, target.custom.unwrap(), &stmt.location)?;
}
ast::StmtKind::Assert { test, msg, .. } => {
self.unify(test.custom.unwrap(), self.primitives.bool, &test.location)?;
match msg {
Some(m) => self.unify(m.custom.unwrap(), self.primitives.str, &m.location)?,
None => ()
Some(m) => {
self.unify(m.custom.unwrap(), self.primitives.str, &m.location)?;
}
None => {}
}
}
_ => return report_error("Unsupported statement type", stmt.location),
_ => {
diagnostics.insert(
DiagErr("Unsupported statement type".into(), stmt.location)
);
return Err(diagnostics)
}
};
Ok(stmt)
}
fn fold_expr(&mut self, node: ast::Expr<()>) -> Result<ast::Expr<Self::TargetU>, Self::Error> {
let mut diagnostics = DiagnosticEngine::new();
let expr = match node.node {
// TODO
ast::ExprKind::Call { func, args, keywords } => {
return self.fold_call(node.location, *func, args, keywords);
return self.fold_call(node.location, *func, args, keywords)
.map(|res| res.value);
}
ast::ExprKind::Lambda { args, body } => {
return self.fold_lambda(node.location, *args, *body);
return self.fold_lambda(node.location, *args, *body)
.map(|res| res.value);
}
ast::ExprKind::ListComp { elt, generators } => {
return self.fold_listcomp(node.location, *elt, generators);
return self.fold_listcomp(node.location, *elt, generators)
.map(|res| res.value);
}
_ => fold::fold_expr(self, node)?,
};
let custom = match &expr.node {
ast::ExprKind::Constant { value, .. } => {
Some(self.infer_constant(value, &expr.location)?)
// TODO
Some(self.infer_constant(value, &expr.location)?.value)
}
ast::ExprKind::Name { id, .. } => {
// the name `none` is special since it may have different types
@ -488,76 +550,96 @@ impl<'a> fold::Fold<()> for Inferencer<'a> {
self.defined_identifiers.insert(*id);
}
Err(e) => {
return report_error(
&format!("type error at identifier `{}` ({})", id, e),
diagnostics.insert(DiagErr(
format!("type error at identifier `{}` ({})", id, e),
expr.location,
);
));
}
}
}
Some(self.infer_identifier(*id)?)
diagnostics.consume_result_to_option(self.infer_identifier(*id))
}
}
ast::ExprKind::List { elts, .. } => Some(self.infer_list(elts)?),
ast::ExprKind::Tuple { elts, .. } => Some(self.infer_tuple(elts)?),
ast::ExprKind::List { elts, .. } => {
Some(self.infer_list(elts)?.value)
}
ast::ExprKind::Tuple { elts, .. } => {
Some(self.infer_tuple(elts)?.value)
}
ast::ExprKind::Attribute { value, attr, ctx } => {
Some(self.infer_attribute(value, *attr, ctx)?)
Some(self.infer_attribute(value, *attr, ctx)?.value)
}
ast::ExprKind::BoolOp { values, .. } => Some(self.infer_bool_ops(values)?),
ast::ExprKind::BoolOp { values, .. } => Some(self.infer_bool_ops(values)?.value),
ast::ExprKind::BinOp { left, op, right } => {
Some(self.infer_bin_ops(expr.location, left, op, right, false)?)
Some(self.infer_bin_ops(expr.location, left, op, right, false)?.value)
}
ast::ExprKind::UnaryOp { op, operand } => Some(self.infer_unary_ops(op, operand)?),
ast::ExprKind::UnaryOp { op, operand } => Some(self.infer_unary_ops(op, operand)?.value),
ast::ExprKind::Compare { left, ops, comparators } => {
Some(self.infer_compare(left, ops, comparators)?)
Some(self.infer_compare(left, ops, comparators)?.value)
}
ast::ExprKind::Subscript { value, slice, ctx, .. } => {
Some(self.infer_subscript(value.as_ref(), slice.as_ref(), ctx)?)
Some(self.infer_subscript(value.as_ref(), slice.as_ref(), ctx)?.value)
}
ast::ExprKind::IfExp { test, body, orelse } => {
Some(self.infer_if_expr(test, body.as_ref(), orelse.as_ref())?)
Some(self.infer_if_expr(test, body.as_ref(), orelse.as_ref())?.value)
}
ast::ExprKind::ListComp { .. }
| ast::ExprKind::Lambda { .. }
| ast::ExprKind::Call { .. } => expr.custom, // already computed
ast::ExprKind::Slice { .. } => None, // we don't need it for slice
_ => return report_error("not supported", expr.location),
_ => {
diagnostics.insert(DiagErr(
"not supported".into(),
expr.location,
));
return Err(diagnostics)
}
};
Ok(ast::Expr { custom, location: expr.location, node: expr.node })
}
}
type InferenceResult = Result<Type, String>;
type InferenceResult = ResultOrDiagnostics<Type>;
impl<'a> Inferencer<'a> {
/// Constrain a <: b
/// Currently implemented as unification
fn constrain(&mut self, a: Type, b: Type, location: &Location) -> Result<(), String> {
fn constrain(&mut self, a: Type, b: Type, location: &Location) -> ResultOrDiagnostics<()> {
self.unify(a, b, location)
}
fn unify(&mut self, a: Type, b: Type, location: &Location) -> Result<(), String> {
fn unify(&mut self, a: Type, b: Type, location: &Location) -> ResultOrDiagnostics<()> {
self.unifier
.unify(a, b)
.map_err(|e| e.at(Some(*location)).to_display(self.unifier).to_string())
.map(|res| DiagnosticsResult::ok(res))
.map_err(|e| DiagnosticEngine::single(DiagErr(e.to_display(self.unifier).to_string(), *location)))
}
fn infer_pattern(&mut self, pattern: &ast::Expr<()>) -> Result<(), String> {
fn infer_pattern(&mut self, pattern: &ast::Expr<()>) -> ResultOrDiagnostics<()> {
let diagnostics = DiagnosticEngine::new();
match &pattern.node {
ExprKind::Name { id, .. } => {
if !self.defined_identifiers.contains(id) {
self.defined_identifiers.insert(*id);
}
Ok(())
}
ExprKind::Tuple { elts, .. } => {
for elt in elts.iter() {
self.infer_pattern(elt)?;
}
Ok(())
}
_ => Ok(()),
_ => ()
}
Ok(DiagnosticsResult::err((), diagnostics))
}
fn build_method_call(
@ -592,9 +674,12 @@ impl<'a> Inferencer<'a> {
.rev()
.collect();
self.unifier.unify_call(&call, ty, sign, &required).map_err(|e| {
e.at(Some(location)).to_display(self.unifier).to_string()
DiagnosticEngine::single(DiagErr(
e.to_display(self.unifier).to_string(),
location,
))
})?;
return Ok(sign.ret);
return Ok(DiagnosticsResult::ok(sign.ret));
}
}
}
@ -614,7 +699,7 @@ impl<'a> Inferencer<'a> {
let fields = once((method.into(), RecordField::new(call, false, Some(location)))).collect();
let record = self.unifier.add_record(fields);
self.constrain(obj, record, &location)?;
Ok(ret)
Ok(DiagnosticsResult::ok(ret))
}
fn fold_lambda(
@ -622,7 +707,9 @@ impl<'a> Inferencer<'a> {
location: Location,
args: Arguments,
body: ast::Expr<()>,
) -> Result<ast::Expr<Option<Type>>, String> {
) -> ResultOrDiagnostics<ast::Expr<Option<Type>>> {
let mut diagnostics = DiagnosticEngine::new();
if !args.posonlyargs.is_empty()
|| args.vararg.is_some()
|| !args.kwonlyargs.is_empty()
@ -630,10 +717,10 @@ impl<'a> Inferencer<'a> {
|| !args.defaults.is_empty()
{
// actually I'm not sure whether programs violating this is a valid python program.
return report_error(
"We only support positional or keyword arguments without defaults for lambdas",
diagnostics.insert(DiagErr(
"only positional or keyword arguments without defaults is supported for lambdas".into(),
if args.args.is_empty() { body.location } else { args.args[0].location },
);
));
}
let mut defined_identifiers = self.defined_identifiers.clone();
@ -675,15 +762,21 @@ impl<'a> Inferencer<'a> {
let body = new_context.fold_expr(body)?;
new_context.unify(fun.ret, body.custom.unwrap(), &location)?;
let mut args = new_context.fold_arguments(args)?;
if diagnostics.has_errors() {
return Err(diagnostics)
}
for (arg, (name, ty)) in args.args.iter_mut().zip(fn_args.iter()) {
assert_eq!(&arg.node.arg, name);
arg.custom = Some(*ty);
}
Ok(Located {
Ok(DiagnosticsResult::err(Located {
location,
node: ExprKind::Lambda { args: args.into(), body: body.into() },
custom: Some(self.unifier.add_ty(TypeEnum::TFunc(fun))),
})
}, diagnostics))
}
fn fold_listcomp(
@ -691,13 +784,17 @@ impl<'a> Inferencer<'a> {
location: Location,
elt: ast::Expr<()>,
mut generators: Vec<Comprehension>,
) -> Result<ast::Expr<Option<Type>>, String> {
) -> ResultOrDiagnostics<ast::Expr<Option<Type>>> {
let mut diagnostics = DiagnosticEngine::new();
if generators.len() != 1 {
return report_error(
"Only 1 generator statement for list comprehension is supported",
diagnostics.insert(DiagErr(
"more than one generator statement for list comprehension is not supported".into(),
generators[0].target.location,
);
));
return Err(diagnostics)
}
let variable_mapping = self.variable_mapping.clone();
let defined_identifiers = self.defined_identifiers.clone();
let mut new_context = Inferencer {
@ -712,10 +809,15 @@ impl<'a> Inferencer<'a> {
// listcomp expr should not be considered as inside an exception handler...
in_handler: false,
};
let generator = generators.pop().unwrap();
if generator.is_async {
return report_error("Async iterator not supported", generator.target.location);
diagnostics.insert(DiagErr(
"async iterator not supported".into(),
generator.target.location,
));
}
new_context.infer_pattern(&generator.target)?;
let target = new_context.fold_expr(*generator.target)?;
let iter = new_context.fold_expr(*generator.iter)?;
@ -743,7 +845,11 @@ impl<'a> Inferencer<'a> {
new_context.unify(v.custom.unwrap(), new_context.primitives.bool, &v.location)?;
}
Ok(Located {
if diagnostics.has_errors() {
return Err(diagnostics)
}
Ok(DiagnosticsResult::err(Located {
location,
custom: Some(new_context.unifier.add_ty(TypeEnum::TList { ty: elt.custom.unwrap() })),
node: ExprKind::ListComp {
@ -755,7 +861,7 @@ impl<'a> Inferencer<'a> {
is_async: false,
}],
},
})
}, diagnostics))
}
fn fold_call(
@ -764,34 +870,42 @@ impl<'a> Inferencer<'a> {
func: ast::Expr<()>,
mut args: Vec<ast::Expr<()>>,
keywords: Vec<Located<ast::KeywordData>>,
) -> Result<ast::Expr<Option<Type>>, String> {
let func =
if let Located { location: func_location, custom, node: ExprKind::Name { id, ctx } } =
func
{
) -> ResultOrDiagnostics<ast::Expr<Option<Type>>> {
let mut diagnostics = DiagnosticEngine::new();
let func = if let Located {
location: func_location,
custom,
node: ExprKind::Name { id, ctx }
} = func {
// handle special functions that cannot be typed in the usual way...
if id == "virtual".into() {
if args.is_empty() || args.len() > 2 || !keywords.is_empty() {
return report_error(
"`virtual` can only accept 1/2 positional arguments",
diagnostics.insert(DiagErr(
"`virtual` can only accept 1/2 positional arguments".into(),
func_location,
);
));
}
let arg0 = self.fold_expr(args.remove(0))?;
let ty = if let Some(arg) = args.pop() {
let top_level_defs = self.top_level.definitions.read();
self.function_data.resolver.parse_type_annotation(
let ty = self.function_data.resolver.parse_type_annotation(
top_level_defs.as_slice(),
self.unifier,
self.primitives,
&arg,
)?
)?;
let ty = diagnostics.consume_partial_result(ty);
ty
} else {
self.unifier.get_dummy_var().0
};
self.virtual_checks.push((arg0.custom.unwrap(), ty, func_location));
let custom = Some(self.unifier.add_ty(TypeEnum::TVirtual { ty }));
return Ok(Located {
return Ok(DiagnosticsResult::err(Located {
location,
custom,
node: ExprKind::Call {
@ -803,8 +917,9 @@ impl<'a> Inferencer<'a> {
args: vec![arg0],
keywords: vec![],
},
});
}, diagnostics));
}
// int64 is special because its argument can be a constant larger than int32
if id == "int64".into() && args.len() == 1 {
if let ExprKind::Constant { value: ast::Constant::Int(val), kind } =
@ -812,64 +927,83 @@ impl<'a> Inferencer<'a> {
{
let custom = Some(self.primitives.int64);
let v: Result<i64, _> = (*val).try_into();
if v.is_ok() {
return Ok(Located {
return v
.map(|_| DiagnosticsResult::ok(Located {
location: args[0].location,
custom,
node: ExprKind::Constant {
value: ast::Constant::Int(*val),
kind: kind.clone(),
},
});
} else {
return report_error("Integer out of bound", args[0].location)
}
}))
.map_err(|_| {
diagnostics.insert(DiagErr(
format!("integer {} out of bound for int64", *val),
args[0].location,
));
diagnostics
})
}
}
if id == "uint32".into() && args.len() == 1 {
if let ExprKind::Constant { value: ast::Constant::Int(val), kind } =
&args[0].node
{
let custom = Some(self.primitives.uint32);
let v: Result<u32, _> = (*val).try_into();
if v.is_ok() {
return Ok(Located {
return v
.map(|_| DiagnosticsResult::ok(Located {
location: args[0].location,
custom,
node: ExprKind::Constant {
value: ast::Constant::Int(*val),
kind: kind.clone(),
},
});
} else {
return report_error("Integer out of bound", args[0].location)
}
}))
.map_err(|_| {
diagnostics.insert(DiagErr(
format!("integer {} out of bound for uint32", *val),
args[0].location,
));
diagnostics
})
}
}
if id == "uint64".into() && args.len() == 1 {
if let ExprKind::Constant { value: ast::Constant::Int(val), kind } =
&args[0].node
{
let custom = Some(self.primitives.uint64);
let v: Result<u64, _> = (*val).try_into();
if v.is_ok() {
return Ok(Located {
return v
.map(|_| DiagnosticsResult::ok(Located {
location: args[0].location,
custom,
node: ExprKind::Constant {
value: ast::Constant::Int(*val),
kind: kind.clone(),
},
});
} else {
return report_error("Integer out of bound", args[0].location)
}
}))
.map_err(|_| {
diagnostics.insert(DiagErr(
format!("integer {} out of bound for uint64", *val),
args[0].location,
));
diagnostics
})
}
}
Located { location: func_location, custom, node: ExprKind::Name { id, ctx } }
} else {
func
};
let func = Box::new(self.fold_expr(func)?);
let args = args.into_iter().map(|v| self.fold_expr(v)).collect::<Result<Vec<_>, _>>()?;
let keywords = keywords
@ -898,12 +1032,18 @@ impl<'a> Inferencer<'a> {
.collect();
self.unifier
.unify_call(&call, func.custom.unwrap(), sign, &required)
.map_err(|e| e.at(Some(location)).to_display(self.unifier).to_string())?;
return Ok(Located {
.map_err(|e|
DiagnosticEngine::single(DiagErr(
e.to_display(self.unifier).to_string(),
location
))
)?;
return Ok(DiagnosticsResult::err(Located {
location,
custom: Some(sign.ret),
node: ExprKind::Call { func, args, keywords },
});
}, diagnostics));
}
}
@ -922,16 +1062,21 @@ impl<'a> Inferencer<'a> {
let call = self.unifier.add_ty(TypeEnum::TCall(vec![call]));
self.unify(func.custom.unwrap(), call, &func.location)?;
Ok(Located { location, custom: Some(ret), node: ExprKind::Call { func, args, keywords } })
Ok(DiagnosticsResult::err(Located {
location,
custom: Some(ret),
node: ExprKind::Call { func, args, keywords }
}, diagnostics))
}
fn infer_identifier(&mut self, id: StrRef) -> InferenceResult {
if let Some(ty) = self.variable_mapping.get(&id) {
Ok(*ty)
Ok(DiagnosticsResult::ok(*ty))
} else {
let variable_mapping = &mut self.variable_mapping;
let unifier = &mut self.unifier;
Ok(self
Ok(DiagnosticsResult::ok(
self
.function_data
.resolver
.get_symbol_type(unifier, &self.top_level.definitions.read(), self.primitives, id)
@ -940,45 +1085,83 @@ impl<'a> Inferencer<'a> {
variable_mapping.insert(id, ty);
ty
}))
)
}
}
fn infer_constant(&mut self, constant: &ast::Constant, loc: &Location) -> InferenceResult {
let mut diagnostics = DiagnosticEngine::new();
match constant {
ast::Constant::Bool(_) => Ok(self.primitives.bool),
ast::Constant::Bool(_) => Ok(DiagnosticsResult::ok(self.primitives.bool)),
ast::Constant::Int(val) => {
let int32: Result<i32, _> = (*val).try_into();
// int64 and unsigned integers are handled separately in functions
if int32.is_ok() {
Ok(self.primitives.int32)
Ok(DiagnosticsResult::ok(self.primitives.int32))
} else {
report_error("Integer out of bound", *loc)
diagnostics.insert(DiagErr(
format!("integer {} out of bound for int32", *val),
*loc,
));
Err(diagnostics)
}
}
ast::Constant::Float(_) => Ok(self.primitives.float),
ast::Constant::Float(_) => Ok(DiagnosticsResult::ok(self.primitives.float)),
ast::Constant::Tuple(vals) => {
let ty: Result<Vec<_>, _> =
vals.iter().map(|x| self.infer_constant(x, loc)).collect();
Ok(self.unifier.add_ty(TypeEnum::TTuple { ty: ty? }))
let ty = vals.iter()
.map(|x| self.infer_constant(x, loc))
.collect_vec();
diagnostics.extend(ty.iter().cloned().flat_map(|res| match res {
Ok(v) => v.engine,
Err(errs) => errs
}.into_iter()));
if diagnostics.has_errors() {
return Err(diagnostics)
}
let ty = ty.into_iter()
.map(|res| res.ok().unwrap().value)
.collect_vec();
Ok(DiagnosticsResult::err(self.unifier.add_ty(TypeEnum::TTuple { ty }), diagnostics))
}
ast::Constant::Str(_) => Ok(DiagnosticsResult::ok(self.primitives.str)),
ast::Constant::None => {
diagnostics.insert(DiagErr(
"CPython `None` not supported (nac3 uses `none` instead)".into(),
*loc,
));
Err(diagnostics)
},
_ => {
diagnostics.insert(DiagErr(
"not supported".into(),
*loc,
));
Err(diagnostics)
}
ast::Constant::Str(_) => Ok(self.primitives.str),
ast::Constant::None
=> report_error("CPython `None` not supported (nac3 uses `none` instead)", *loc),
_ => report_error("not supported", *loc),
}
}
fn infer_list(&mut self, elts: &[ast::Expr<Option<Type>>]) -> InferenceResult {
let mut diagnostics = DiagnosticEngine::new();
let ty = self.unifier.get_dummy_var().0;
for t in elts.iter() {
self.unify(ty, t.custom.unwrap(), &t.location)?;
diagnostics.consume_partial_result(self.unify(ty, t.custom.unwrap(), &t.location)?);
}
Ok(self.unifier.add_ty(TypeEnum::TList { ty }))
Ok(DiagnosticsResult::err(self.unifier.add_ty(TypeEnum::TList { ty }), diagnostics))
}
fn infer_tuple(&mut self, elts: &[ast::Expr<Option<Type>>]) -> InferenceResult {
let ty = elts.iter().map(|x| x.custom.unwrap()).collect();
Ok(self.unifier.add_ty(TypeEnum::TTuple { ty }))
Ok(DiagnosticsResult::ok(self.unifier.add_ty(TypeEnum::TTuple { ty })))
}
fn infer_attribute(
@ -987,18 +1170,29 @@ impl<'a> Inferencer<'a> {
attr: StrRef,
ctx: &ExprContext,
) -> InferenceResult {
let mut diagnostics = DiagnosticEngine::new();
let ty = value.custom.unwrap();
if let TypeEnum::TObj { fields, .. } = &*self.unifier.get_ty(ty) {
// just a fast path
match (fields.get(&attr), ctx == &ExprContext::Store) {
(Some((ty, true)), _) => Ok(*ty),
(Some((ty, false)), false) => Ok(*ty),
(Some((ty, true)), _) => Ok(DiagnosticsResult::ok(*ty)),
(Some((ty, false)), false) => Ok(DiagnosticsResult::ok(*ty)),
(Some((_, false)), true) => {
report_error(&format!("Field `{}` is immutable", attr), value.location)
diagnostics.insert(DiagErr(
format!("Field `{}` is immutable", attr),
value.location,
));
Err(diagnostics)
}
(None, _) => {
let t = self.unifier.stringify(ty);
report_error(&format!("`{}::{}` field/method does not exist", t, attr), value.location)
diagnostics.insert(DiagErr(
format!("`{}::{}` field/method does not exist", t, attr),
value.location,
));
Err(diagnostics)
},
}
} else {
@ -1010,7 +1204,7 @@ impl<'a> Inferencer<'a> {
.collect();
let record = self.unifier.add_record(fields);
self.constrain(value.custom.unwrap(), record, &value.location)?;
Ok(attr_ty)
Ok(DiagnosticsResult::err(attr_ty, diagnostics))
}
}
@ -1019,7 +1213,7 @@ impl<'a> Inferencer<'a> {
for v in values {
self.constrain(v.custom.unwrap(), b, &v.location)?;
}
Ok(b)
Ok(DiagnosticsResult::ok(b))
}
fn infer_bin_ops(
@ -1072,8 +1266,11 @@ impl<'a> Inferencer<'a> {
) -> InferenceResult {
let boolean = self.primitives.bool;
for (a, b, c) in izip!(once(left).chain(comparators), comparators, ops) {
let method =
comparison_name(c).ok_or_else(|| "unsupported comparator".to_string())?.into();
let method = comparison_name(c)
.ok_or_else(||
DiagnosticEngine::single(DiagErr("unsupported comparator".to_string(), left.location))
)?
.into();
self.build_method_call(
a.location,
method,
@ -1082,7 +1279,7 @@ impl<'a> Inferencer<'a> {
Some(boolean),
)?;
}
Ok(boolean)
Ok(DiagnosticsResult::ok(boolean))
}
fn infer_subscript(
@ -1091,6 +1288,8 @@ impl<'a> Inferencer<'a> {
slice: &ast::Expr<Option<Type>>,
ctx: &ExprContext,
) -> InferenceResult {
let mut diagnostics = DiagnosticEngine::new();
let ty = self.unifier.get_dummy_var().0;
match &slice.node {
ast::ExprKind::Slice { lower, upper, step } => {
@ -1099,12 +1298,14 @@ impl<'a> Inferencer<'a> {
}
let list = self.unifier.add_ty(TypeEnum::TList { ty });
self.constrain(value.custom.unwrap(), list, &value.location)?;
Ok(list)
Ok(DiagnosticsResult::err(list, diagnostics))
}
ast::ExprKind::Constant { value: ast::Constant::Int(val), .. } => {
// the index is a constant, so value can be a sequence.
let ind: Option<i32> = (*val).try_into().ok();
let ind = ind.ok_or_else(|| "Index must be int32".to_string())?;
let ind = ind.ok_or_else(||
DiagnosticEngine::single(DiagErr("Index must be int32".to_string(), slice.location))
)?;
let map = once((
ind.into(),
RecordField::new(ty, ctx == &ExprContext::Store, Some(value.location)),
@ -1112,18 +1313,23 @@ impl<'a> Inferencer<'a> {
.collect();
let seq = self.unifier.add_record(map);
self.constrain(value.custom.unwrap(), seq, &value.location)?;
Ok(ty)
Ok(DiagnosticsResult::err(ty, diagnostics))
}
_ => {
if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty(value.custom.unwrap())
{
return report_error("Tuple index must be a constant (KernelInvariant is also not supported)", slice.location)
diagnostics.insert(DiagErr(
"Tuple index must be a constant (KernelInvariant is also not supported)".into(),
slice.location,
));
return Err(diagnostics)
}
// the index is not a constant, so value can only be a list
self.constrain(slice.custom.unwrap(), self.primitives.int32, &slice.location)?;
let list = self.unifier.add_ty(TypeEnum::TList { ty });
self.constrain(value.custom.unwrap(), list, &value.location)?;
Ok(ty)
Ok(DiagnosticsResult::err(ty, diagnostics))
}
}
}
@ -1136,6 +1342,6 @@ impl<'a> Inferencer<'a> {
) -> InferenceResult {
self.constrain(test.custom.unwrap(), self.primitives.bool, &test.location)?;
self.constrain(body.custom.unwrap(), orelse.custom.unwrap(), &body.location)?;
Ok(body.custom.unwrap())
Ok(DiagnosticsResult::ok(body.custom.unwrap()))
}
}

View File

@ -43,8 +43,10 @@ impl SymbolResolver for Resolver {
unimplemented!()
}
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
self.id_to_def.get(&id).cloned().ok_or_else(|| "Unknown identifier".to_string())
fn get_identifier_def(&self, id: StrRef, loc: Option<Location>) -> ResultOrDiagnostics<DefinitionId> {
self.id_to_def.get(&id).cloned()
.map(|res| DiagnosticsResult::ok(res))
.ok_or_else(|| DiagnosticEngine::single(DiagErr("undefined identifier".into(), loc.unwrap())))
}
fn get_string_id(&self, _: &str) -> i32 {
@ -543,7 +545,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 DiagnosticsResult { engine: 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 +692,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 DiagnosticsResult { engine: 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(

View File

@ -1,15 +1,11 @@
use nac3core::{
codegen::CodeGenContext,
symbol_resolver::{SymbolResolver, SymbolValue, ValueEnum},
toplevel::{DefinitionId, TopLevelDef},
typecheck::{
use nac3core::{codegen::CodeGenContext, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, symbol_resolver::{SymbolResolver, SymbolValue, ValueEnum}, toplevel::{DefinitionId, TopLevelDef}, typecheck::{
type_inferencer::PrimitiveStore,
typedef::{Type, Unifier},
},
};
use nac3parser::ast::{self, StrRef};
}};
use nac3parser::ast::{self, Location, StrRef};
use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, sync::Arc};
use nac3core::Diagnostic::DiagErr;
pub struct ResolverInternal {
pub id_to_type: Mutex<HashMap<StrRef, Type>>,
@ -61,8 +57,10 @@ impl SymbolResolver for Resolver {
unimplemented!()
}
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
self.0.id_to_def.lock().get(&id).cloned().ok_or_else(|| "Undefined identifier".to_string())
fn get_identifier_def(&self, id: StrRef, loc: Option<Location>) -> ResultOrDiagnostics<DefinitionId> {
self.0.id_to_def.lock().get(&id).cloned()
.map(|res| DiagnosticsResult::ok(res))
.ok_or_else(|| DiagnosticEngine::single(DiagErr("undefined identifier".into(), loc.unwrap())))
}
fn get_string_id(&self, s: &str) -> i32 {

View File

@ -9,21 +9,16 @@ use inkwell::{
use parking_lot::{Mutex, RwLock};
use std::{borrow::Borrow, collections::HashMap, fs, path::Path, sync::Arc};
use nac3core::{
codegen::{
use nac3core::{codegen::{
concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions,
CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry,
},
symbol_resolver::SymbolResolver,
toplevel::{
}, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, symbol_resolver::SymbolResolver, toplevel::{
composer::TopLevelComposer, helper::parse_parameter_default_value, type_annotation::*,
TopLevelDef,
},
typecheck::{
}, typecheck::{
type_inferencer::PrimitiveStore,
typedef::{FunSignature, Type, Unifier},
},
};
}};
use nac3parser::{
ast::{Expr, ExprKind, StmtKind},
parser,
@ -31,6 +26,7 @@ use nac3parser::{
mod basic_symbol_resolver;
use basic_symbol_resolver::*;
use nac3core::Diagnostic::DiagErr;
/// Command-line argument parser definition.
#[derive(Parser)]
@ -73,13 +69,17 @@ fn handle_typevar_definition(
def_list: &[Arc<RwLock<TopLevelDef>>],
unifier: &mut Unifier,
primitives: &PrimitiveStore,
) -> Result<Type, String> {
) -> ResultOrDiagnostics<Type> {
let mut diagnostics = DiagnosticEngine::new();
if let ExprKind::Call { func, args, .. } = &var.node {
if matches!(&func.node, ExprKind::Name { id, .. } if id == &"TypeVar".into()) {
let constraints = args
.iter()
.skip(1)
.map(|x| -> Result<Type, String> {
.map(|x| {
let mut diagnostics = DiagnosticEngine::new();
let ty = parse_ast_to_type_annotation_kinds(
resolver,
def_list,
@ -88,23 +88,69 @@ fn handle_typevar_definition(
x,
Default::default(),
)?;
get_type_from_type_annotation_kinds(
def_list, unifier, primitives, &ty, &mut None
)
let (ty, diags) = ty.into();
diagnostics.merge_with(diags);
match get_type_from_type_annotation_kinds(
def_list,
unifier,
primitives,
&ty,
&mut None,
&Some(x.location),
) {
Ok(v) => {
let v: (Type, DiagnosticEngine) = v.into();
diagnostics.merge_with(v.1);
Ok(DiagnosticsResult::err(v.0, diagnostics))
},
Err(diags) => {
diagnostics.merge_with(diags);
Err(diagnostics)
}
}
})
.collect::<Result<Vec<_>, _>>()?;
Ok(unifier.get_fresh_var_with_range(&constraints, None, None).0)
.collect::<Vec<_>>();
diagnostics.extend(constraints.iter().cloned().flat_map(|res| match res {
Ok(v) => {
let (_, diags) = v.into();
diags
}
Err(diags) => diags
}.into_iter()));
if diagnostics.has_errors() {
return Err(diagnostics)
}
let constraints: Vec<Type> = constraints.into_iter()
.map(|res|
if let Ok(res) = res {
let (v, _) = res.into();
v
} else { unreachable!() }
)
.collect();
Ok(DiagnosticsResult::err(unifier.get_fresh_var_with_range(&constraints, None, None).0, diagnostics))
} else {
Err(format!(
Err(DiagnosticEngine::single(DiagErr(
format!(
"expression {:?} cannot be handled as a TypeVar in global scope",
var
))
),
var.location
)))
}
} else {
Err(format!(
Err(DiagnosticEngine::single(DiagErr(
format!(
"expression {:?} cannot be handled as a TypeVar in global scope",
var
))
),
var.location
)))
}
}
@ -116,7 +162,9 @@ fn handle_assignment_pattern(
def_list: &[Arc<RwLock<TopLevelDef>>],
unifier: &mut Unifier,
primitives: &PrimitiveStore,
) -> Result<(), String> {
) -> ResultOrDiagnostics<()> {
let mut diagnostics = DiagnosticEngine::new();
if targets.len() == 1 {
match &targets[0].node {
ExprKind::Name { id, .. } => {
@ -127,18 +175,23 @@ fn handle_assignment_pattern(
unifier,
primitives,
) {
let (var, diags) = var.into();
diagnostics.merge_with(diags);
internal_resolver.add_id_type(*id, var);
Ok(())
Ok(DiagnosticsResult::err((), diagnostics))
} else if let Ok(val) =
parse_parameter_default_value(value.borrow(), resolver)
{
let (val, diags) = val.into();
diagnostics.merge_with(diags);
internal_resolver.add_module_global(*id, val);
Ok(())
Ok(DiagnosticsResult::err((), diagnostics))
} else {
Err(format!("fails to evaluate this expression `{:?}` as a constant or TypeVar at {}",
targets[0].node,
Err(DiagnosticEngine::single(DiagErr(
format!("fails to evaluate this expression `{:?}` as a constant or TypeVar", targets[0].node),
targets[0].location,
))
)))
}
}
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
@ -151,23 +204,21 @@ fn handle_assignment_pattern(
unifier,
primitives,
)?;
Ok(())
Ok(DiagnosticsResult::ok(()))
}
_ => Err(format!(
"assignment to {:?} is not supported at {}",
targets[0], targets[0].location
)),
_ => Err(DiagnosticEngine::single(DiagErr(
format!("assignment to {:?} is not supported", targets[0]),
targets[0].location
))),
}
} else {
match &value.node {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
if elts.len() != targets.len() {
Err(format!(
"number of elements to unpack does not match (expect {}, found {}) at {}",
targets.len(),
elts.len(),
value.location
))
Err(DiagnosticEngine::single(DiagErr(
format!("number of elements to unpack does not match (expect {}, found {})", targets.len(), elts.len()),
value.location,
)))
} else {
for (tar, val) in targets.iter().zip(elts) {
handle_assignment_pattern(
@ -180,13 +231,13 @@ fn handle_assignment_pattern(
primitives,
)?;
}
Ok(())
Ok(DiagnosticsResult::ok(()))
}
}
_ => Err(format!(
"unpack of this expression is not supported at {}",
_ => Err(DiagnosticEngine::single(DiagErr(
"unpack of this expression is not supported".into(),
value.location
)),
))),
}
}
}
@ -272,7 +323,7 @@ fn main() {
unifier,
primitives,
) {
eprintln!("{}", err);
eprintln!("{}", err.as_string().join("\n----------\n"));
return;
}
},
@ -303,9 +354,12 @@ fn main() {
let instance = {
let defs = top_level.definitions.read();
let mut instance = defs[resolver
.get_identifier_def("run".into())
.unwrap_or_else(|_| panic!("cannot find run() entry point"))
.0]
.get_identifier_def("run".into(), None)
.map(|res| {
let (res, _) = res.into();
res.0
})
.unwrap_or_else(|_| panic!("cannot find run() entry point"))]
.write();
if let TopLevelDef::Function { instance_to_stmt, instance_to_symbol, .. } = &mut *instance {
instance_to_symbol.insert("".to_string(), "run".to_string());