diff --git a/Cargo.lock b/Cargo.lock index 879e142..9f47fd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/nac3artiq/Cargo.toml b/nac3artiq/Cargo.toml index 1b4788f..4994d90 100644 --- a/nac3artiq/Cargo.toml +++ b/nac3artiq/Cargo.toml @@ -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" diff --git a/nac3artiq/src/lib.rs b/nac3artiq/src/lib.rs index bef1e73..7981ed2 100644 --- a/nac3artiq/src/lib.rs +++ b/nac3artiq/src/lib.rs @@ -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("") { - return Err(CompileError::new_err(format!( + return if !e.iter().any(|err| err.message().contains("")) { + 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()); diff --git a/nac3artiq/src/symbol_resolver.rs b/nac3artiq/src/symbol_resolver.rs index 9953cba..f0c015b 100644 --- a/nac3artiq/src/symbol_resolver.rs +++ b/nac3artiq/src/symbol_resolver.rs @@ -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::{ - type_inferencer::PrimitiveStore, - typedef::{Type, TypeEnum, Unifier}, - }, -}; -use nac3parser::ast::{self, StrRef}; +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, 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 { + fn get_identifier_def(&self, id: StrRef, id_loc: Option) -> ResultOrDiagnostics { { 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)) }) } diff --git a/nac3core/Cargo.toml b/nac3core/Cargo.toml index 41415df..b22b286 100644 --- a/nac3core/Cargo.toml +++ b/nac3core/Cargo.toml @@ -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" diff --git a/nac3core/src/codegen/expr.rs b/nac3core/src/codegen/expr.rs index 052e483..429ee29 100644 --- a/nac3core/src/codegen/expr.rs +++ b/nac3core/src/codegen/expr.rs @@ -1,22 +1,17 @@ use std::{collections::HashMap, convert::TryInto, iter::once, iter::zip}; -use crate::{ - codegen::{ - concrete_type::{ConcreteFuncArg, ConcreteTypeEnum, ConcreteTypeStore}, - gen_in_range_check, - get_llvm_type, - get_llvm_abi_type, - irrt::*, - stmt::{gen_raise, gen_var}, - CodeGenContext, CodeGenTask, - }, - symbol_resolver::{SymbolValue, ValueEnum}, - toplevel::{DefinitionId, TopLevelDef}, - typecheck::{ - typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier}, - magic_methods::{binop_name, binop_assign_name}, - }, -}; +use crate::{codegen::{ + concrete_type::{ConcreteFuncArg, ConcreteTypeEnum, ConcreteTypeStore}, + gen_in_range_check, + get_llvm_type, + get_llvm_abi_type, + irrt::*, + stmt::{gen_raise, gen_var}, + CodeGenContext, CodeGenTask, +}, 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())); diff --git a/nac3core/src/codegen/test.rs b/nac3core/src/codegen/test.rs index 6daf50e..8e62541 100644 --- a/nac3core/src/codegen/test.rs +++ b/nac3core/src/codegen/test.rs @@ -1,17 +1,12 @@ -use crate::{ - codegen::{ - concrete_type::ConcreteTypeStore, CodeGenContext, CodeGenLLVMOptions, - CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry, - }, - symbol_resolver::{SymbolResolver, ValueEnum}, - toplevel::{ - composer::TopLevelComposer, DefinitionId, FunInstance, TopLevelContext, TopLevelDef, - }, - typecheck::{ - type_inferencer::{FunctionData, Inferencer, PrimitiveStore}, - typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier}, - }, -}; +use crate::{codegen::{ + concrete_type::ConcreteTypeStore, CodeGenContext, CodeGenLLVMOptions, + CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry, +}, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, symbol_resolver::{SymbolResolver, ValueEnum}, toplevel::{ + composer::TopLevelComposer, DefinitionId, FunInstance, TopLevelContext, TopLevelDef, +}, 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, @@ -63,12 +60,13 @@ impl SymbolResolver for Resolver { unimplemented!() } - fn get_identifier_def(&self, id: StrRef) -> Result { + fn get_identifier_def(&self, id: StrRef, loc: Option) -> ResultOrDiagnostics { 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::, _>>() .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)])), diff --git a/nac3core/src/lib.rs b/nac3core/src/lib.rs index f31dd35..3f9ac37 100644 --- a/nac3core/src/lib.rs +++ b/nac3core/src/lib.rs @@ -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) -> 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 = Result<(T, DiagnosticEngine), DiagnosticEngine>; +pub type ResultOrDiagnostics = Result, 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 { +#[derive(Clone)] +pub struct DiagnosticsResult { value: T, engine: DiagnosticEngine, } -impl Into> for DiagnosticsPartialResult { - fn into(self) -> DiagnosticsResult { +impl DiagnosticsResult { + pub fn ok(value: T) -> DiagnosticsResult { + DiagnosticsResult { + value, + engine: DiagnosticEngine::default(), + } + } + + pub fn err(value: T, engine: DiagnosticEngine) -> DiagnosticsResult { + DiagnosticsResult { + value, + engine + } + } +} + +impl Into<(T, DiagnosticEngine)> for DiagnosticsResult { + fn into(self) -> (T, DiagnosticEngine) { + (self.value, self.engine) + } +} + +impl Into> for DiagnosticsResult { + fn into(self) -> ResultOrDiagnostics { 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(&mut self, result: DiagnosticsPartialResult) -> T { + pub fn consume_partial_result(&mut self, result: DiagnosticsResult) -> T { self.merge_with(result.engine); result.value } - pub fn consume_result(&mut self, result: DiagnosticsResult) -> Result { + pub fn consume_result(&mut self, result: ResultOrDiagnostics) -> Result { 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(&mut self, result: DiagnosticsResult) -> Option { + pub fn consume_result_to_option(&mut self, result: ResultOrDiagnostics) -> Option { 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(self, value: T) -> DiagnosticsPartialResult { - DiagnosticsPartialResult { + pub fn into_partial_result(self, value: T) -> DiagnosticsResult { + DiagnosticsResult { value, engine: self } } - pub fn into_result(self, value: T) -> DiagnosticsResult { + pub fn into_result(self, value: T) -> ResultOrDiagnostics { + self.into_result_with(|| value) + } + + pub fn into_result_with(self, value: F) -> ResultOrDiagnostics + 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 { + 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")) + } } diff --git a/nac3core/src/symbol_resolver.rs b/nac3core/src/symbol_resolver.rs index cce05c4..8a7cf34 100644 --- a/nac3core/src/symbol_resolver.rs +++ b/nac3core/src/symbol_resolver.rs @@ -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; - // get the top-level definition of identifiers - fn get_identifier_def(&self, str: StrRef) -> Result; + /// 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 + ) -> ResultOrDiagnostics; 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( resolver: &dyn SymbolResolver, top_level_defs: &[Arc>], unifier: &mut Unifier, primitives: &PrimitiveStore, expr: &Expr, -) -> Result { +) -> ResultOrDiagnostics { 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( 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 { - obj_id, - fields, - params: Default::default(), - })) + 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( }; let subscript_name_handle = |id: &StrRef, slice: &Expr, 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::, _>>()?; - Ok(unifier.add_ty(TypeEnum::TTuple { ty })) + .collect::, _>>()? + .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( 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( 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( 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, - ) -> Result { + ) -> ResultOrDiagnostics { parse_type_annotation(self, top_level_defs, unifier, primitives, expr) } diff --git a/nac3core/src/toplevel/composer.rs b/nac3core/src/toplevel/composer.rs index 16f508a..fb764f9 100644 --- a/nac3core/src/toplevel/composer.rs +++ b/nac3core/src/toplevel/composer.rs @@ -3,8 +3,12 @@ use std::rc::Rc; use crate::{ codegen::{expr::get_subst_key, stmt::exn_constructor}, + Diagnostic::DiagErr, + DiagnosticEngine, + DiagnosticsResult, + ResultOrDiagnostics, symbol_resolver::SymbolValue, - typecheck::type_inferencer::{FunctionData, Inferencer}, + typecheck::type_inferencer::{FunctionData, Inferencer} }; use super::*; @@ -22,19 +26,19 @@ impl Default for ComposerConfig { type DefAst = (Arc>, Option>); pub struct TopLevelComposer { - // list of top level definitions, same as top level context + /// List of top level definitions, same as top level context pub definition_ast_list: Vec, - // start as a primitive unifier, will add more top_level defs inside + /// Start as a primitive unifier, will add more top_level defs inside pub unifier: Unifier, - // primitive store + /// Primitive store pub primitives_ty: PrimitiveStore, - // keyword list to prevent same user-defined name + /// Keyword list to prevent same user-defined name pub keyword_list: HashSet, - // to prevent duplicate definition + /// To prevent duplicate definition pub defined_names: HashSet, - // get the class def id of a class method + /// Get the class def id of a class method pub method_class: HashMap, - // number of built-in function and classes in the definition list, later skip + /// Number of built-in function and classes in the definition list, later skip pub builtin_num: usize, pub core_config: ComposerConfig, } @@ -227,8 +231,8 @@ impl TopLevelComposer { // TODO: Fix this hack. We will generate constructor for classes that inherit // from Exception class (directly or indirectly), but this code cannot handle // subclass of other exception classes. - let mut contains_constructor = bases - .iter().any(|base| matches!(base.node, ast::ExprKind::Name { id, .. } if id == exception_id)); + let mut contains_constructor = bases.iter() + .any(|base| matches!(base.node, ast::ExprKind::Name { id, .. } if id == exception_id)); for b in body { if let ast::StmtKind::FunctionDef { name: method_name, .. } = &b.node { if method_name == &init_id { @@ -348,25 +352,30 @@ impl TopLevelComposer { } } - pub fn start_analysis(&mut self, inference: bool) -> Result<(), String> { - self.analyze_top_level_class_type_var()?; - self.analyze_top_level_class_bases()?; - self.analyze_top_level_class_fields_methods()?; - self.analyze_top_level_function()?; + pub fn start_analysis(&mut self, inference: bool) -> ResultOrDiagnostics<()> { + let mut diagnostics = DiagnosticEngine::new(); + + diagnostics.consume_partial_result(self.analyze_top_level_class_type_var()); + diagnostics.consume_partial_result(self.analyze_top_level_class_bases()); + diagnostics.consume_partial_result(self.analyze_top_level_class_fields_methods()); + diagnostics.consume_partial_result(self.analyze_top_level_function()); if inference { - self.analyze_function_instance()?; + diagnostics.consume_partial_result(self.analyze_function_instance()); } - Ok(()) + + diagnostics.into_result(()) } /// step 1, analyze the type vars associated with top level class - fn analyze_top_level_class_type_var(&mut self) -> Result<(), String> { + fn analyze_top_level_class_type_var(&mut self) -> DiagnosticsResult<()> { let def_list = &self.definition_ast_list; let temp_def_list = self.extract_def_list(); let unifier = self.unifier.borrow_mut(); let primitives_store = &self.primitives_ty; let mut analyze = |class_def: &Arc>, class_ast: &Option| { + let mut diagnostics = DiagnosticEngine::new(); + // only deal with class def here let mut class_def = class_def.write(); let (class_bases_ast, class_def_type_vars, class_resolver) = { @@ -380,9 +389,10 @@ impl TopLevelComposer { unreachable!("must be both class") } } else { - return Ok(()); + return diagnostics.into_partial_result(()); } }; + let class_resolver = class_resolver.as_ref().unwrap(); let class_resolver = class_resolver.deref(); @@ -405,9 +415,9 @@ impl TopLevelComposer { if !is_generic { is_generic = true; } else { - return Err(format!( - "only single Generic[...] is allowed (at {})", - b.location + diagnostics.insert(DiagErr( + "only single Generic[...] is allowed".into(), + b.location, )); } @@ -431,7 +441,19 @@ impl TopLevelComposer { e, ) }) - .collect::, _>>()?; + .collect_vec(); + diagnostics.extend(type_vars.iter().cloned().flat_map(|res| match res { + Ok(DiagnosticsResult { engine: diags, .. }) => diags, + Err(diags) => diags, + }.into_iter())); + + if diagnostics.has_errors() { + continue + } + + let type_vars = type_vars.into_iter() + .map(|res| res.ok().unwrap().value) + .collect_vec(); // check if all are unique type vars let all_unique_type_var = { @@ -445,11 +467,13 @@ impl TopLevelComposer { } }) }; + if !all_unique_type_var { - return Err(format!( - "duplicate type variable occurs (at {})", - slice.location + diagnostics.insert(DiagErr( + "duplicate type variable occurs".into(), + slice.location, )); + continue } // add to TopLevelDef @@ -460,27 +484,26 @@ impl TopLevelComposer { _ => continue, } } - Ok(()) + + diagnostics.into_partial_result(()) }; - let mut errors = HashSet::new(); + + let mut diagnostics = DiagnosticEngine::new(); for (class_def, class_ast) in def_list.iter().skip(self.builtin_num) { if class_ast.is_none() { continue; } - if let Err(e) = analyze(class_def, class_ast) { - errors.insert(e); - } + + diagnostics.consume_partial_result(analyze(class_def, class_ast)); } - if !errors.is_empty() { - return Err(errors.into_iter().sorted().join("\n----------\n")); - } - Ok(()) + + diagnostics.into_partial_result(()) } /// step 2, base classes. /// now that the type vars of all classes are done, handle base classes and /// put Self class into the ancestors list. We only allow single inheritance - fn analyze_top_level_class_bases(&mut self) -> Result<(), String> { + fn analyze_top_level_class_bases(&mut self) -> DiagnosticsResult<()> { if self.unifier.top_level.is_none() { let ctx = Arc::new(self.make_top_level_context()); self.unifier.top_level = Some(ctx); @@ -492,6 +515,7 @@ impl TopLevelComposer { let mut get_direct_parents = |class_def: &Arc>, class_ast: &Option| { + let mut diagnostics = DiagnosticEngine::new(); let mut class_def = class_def.write(); let (class_def_id, class_bases, class_ancestors, class_resolver, class_type_vars) = { if let TopLevelDef::Class { @@ -508,9 +532,10 @@ impl TopLevelComposer { unreachable!("must be both class") } } else { - return Ok(()); + return diagnostics.into_result(()) } }; + let class_resolver = class_resolver.as_ref().unwrap(); let class_resolver = class_resolver.deref(); @@ -529,11 +554,12 @@ impl TopLevelComposer { } if has_base { - return Err(format!( + diagnostics.insert(DiagErr( "a class definition can only have at most one base class \ - declaration and one generic declaration (at {})", + declaration and one generic declaration".into(), b.location )); + continue } has_base = true; @@ -546,66 +572,76 @@ impl TopLevelComposer { &primitive_types, b, vec![(*class_def_id, class_type_vars.clone())].into_iter().collect(), - )?; + ); + let Some(base_ty) = diagnostics.consume_result_to_option(base_ty) else { continue }; if let TypeAnnotation::CustomClass { .. } = &base_ty { class_ancestors.push(base_ty); } else { - return Err(format!( - "class base declaration can only be custom class (at {})", + diagnostics.insert(DiagErr( + "class base declaration can only be custom class".into(), b.location, )); } } - Ok(()) + + diagnostics.into_result(()) }; // first, only push direct parent into the list - let mut errors = HashSet::new(); + let mut diagnostics = DiagnosticEngine::new(); for (class_def, class_ast) in self.definition_ast_list.iter_mut().skip(self.builtin_num) { if class_ast.is_none() { continue; } - if let Err(e) = get_direct_parents(class_def, class_ast) { - errors.insert(e); + if let Err(errs) = get_direct_parents(class_def, class_ast) { + diagnostics.merge_with(errs); } } - if !errors.is_empty() { - return Err(errors.into_iter().sorted().join("\n----------\n")); + + if diagnostics.has_errors() { + return diagnostics.into_partial_result(()) } // second, get all ancestors let mut ancestors_store: HashMap> = Default::default(); - let mut get_all_ancestors = |class_def: &Arc>| { + let mut get_all_ancestors = |class_def: &Arc>| -> ResultOrDiagnostics<()> { + let mut diagnostics = DiagnosticEngine::new(); let class_def = class_def.read(); - let (class_ancestors, class_id) = { - if let TopLevelDef::Class { ancestors, object_id, .. } = class_def.deref() { - (ancestors, *object_id) + let (class_ancestors, class_id, loc) = { + if let TopLevelDef::Class { ancestors, object_id, loc, .. } = class_def.deref() { + (ancestors, *object_id, loc) } else { - return Ok(()); + return diagnostics.into_result(()) } }; + ancestors_store.insert( class_id, // if class has direct parents, get all ancestors of its parents. Else just empty if class_ancestors.is_empty() { vec![] } else { - Self::get_all_ancestors_helper(&class_ancestors[0], temp_def_list.as_slice())? + let ancestors = Self::get_all_ancestors_helper(&class_ancestors[0], temp_def_list.as_slice(), unifier, loc); + let Some(ancestors) = diagnostics.consume_result_to_option(ancestors) else { return Err(diagnostics) }; + + ancestors }, ); - Ok(()) + + diagnostics.into_result(()) }; + for (class_def, ast) in self.definition_ast_list.iter().skip(self.builtin_num) { if ast.is_none() { continue; } - if let Err(e) = get_all_ancestors(class_def) { - errors.insert(e); - } + + diagnostics.consume_result_to_option(get_all_ancestors(class_def)); } - if !errors.is_empty() { - return Err(errors.into_iter().sorted().join("\n----------\n")); + + if !diagnostics.is_empty() { + return diagnostics.into_partial_result(()) } // insert the ancestors to the def list @@ -643,7 +679,11 @@ impl TopLevelComposer { stmt.node, ast::StmtKind::FunctionDef { .. } | ast::StmtKind::AnnAssign { .. } ) { - return Err("Classes inherited from exception should have no custom fields/methods".into()); + diagnostics.insert(DiagErr( + "Classes inherited from exception should have no custom fields/methods".into(), + stmt.location, + )); + return diagnostics.into_partial_result(()) } } } else { @@ -662,11 +702,11 @@ impl TopLevelComposer { unreachable!(); } - Ok(()) + diagnostics.into_partial_result(()) } /// step 3, class fields and methods - fn analyze_top_level_class_fields_methods(&mut self) -> Result<(), String> { + fn analyze_top_level_class_fields_methods(&mut self) -> DiagnosticsResult<()> { let temp_def_list = self.extract_def_list(); let primitives = &self.primitives_ty; let def_ast_list = &self.definition_ast_list; @@ -674,7 +714,7 @@ impl TopLevelComposer { let mut type_var_to_concrete_def: HashMap = HashMap::new(); - let mut errors = HashSet::new(); + let mut diagnostics = DiagnosticEngine::new(); for (class_def, class_ast) in def_ast_list.iter().skip(self.builtin_num) { if class_ast.is_none() { continue; @@ -689,12 +729,13 @@ impl TopLevelComposer { &mut type_var_to_concrete_def, (&self.keyword_list, &self.core_config), ) { - errors.insert(e); + diagnostics.merge_with(e); } } } - if !errors.is_empty() { - return Err(errors.into_iter().sorted().join("\n----------\n")); + + if diagnostics.has_errors() { + return diagnostics.into_partial_result(()); } // handle the inherited methods and fields @@ -709,19 +750,29 @@ impl TopLevelComposer { if class_ast.is_none() { continue; } + let mut class_def = class_def.write(); if let TopLevelDef::Class { ancestors, .. } = class_def.deref() { // if the length of the ancestor is equal to the current depth // it means that all the ancestors of the class is handled if ancestors.len() == current_ancestor_depth { finished = false; - Self::analyze_single_class_ancestors( + match Self::analyze_single_class_ancestors( class_def.deref_mut(), &temp_def_list, unifier, primitives, &mut type_var_to_concrete_def, - )?; + ) { + Ok(DiagnosticsResult { engine: diags, .. }) => { + diagnostics.merge_with(diags) + } + + Err(errs) => { + diagnostics.merge_with(errs); + return diagnostics.into_partial_result(()); + } + } } } } @@ -740,68 +791,95 @@ impl TopLevelComposer { let mut subst_list = Some(Vec::new()); // unification of previously assigned typevar let mut unification_helper = |ty, def| { - let target_ty = - get_type_from_type_annotation_kinds(&temp_def_list, unifier, primitives, &def, &mut subst_list)?; - unifier.unify(ty, target_ty).map_err(|e| e.to_display(unifier).to_string())?; - Ok(()) as Result<(), String> + let mut diagnostics = DiagnosticEngine::new(); + + let target_ty = get_type_from_type_annotation_kinds( + &temp_def_list, + unifier, + primitives, + &def, + &mut subst_list, + &None, + )?; + let target_ty = diagnostics.consume_partial_result(target_ty); + unifier.unify(ty, target_ty) + .map_err(|e| { + let loc = e.loc.unwrap(); + DiagnosticEngine::single(DiagErr(e.to_display(unifier).to_string(), loc)) + })?; + + Ok(DiagnosticsResult::err((), diagnostics)) }; + for (ty, def) in type_var_to_concrete_def { if let Err(e) = unification_helper(ty, def) { - errors.insert(e); + diagnostics.merge_with(e); } } + for ty in subst_list.unwrap().into_iter() { if let TypeEnum::TObj { obj_id, params, fields } = &*unifier.get_ty(ty) { let mut new_fields = HashMap::new(); let mut need_subst = false; + for (name, (ty, mutable)) in fields.iter() { let substituted = unifier.subst(*ty, params); need_subst |= substituted.is_some(); new_fields.insert(*name, (substituted.unwrap_or(*ty), *mutable)); } + if need_subst { let new_ty = unifier.add_ty(TypeEnum::TObj { obj_id: *obj_id, params: params.clone(), fields: new_fields, }); + if let Err(e) = unifier.unify(ty, new_ty) { - errors.insert(e.to_display(unifier).to_string()); + let loc = e.loc.unwrap(); + diagnostics.insert(DiagErr( + e.to_display(unifier).to_string(), + loc, + )); } } } else { unreachable!() } } - if !errors.is_empty() { - return Err(errors.into_iter().sorted().join("\n----------\n")); + + if !diagnostics.is_empty() { + return diagnostics.into_partial_result(()) } for (def, _) in def_ast_list.iter().skip(self.builtin_num) { match &*def.read() { - TopLevelDef::Class { resolver: Some(resolver), .. } - | TopLevelDef::Function { resolver: Some(resolver), .. } => { + TopLevelDef::Class { resolver: Some(resolver), loc, .. } + | TopLevelDef::Function { resolver: Some(resolver), loc, .. } => { if let Err(e) = resolver.handle_deferred_eval(unifier, &temp_def_list, primitives) { - errors.insert(e); + diagnostics.insert(DiagErr(e, loc.unwrap())); } } _ => {} } } - Ok(()) + diagnostics.into_partial_result(()) } /// step 4, after class methods are done, top level functions have nothing unknown - fn analyze_top_level_function(&mut self) -> Result<(), String> { + fn analyze_top_level_function(&mut self) -> DiagnosticsResult<()> { let def_list = &self.definition_ast_list; let keyword_list = &self.keyword_list; let temp_def_list = self.extract_def_list(); let unifier = self.unifier.borrow_mut(); let primitives_store = &self.primitives_ty; - let mut errors = HashSet::new(); + let mut diagnostics = DiagnosticEngine::new(); + let mut analyze = |function_def: &Arc>, function_ast: &Option| { + let mut diagnostics = DiagnosticEngine::new(); + let mut function_def = function_def.write(); let function_def = function_def.deref_mut(); let function_ast = if let Some(x) = function_ast.as_ref() { @@ -818,6 +896,7 @@ impl TopLevelComposer { // already have a function type, is class method, skip return Ok(()); } + if let ast::StmtKind::FunctionDef { args, returns, .. } = &function_ast.node { let resolver = resolver.as_ref(); let resolver = resolver.unwrap(); @@ -831,14 +910,18 @@ impl TopLevelComposer { if !defined_parameter_name.insert(x.node.arg) || keyword_list.contains(&x.node.arg) { - return Err(format!( + diagnostics.insert(DiagErr( "top level function must have unique parameter names \ - and names should not be the same as the keywords (at {})", + and names should not be the same as the keywords".into(), x.location )); } } + if diagnostics.has_errors() { + return Err(diagnostics) + } + let arg_with_default: Vec<( &ast::Located>, Option<&ast::Expr>, @@ -855,23 +938,23 @@ impl TopLevelComposer { ) .collect_vec(); - arg_with_default + match arg_with_default .iter() .rev() - .map(|(x, default)| -> Result { + .map(|(x, default)| -> ResultOrDiagnostics { + let mut diagnostics = DiagnosticEngine::new(); + let annotation = x .node .annotation .as_ref() - .ok_or_else(|| { - format!( - "function parameter `{}` needs type annotation at {}", - x.node.arg, x.location - ) - })? + .ok_or(DiagnosticEngine::single(DiagErr( + format!("function parameter `{}` needs type annotation", x.node.arg), + x.location, + )))? .as_ref(); - let type_annotation = parse_ast_to_type_annotation_kinds( + let type_annotation= parse_ast_to_type_annotation_kinds( resolver, temp_def_list.as_slice(), unifier, @@ -881,19 +964,22 @@ impl TopLevelComposer { // it should be fine to be empty map HashMap::new(), )?; + let type_annotation = diagnostics.consume_partial_result(type_annotation); let type_vars_within = get_type_var_contained_in_type_annotation(&type_annotation) .into_iter() - .map(|x| -> Result<(u32, Type), String> { - if let TypeAnnotation::TypeVar(ty) = x { - Ok((Self::get_var_id(ty, unifier)?, ty)) + .map(|type_anno| -> ResultOrDiagnostics<(u32, Type)> { + if let TypeAnnotation::TypeVar(ty) = type_anno { + let var_id = Self::get_var_id(ty, unifier) + .map_err(|err| DiagnosticEngine::single(DiagErr(err, x.location)))?; + Ok(DiagnosticsResult::ok((var_id, ty))) } else { unreachable!("must be type var annotation kind") } }) .collect::, _>>()?; - for (id, ty) in type_vars_within { + for DiagnosticsResult { value: (id, ty), .. } in type_vars_within { if let Some(prev_ty) = function_var_map.insert(id, ty) { // if already have the type inserted, make sure they are the same thing assert_eq!(prev_ty, ty); @@ -905,10 +991,12 @@ impl TopLevelComposer { unifier, primitives_store, &type_annotation, - &mut None + &mut None, + &Some(annotation.location), )?; + let ty = diagnostics.consume_partial_result(ty); - Ok(FuncArg { + Ok(DiagnosticsResult::err(FuncArg { name: x.node.arg, ty, default_value: match default { @@ -917,28 +1005,42 @@ impl TopLevelComposer { let v = Self::parse_parameter_default_value( default, resolver, )?; + let v = diagnostics.consume_partial_result(v); + Self::check_default_param_type( &v, &type_annotation, primitives_store, unifier, ) - .map_err( - |err| format!("{} (at {})", err, x.location), - )?; + .map_err(|err| + DiagnosticEngine::single(DiagErr(err, x.location)) + )?; v }), }, - }) + }, diagnostics)) }) - .collect::, _>>()? - }; + .collect::, _>>() { + // TODO + Ok(v) => { + diagnostics.extend(v.iter().cloned().flat_map(|res| res.engine.into_iter())); + + Ok(v.into_iter().map(|v| v.value).collect_vec()) + } + + Err(e) => { + diagnostics.merge_with(e); + return Err(diagnostics) + }, + } + }?; let return_ty = { if let Some(returns) = returns { let return_ty_annotation = { let return_annotation = returns.as_ref(); - parse_ast_to_type_annotation_kinds( + match parse_ast_to_type_annotation_kinds( resolver, &temp_def_list, unifier, @@ -947,20 +1049,39 @@ impl TopLevelComposer { // NOTE: since only class need this, for function // it should be fine to be empty map HashMap::new(), - )? + ) { + Ok(v) => diagnostics.consume_partial_result(v), + Err(e) => { + diagnostics.merge_with(e); + return Err(diagnostics) + }, + } }; - let type_vars_within = - get_type_var_contained_in_type_annotation(&return_ty_annotation) + let type_vars_within = match get_type_var_contained_in_type_annotation(&return_ty_annotation) .into_iter() - .map(|x| -> Result<(u32, Type), String> { - if let TypeAnnotation::TypeVar(ty) = x { - Ok((Self::get_var_id(ty, unifier)?, ty)) + .map(|type_anno| -> ResultOrDiagnostics<(u32, Type)> { + if let TypeAnnotation::TypeVar(ty) = type_anno { + let var_id = Self::get_var_id(ty, unifier) + .map_err(|err| DiagnosticEngine::single(DiagErr(err, returns.location)))?; + Ok(DiagnosticsResult::ok((var_id, ty))) } else { unreachable!("must be type var here") } }) - .collect::, _>>()?; + .collect::, _>>() { + Ok(v) => v, + Err(e) => { + diagnostics.merge_with(e); + return Err(diagnostics) + }, + }; + diagnostics.extend(type_vars_within.iter().cloned().flat_map(|diags| diags.engine.into_iter())); + + let type_vars_within = type_vars_within.into_iter() + .map(|v| v.value) + .collect_vec(); + for (id, ty) in type_vars_within { if let Some(prev_ty) = function_var_map.insert(id, ty) { // if already have the type inserted, make sure they are the same thing @@ -968,17 +1089,25 @@ impl TopLevelComposer { } } - get_type_from_type_annotation_kinds( + match get_type_from_type_annotation_kinds( &temp_def_list, unifier, primitives_store, &return_ty_annotation, - &mut None - )? + &mut None, + &Some(returns.location), + ) { + Ok(v) => diagnostics.consume_partial_result(v), + Err(e) => return { + diagnostics.merge_with(e); + Err(diagnostics) + }, + } } else { primitives_store.none } }; + var_id.extend_from_slice(function_var_map .iter() .filter_map(|(id, ty)| { @@ -991,35 +1120,45 @@ impl TopLevelComposer { .collect_vec() .as_slice() ); + let function_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature { args: arg_types, ret: return_ty, vars: function_var_map, })); - unifier.unify(*dummy_ty, function_ty).map_err(|e| { - e.at(Some(function_ast.location)).to_display(unifier).to_string() - })?; + + match unifier.unify(*dummy_ty, function_ty).map_err(|e| { + DiagnosticEngine::single(DiagErr( + e.at(Some(function_ast.location)).to_display(unifier).to_string(), + function_ast.location, + )) + }) { + Err(e) => { + diagnostics.merge_with(e); + Err(diagnostics) + } + _ => Ok(()) + } } else { unreachable!("must be both function"); } } else { // not top level function def, skip - return Ok(()); + Ok(()) } - Ok(()) }; + for (function_def, function_ast) in def_list.iter().skip(self.builtin_num) { if function_ast.is_none() { continue; } + if let Err(e) = analyze(function_def, function_ast) { - errors.insert(e); + diagnostics.merge_with(e); } } - if !errors.is_empty() { - return Err(errors.into_iter().sorted().join("\n----------\n")); - } - Ok(()) + + return diagnostics.into_partial_result(()) } fn analyze_single_class_methods_fields( @@ -1030,7 +1169,8 @@ impl TopLevelComposer { primitives: &PrimitiveStore, type_var_to_concrete_def: &mut HashMap, core_info: (&HashSet, &ComposerConfig), - ) -> Result<(), String> { + ) -> ResultOrDiagnostics<()> { + let mut diagnostics = DiagnosticEngine::new(); let (keyword_list, core_config) = core_info; let mut class_def = class_def.write(); let ( @@ -1061,6 +1201,7 @@ impl TopLevelComposer { } else { unreachable!("here must be toplevel class def"); }; + let class_resolver = class_resolver.as_ref().unwrap(); let class_resolver = class_resolver.as_ref(); @@ -1068,8 +1209,13 @@ impl TopLevelComposer { for b in class_body_ast { match &b.node { ast::StmtKind::FunctionDef { args, returns, name, .. } => { - let (method_dummy_ty, method_id) = - Self::get_class_method_def_info(class_methods_def, *name)?; + let (method_dummy_ty, method_id) = match Self::get_class_method_def_info(class_methods_def, *name, b.location) { + Ok(v) => diagnostics.consume_partial_result(v), + Err(e) => { + diagnostics.merge_with(e); + continue + } + }; let mut method_var_map: HashMap = HashMap::new(); @@ -1078,27 +1224,32 @@ impl TopLevelComposer { let mut defined_parameter_name: HashSet<_> = HashSet::new(); let zelf: StrRef = "self".into(); for x in args.args.iter() { - if !defined_parameter_name.insert(x.node.arg) - || (keyword_list.contains(&x.node.arg) && x.node.arg != zelf) - { - return Err(format!( - "top level function must have unique parameter names \ - and names should not be the same as the keywords (at {})", - x.location + if !defined_parameter_name.insert(x.node.arg) { + diagnostics.insert(DiagErr( + format!("top level function must have unique parameter names `{}`", x.node.arg), + x.location, + )); + } + + if keyword_list.contains(&x.node.arg) && x.node.arg != zelf { + diagnostics.insert(DiagErr( + format!("top level function parameter name `{}` must not be a keyword", x.node.arg), + x.location, )); } } if name == &"__init__".into() && !defined_parameter_name.contains(&zelf) { - return Err(format!( - "__init__ method must have a `self` parameter (at {})", - b.location + diagnostics.insert(DiagErr( + "__init__ method must have a `self` parameter".into(), + b.location, )); } + if !defined_parameter_name.contains(&zelf) { - return Err(format!( - "class method must have a `self` parameter (at {})", - b.location + diagnostics.insert(DiagErr( + "class method must have a `self` parameter".into(), + b.location, )); } @@ -1124,18 +1275,19 @@ impl TopLevelComposer { let name = x.node.arg; if name != zelf { let type_ann = { - let annotation_expr = x - .node - .annotation - .as_ref() - .ok_or_else(|| { - format!( - "type annotation needed for `{}` at {}", - x.node.arg, x.location - ) - })? - .as_ref(); - parse_ast_to_type_annotation_kinds( + let annotation_expr = match x.node.annotation.as_ref() { + Some(v) => v.as_ref(), + None => { + diagnostics.insert(DiagErr( + format!("type annotation needed for `{}`", x.node.arg), + x.location, + )); + + continue + }, + }; + + match parse_ast_to_type_annotation_kinds( class_resolver, temp_def_list, unifier, @@ -1144,15 +1296,26 @@ impl TopLevelComposer { vec![(class_id, class_type_vars_def.clone())] .into_iter() .collect(), - )? + ) { + Ok(v) => diagnostics.consume_partial_result(v), + Err(e) => { + diagnostics.merge_with(e); + return Err(diagnostics) + }, + } }; + // find type vars within this method parameter type annotation let type_vars_within = get_type_var_contained_in_type_annotation(&type_ann); // handle the class type var and the method type var for type_var_within in type_vars_within { if let TypeAnnotation::TypeVar(ty) = type_var_within { - let id = Self::get_var_id(ty, unifier)?; + let id = match Self::get_var_id(ty, unifier).map_err(|e| DiagnosticEngine::single(DiagErr(e, x.location))) { + Ok(v) => v, + Err(e) => return Err(e), + }; + if let Some(prev_ty) = method_var_map.insert(id, ty) { // if already in the list, make sure they are the same? assert_eq!(prev_ty, ty); @@ -1169,24 +1332,42 @@ impl TopLevelComposer { None => None, Some(default) => { if name == "self".into() { - return Err(format!("`self` parameter cannot take default value (at {})", x.location)); + diagnostics.insert(DiagErr( + "`self` parameter cannot take default value (at {})".into(), + x.location + )); + + continue + } + + let v = match Self::parse_parameter_default_value( + default, + class_resolver, + ) { + Ok(v) => diagnostics.consume_partial_result(v), + Err(e) => { + diagnostics.merge_with(e); + continue + } + }; + + match Self::check_default_param_type( + &v, &type_ann, primitives, unifier, + ) { + Ok(_) => Some(v), + Err(e) => { + diagnostics.insert(DiagErr( + e, + x.location, + )); + + continue + } } - Some({ - let v = Self::parse_parameter_default_value( - default, - class_resolver, - )?; - Self::check_default_param_type( - &v, &type_ann, primitives, unifier, - ) - .map_err(|err| { - format!("{} (at {})", err, x.location) - })?; - v - }) } }, }; + // push the dummy type and the type annotation // into the list for later unification type_var_to_concrete_def @@ -1194,27 +1375,46 @@ impl TopLevelComposer { result.push(dummy_func_arg) } } + result }; let ret_type = { if let Some(result) = returns { let result = result.as_ref(); - let annotation = parse_ast_to_type_annotation_kinds( + let annotation = match parse_ast_to_type_annotation_kinds( class_resolver, temp_def_list, unifier, primitives, result, vec![(class_id, class_type_vars_def.clone())].into_iter().collect(), - )?; + ) { + Ok(v) => diagnostics.consume_partial_result(v), + Err(e) => { + diagnostics.merge_with(e); + continue + } + }; + // find type vars within this return type annotation let type_vars_within = get_type_var_contained_in_type_annotation(&annotation); + // handle the class type var and the method type var for type_var_within in type_vars_within { if let TypeAnnotation::TypeVar(ty) = type_var_within { - let id = Self::get_var_id(ty, unifier)?; + let id = match Self::get_var_id(ty, unifier) { + Ok(v) => v, + Err(e) => { + diagnostics.insert(DiagErr( + e, + result.location + )); + return Err(diagnostics) + } + }; + if let Some(prev_ty) = method_var_map.insert(id, ty) { // if already in the list, make sure they are the same? assert_eq!(prev_ty, ty); @@ -1264,10 +1464,18 @@ impl TopLevelComposer { // unify now since function type is not in type annotation define // which should be fine since type within method_type will be subst later - unifier - .unify(method_dummy_ty, method_type) - .map_err(|e| e.to_display(unifier).to_string())?; + match unifier.unify(method_dummy_ty, method_type) { + Ok(()) => (), + Err(e) => { + diagnostics.insert(DiagErr( + e.to_display(unifier).to_string(), + returns.as_deref().unwrap().location, + )); + continue + } + } } + ast::StmtKind::AnnAssign { target, annotation, value: None, .. } => { if let ast::ExprKind::Name { id: attr, .. } = &target.node { if defined_fields.insert(attr.to_string()) { @@ -1296,26 +1504,35 @@ impl TopLevelComposer { }; class_fields_def.push((*attr, dummy_field_type, mutable)); - let parsed_annotation = parse_ast_to_type_annotation_kinds( + let parsed_annotation = match parse_ast_to_type_annotation_kinds( class_resolver, temp_def_list, unifier, primitives, annotation.as_ref(), vec![(class_id, class_type_vars_def.clone())].into_iter().collect(), - )?; + ) { + Ok(v) => diagnostics.consume_partial_result(v), + Err(e) => { + diagnostics.merge_with(e); + continue + } + }; + // find type vars within this return type annotation let type_vars_within = get_type_var_contained_in_type_annotation(&parsed_annotation); + // handle the class type var and the method type var for type_var_within in type_vars_within { if let TypeAnnotation::TypeVar(t) = type_var_within { if !class_type_vars_def.contains(&t) { - return Err(format!( + diagnostics.insert(DiagErr( "class fields can only use type \ - vars over which the class is generic (at {})", + vars over which the class is generic".into(), annotation.location )); + continue } } else { unreachable!("must be type var annotation"); @@ -1323,30 +1540,33 @@ impl TopLevelComposer { } type_var_to_concrete_def.insert(dummy_field_type, parsed_annotation); } else { - return Err(format!( - "same class fields `{}` defined twice (at {})", - attr, target.location + diagnostics.insert(DiagErr( + format!("same class fields `{}` defined twice", attr), + target.location )); } } else { - return Err(format!( - "unsupported statement type in class definition body (at {})", + diagnostics.insert(DiagErr( + "unsupported statement type in class definition body".into(), target.location )); } } + ast::StmtKind::Assign { .. } => {}, // we don't class attributes ast::StmtKind::Pass { .. } => {} ast::StmtKind::Expr { value: _, .. } => {} // typically a docstring; ignoring all expressions matches CPython behavior + _ => { - return Err(format!( - "unsupported statement in class definition body (at {})", + diagnostics.insert(DiagErr( + "unsupported statement in class definition body".into(), b.location - )) + )); } } } - Ok(()) + + diagnostics.into_result(()) } fn analyze_single_class_ancestors( @@ -1355,7 +1575,9 @@ impl TopLevelComposer { unifier: &mut Unifier, _primitives: &PrimitiveStore, type_var_to_concrete_def: &mut HashMap, - ) -> Result<(), String> { + ) -> ResultOrDiagnostics<()> { + let mut diagnostics = DiagnosticEngine::new(); + let ( _class_id, class_ancestor_def, @@ -1363,6 +1585,7 @@ impl TopLevelComposer { class_methods_def, _class_type_vars_def, _class_resolver, + loc, ) = if let TopLevelDef::Class { object_id, ancestors, @@ -1370,10 +1593,11 @@ impl TopLevelComposer { methods, resolver, type_vars, + loc, .. } = class_def { - (*object_id, ancestors, fields, methods, type_vars, resolver) + (*object_id, ancestors, fields, methods, type_vars, resolver, loc) } else { unreachable!("here must be class def ast"); }; @@ -1406,10 +1630,15 @@ impl TopLevelComposer { type_var_to_concrete_def, ); if !ok { - return Err(format!( - "method {} has same name as ancestors' method, but incompatible type", - class_method_name + diagnostics.insert(DiagErr( + format!( + "method {} has same name as ancestors' method, but incompatible type", + class_method_name + ), + loc.unwrap(), /* TODO */ )); + + return Err(diagnostics) } // mark it as added is_override.insert(*class_method_name); @@ -1444,10 +1673,15 @@ impl TopLevelComposer { // find if there is a fields with the same name in the child class for (class_field_name, ..) in class_fields_def.iter() { if class_field_name == anc_field_name { - return Err(format!( - "field `{}` has already declared in the ancestor classes", - class_field_name + diagnostics.insert(DiagErr( + format!( + "field `{}` has already declared in the ancestor classes", + class_field_name + ), + loc.unwrap(), )); + + return Err(diagnostics) } } new_child_fields.push(to_be_added); @@ -1466,11 +1700,11 @@ impl TopLevelComposer { unreachable!("must be class type annotation") } - Ok(()) + Ok(DiagnosticsResult::ok(())) } /// step 5, analyze and call type inferencer to fill the `instance_to_stmt` of topleveldef::function - fn analyze_function_instance(&mut self) -> Result<(), String> { + fn analyze_function_instance(&mut self) -> DiagnosticsResult<()> { // first get the class constructor type correct for the following type check in function body // also do class field instantiation check let init_str_id = "__init__".into(); @@ -1508,8 +1742,10 @@ impl TopLevelComposer { } } - let mut errors = HashSet::new(); + let mut diagnostics = DiagnosticEngine::new(); let mut analyze = |i, def: &Arc>, ast: &Option| { + let mut diagnostics = DiagnosticEngine::new(); + let class_def = def.read(); if let TopLevelDef::Class { constructor, @@ -1520,16 +1756,24 @@ impl TopLevelComposer { name: class_name, object_id, resolver: _, + loc, .. - } = &*class_def - { + } = &*class_def { let self_type = get_type_from_type_annotation_kinds( &def_list, unifier, primitives_ty, &make_self_type_annotation(type_vars, *object_id), - &mut None - )?; + &mut None, + loc, + ); + let self_type = diagnostics.consume_result_to_option(self_type); + + if diagnostics.has_errors() { + return Err(diagnostics) + } + + let self_type = self_type.unwrap(); if ancestors .iter() .any(|ann| matches!(ann, TypeAnnotation::CustomClass { id, .. } if id.0 == 7)) @@ -1576,11 +1820,19 @@ impl TopLevelComposer { }; constructors.push((i, signature, definition_extension.len())); definition_extension.push((Arc::new(RwLock::new(cons_fun)), None)); - unifier.unify(constructor.unwrap(), signature).map_err(|e| { - e.at(Some(ast.as_ref().unwrap().location)).to_display(unifier).to_string() - })?; - return Ok(()); + + diagnostics.consume_result( + unifier.unify(constructor.unwrap(), signature) + .map(|_| DiagnosticsResult::ok(())) + .map_err(|e| DiagnosticEngine::single(DiagErr( + e.to_display(unifier).to_string(), + ast.as_ref().unwrap().location + ))) + )?; + + return diagnostics.into_result(()) } + let mut init_id: Option = None; // get the class constructor type correct let (contor_args, contor_type_vars) = { @@ -1606,9 +1858,14 @@ impl TopLevelComposer { ret: self_type, vars: contor_type_vars, })); - unifier.unify(constructor.unwrap(), contor_type).map_err(|e| { - e.at(Some(ast.as_ref().unwrap().location)).to_display(unifier).to_string() - })?; + diagnostics.consume_result( + unifier.unify(constructor.unwrap(), contor_type) + .map(|_| DiagnosticsResult::ok(())) + .map_err(|e| DiagnosticEngine::single(DiagErr( + e.to_display(unifier).to_string(), + ast.as_ref().unwrap().location + ))) + )?; // class field instantiation check if let (Some(init_id), false) = (init_id, fields.is_empty()) { @@ -1617,32 +1874,36 @@ impl TopLevelComposer { if *name != init_str_id { unreachable!("must be init function here") } + let all_inited = Self::get_all_assigned_field(body.as_slice())?; + let all_inited = diagnostics.consume_partial_result(all_inited); + for (f, _, _) in fields { if !all_inited.contains(f) { - return Err(format!( - "fields `{}` of class `{}` not fully initialized in the initializer (at {})", - f, - class_name, + diagnostics.insert(DiagErr( + format!("fields `{}` of class `{}` not fully initialized in the initializer", f, class_name), body[0].location, )); + return Err(diagnostics) } } } } } - Ok(()) + + diagnostics.into_result(()) }; + for (i, (def, ast)) in definition_ast_list.iter().enumerate().skip(self.builtin_num) { if ast.is_none() { continue; } - if let Err(e) = analyze(i, def, ast) { - errors.insert(e); - } + + diagnostics.consume_result_to_option(analyze(i, def, ast)); } - if !errors.is_empty() { - return Err(errors.into_iter().sorted().join("\n---------\n")); + + if !diagnostics.is_empty() { + return diagnostics.into_partial_result(()); } for (i, signature, id) in constructors.into_iter() { @@ -1667,9 +1928,12 @@ impl TopLevelComposer { let unifier = &mut self.unifier; let method_class = &mut self.method_class; let mut analyze_2 = |id, def: &Arc>, ast: &Option| { + let mut diagnostics = DiagnosticEngine::new(); + if ast.is_none() { - return Ok(()); + return DiagnosticsResult::ok(()); } + let mut function_def = def.write(); if let TopLevelDef::Function { instance_to_stmt, @@ -1690,15 +1954,19 @@ impl TopLevelComposer { if let Some(class_id) = method_class.get(&DefinitionId(id)) { let class_def = definition_ast_list.get(class_id.0).unwrap(); let class_def = class_def.0.read(); - if let TopLevelDef::Class { type_vars, .. } = &*class_def { + if let TopLevelDef::Class { type_vars, loc, .. } = &*class_def { let ty_ann = make_self_type_annotation(type_vars, *class_id); let self_ty = get_type_from_type_annotation_kinds( &def_list, unifier, primitives_ty, &ty_ann, - &mut None - )?; + &mut None, + loc, + ); + let Some(self_ty) = diagnostics.consume_result_to_option(self_ty) + else { return DiagnosticsResult::err((), diagnostics) }; + vars.extend(type_vars.iter().map(|ty| if let TypeEnum::TVar { id, .. } = &*unifier.get_ty(*ty) { (*id, *ty) @@ -1781,6 +2049,7 @@ impl TopLevelComposer { }) .collect::>() }; + unifier.subst(self_type, &subst_for_self).unwrap_or(self_type) }) }; @@ -1845,10 +2114,27 @@ impl TopLevelComposer { } .into_iter() .map(|b| inferencer.fold_stmt(b)) - .collect::, _>>()?; + .collect_vec(); + diagnostics.extend(fun_body.iter().cloned().flat_map(|res| match res { + Ok(_) => HashSet::new().into_iter(), + Err(diags) => diags.into_iter(), + })); + + if diagnostics.has_errors() { + return DiagnosticsResult::err((), diagnostics) + } + + let fun_body = fun_body.into_iter() + .map(|res| res.ok().unwrap()) + .collect_vec(); + + let DiagnosticsResult { value: returned, engine: errs } = + inferencer.check_block(fun_body.as_slice(), &mut identifiers); + diagnostics.merge_with(errs); + if !diagnostics.has_errors() { + return DiagnosticsResult::err((), diagnostics) + } - let returned = - inferencer.check_block(fun_body.as_slice(), &mut identifiers)?; { // check virtuals let defs = ctx.definitions.read(); @@ -1858,10 +2144,11 @@ impl TopLevelComposer { if let TypeEnum::TObj { obj_id, .. } = &*base { *obj_id } else { - return Err(format!( - "Base type should be a class (at {})", - loc + diagnostics.insert(DiagErr( + "base type should be a class".into(), + *loc )); + return DiagnosticsResult::err((), diagnostics) } }; let subtype_id = { @@ -1871,10 +2158,12 @@ impl TopLevelComposer { } else { let base_repr = inferencer.unifier.stringify(*base); let subtype_repr = inferencer.unifier.stringify(*subtype); - return Err(format!( - "Expected a subtype of {}, but got {} (at {})", - base_repr, subtype_repr, loc + + diagnostics.insert(DiagErr( + format!("expected a subtype of {}, but got {}", base_repr, subtype_repr), + *loc, )); + return DiagnosticsResult::err((), diagnostics) } }; let subtype_entry = defs[subtype_id.0].read(); @@ -1884,10 +2173,12 @@ impl TopLevelComposer { if m.is_none() { let base_repr = inferencer.unifier.stringify(*base); let subtype_repr = inferencer.unifier.stringify(*subtype); - return Err(format!( - "Expected a subtype of {}, but got {} (at {})", - base_repr, subtype_repr, loc + + diagnostics.insert(DiagErr( + format!("expected a subtype of {}, but got {}", base_repr, subtype_repr), + *loc, )); + return DiagnosticsResult::err((), diagnostics) } } else { unreachable!(); @@ -1910,12 +2201,12 @@ impl TopLevelComposer { &mut |id| format!("typevar{}", id), &mut None, ); - return Err(format!( - "expected return type of `{}` in function `{}` (at {})", - ret_str, - name, - ast.as_ref().unwrap().location + + diagnostics.insert(DiagErr( + format!("expected return type of `{}` in function `{}`", ret_str, name), + ast.as_ref().unwrap().location, )); + return DiagnosticsResult::err((), diagnostics) } instance_to_stmt.insert( @@ -1932,19 +2223,18 @@ impl TopLevelComposer { unreachable!("must be typeenum::tfunc") } } - Ok(()) + + DiagnosticsResult::err((), diagnostics) }; + for (id, (def, ast)) in self.definition_ast_list.iter().enumerate().skip(self.builtin_num) { if ast.is_none() { continue; } - if let Err(e) = analyze_2(id, def, ast) { - errors.insert(e); - } + + diagnostics.consume_partial_result(analyze_2(id, def, ast)); } - if !errors.is_empty() { - return Err(errors.into_iter().sorted().join("\n----------\n")); - } - Ok(()) + + diagnostics.into_partial_result(()) } } diff --git a/nac3core/src/toplevel/helper.rs b/nac3core/src/toplevel/helper.rs index 73eebab..774e2b5 100644 --- a/nac3core/src/toplevel/helper.rs +++ b/nac3core/src/toplevel/helper.rs @@ -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>], - ) -> Result, String> { + unifier: &mut Unifier, + loc: &Option, + ) -> ResultOrDiagnostics> { + let mut diagnostics = DiagnosticEngine::new(); let mut result: Vec = 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, String> { + pub fn get_all_assigned_field(stmts: &[ast::Stmt<()>]) -> ResultOrDiagnostics> { 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::>(); 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::>(); 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 { + ) -> ResultOrDiagnostics { 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 { - fn handle_constant(val: &Constant, loc: &Location) -> Result { +) -> ResultOrDiagnostics { + fn handle_constant(val: &Constant, loc: &Location) -> ResultOrDiagnostics { 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::, _>>()?, - )), - 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::, _>>()?; + 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 = (*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 = (*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 = (*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 - .iter() - .map(|x| parse_parameter_default_value(x, resolver)) - .collect::, _>>()? - )), - 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 - ) - ) + ast::ExprKind::Tuple { elts, .. } => { + let param_def_values = elts + .iter() + .map(|x| parse_parameter_default_value(x, resolver)) + .collect::, _>>()?; + let diagnostics = DiagnosticEngine::from( + param_def_values.iter().cloned() + .flat_map(|res| res.engine.into_iter()) + .collect::>() + ); + let param_def_values = param_def_values.into_iter() + .map(|res| res.value) + .collect_vec(); + + Ok(DiagnosticsResult::err( + SymbolValue::Tuple(param_def_values), + diagnostics, + )) } - _ => Err(format!( - "unsupported default parameter (not primitive type, option or tuple) at {}", - default.location - )) + + 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, + ))) } } diff --git a/nac3core/src/toplevel/test.rs b/nac3core/src/toplevel/test.rs index 4940071..ef0a3e0 100644 --- a/nac3core/src/toplevel/test.rs +++ b/nac3core/src/toplevel/test.rs @@ -1,17 +1,13 @@ -use crate::{ - codegen::CodeGenContext, - symbol_resolver::{SymbolResolver, ValueEnum}, - toplevel::DefinitionId, - typecheck::{ - type_inferencer::PrimitiveStore, - typedef::{Type, Unifier}, - }, -}; +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 { - 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) -> ResultOrDiagnostics { + 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 diff --git a/nac3core/src/toplevel/type_annotation.rs b/nac3core/src/toplevel/type_annotation.rs index 116a6b4..0536742 100644 --- a/nac3core/src/toplevel/type_annotation.rs +++ b/nac3core/src/toplevel/type_annotation.rs @@ -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( expr: &ast::Expr, // the key stores the type_var of this topleveldef::class, we only need this field here locked: HashMap>, -) -> Result { +) -> ResultOrDiagnostics { + let mut diagnostics = DiagnosticEngine::new(); + let name_handle = |id: &StrRef, + id_loc: Location, unifier: &mut Unifier, locked: HashMap>| { + 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, unifier: &mut Unifier, mut locked: HashMap>| { + 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( 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( } else { vec![slice] }; + if type_vars.len() != params_ast.len() { - return Err(format!( - "expect {} type parameters but got {} (at {})", - type_vars.len(), - params_ast.len(), + 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( }, ) }) - .collect::, _>>()?; + .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( 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( 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( 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( 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( locked.clone(), ) }) - .collect::, _>>()?; - 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> -) -> Result { + subst_list: &mut Option>, + loc: &Option, +) -> ResultOrDiagnostics { + 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 {}", - type_vars.len(), - params.len() - )) + diagnostics.insert(DiagErr( + format!( + "expected {} type parameters for class `{}` but got {}", + type_vars.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::, _>>()?; + 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,16 +444,20 @@ pub fn get_type_from_type_annotation_kinds( if ok { result.insert(*id, p); } else { - return Err(format!( - "cannot apply type {} to type variable with id {:?}", - unifier.internal_stringify( - p, - &mut |id| format!("class{}", id), - &mut |id| format!("typevar{}", id), - &mut None + diagnostics.insert(DiagErr( + format!( + "cannot apply type {} to type variable with id {:?}", + unifier.internal_stringify( + p, + &mut |id| format!("class{}", id), + &mut |id| format!("typevar{}", id), + &mut None + ), + *id ), - *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::, _>>()?; - 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)) } } } diff --git a/nac3core/src/typecheck/function_check.rs b/nac3core/src/typecheck/function_check.rs index 7ed0735..562dc5a 100644 --- a/nac3core/src/typecheck/function_check.rs +++ b/nac3core/src/typecheck/function_check.rs @@ -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>) -> Result<(), String> { + fn should_have_value(&mut self, expr: &Expr>) -> 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,100 +22,150 @@ impl<'a> Inferencer<'a> { &mut self, pattern: &Expr>, defined_identifiers: &mut HashSet, - ) -> 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); + } + if let Err(e) = self.should_have_value(elt) { + diagnostics.merge_with(e); + } } - Ok(()) } + ExprKind::Subscript { value, slice, .. } => { - self.check_expr(value, defined_identifiers)?; - self.should_have_value(value)?; - self.check_expr(slice, defined_identifiers)?; + if let Err(e) = self.check_expr(value, defined_identifiers) { + 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, + )); + } + + _ => { + if let Err(e) = self.check_expr(pattern, defined_identifiers) { + diagnostics.merge_with(e); + } } - _ => self.check_expr(pattern, defined_identifiers), } + + diagnostics.into_result(()) } fn check_expr( &mut self, expr: &Expr>, defined_identifiers: &mut HashSet, - ) -> 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(()); - } - self.should_have_value(expr)?; - if !defined_identifiers.contains(id) { - match self.function_data.resolver.get_symbol_type( - self.unifier, - &self.top_level.definitions.read(), - self.primitives, - *id, - ) { - Ok(_) => { - self.defined_identifiers.insert(*id); - } - Err(e) => { - return Err(format!( - "type error at identifier `{}` ({}) at {}", - id, e, expr.location - )); + if id != &"none".into() { + if let Err(e) = self.should_have_value(expr) { + diagnostics.merge_with(e); + } + + if !defined_identifiers.contains(id) { + match self.function_data.resolver.get_symbol_type( + self.unifier, + &self.top_level.definitions.read(), + self.primitives, + *id, + ) { + Ok(_) => { + self.defined_identifiers.insert(*id); + } + + Err(e) => { + 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 } => { - self.check_expr(left, defined_identifiers)?; - self.check_expr(right, defined_identifiers)?; - self.should_have_value(left)?; - self.should_have_value(right)?; + + 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); + } // 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>, defined_identifiers: &mut HashSet, - ) -> Result { - match &stmt.node { - StmtKind::For { target, iter, body, orelse, .. } => { - self.check_expr(iter, defined_identifiers)?; - self.should_have_value(iter)?; + ) -> DiagnosticsResult { + 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)?; - for stmt in body.iter() { - self.check_stmt(stmt, &mut local_defined_identifiers)?; + if let Err(errs) = self.check_pattern(target, &mut local_defined_identifiers) { + diagnostics.merge_with(errs); } - Ok(false) + if let Err(e) = self.should_have_value(target) { + diagnostics.merge_with(e); + } + + for stmt in body.iter() { + let DiagnosticsResult { engine: errs, .. } = self.check_stmt(stmt, &mut local_defined_identifiers); + diagnostics.merge_with(errs); + } + + false } + StmtKind::If { test, body, orelse, .. } => { - self.check_expr(test, defined_identifiers)?; - self.should_have_value(test)?; + if let Err(errs) = self.check_expr(test, defined_identifiers) { + 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)?; + 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(); - self.check_block(body, &mut defined_identifiers)?; - self.check_block(orelse, &mut defined_identifiers)?; - Ok(false) + 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) - } - StmtKind::Assign { targets, value, .. } => { - self.check_expr(value, defined_identifiers)?; - self.should_have_value(value)?; - for target in targets { - self.check_pattern(target, defined_identifiers)?; + if let Err(e) = self.check_expr(value, defined_identifiers) { + diagnostics.merge_with(e); } - Ok(false) + + false } + + StmtKind::Assign { targets, 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 { + if let Err(errs) = self.check_pattern(target, defined_identifiers) { + diagnostics.merge_with(errs); + } + } + + false + } + StmtKind::AnnAssign { target, value, .. } => { if let Some(value) = value { - self.check_expr(value, defined_identifiers)?; - self.should_have_value(value)?; - self.check_pattern(target, defined_identifiers)?; + if let Err(errs) = self.check_expr(value, defined_identifiers) { + diagnostics.merge_with(errs); + } + 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); + } } - Ok(false) + + false } + StmtKind::Return { value, .. } => { if let Some(value) = value { - self.check_expr(value, defined_identifiers)?; - self.should_have_value(value)?; + if let Err(errs) = self.check_expr(value, defined_identifiers) { + diagnostics.merge_with(errs); + } + if let Err(e) = self.should_have_value(value) { + diagnostics.merge_with(e); + } } - Ok(true) + + true } + StmtKind::Raise { exc, .. } => { if let Some(value) = exc { - self.check_expr(value, defined_identifiers)?; + if let Err(errs) = self.check_expr(value, defined_identifiers) { + 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>], defined_identifiers: &mut HashSet, - ) -> Result { + ) -> DiagnosticsResult { + let mut diagnostics = DiagnosticEngine::new(); let mut ret = false; for stmt in block { if ret { - println!("warning: dead code at {:?}\n", stmt.location) - } - if self.check_stmt(stmt, defined_identifiers)? { - ret = true; + diagnostics.insert(DiagWarn( + "unreachable statement".into(), + stmt.location, + )); } + + 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) } } diff --git a/nac3core/src/typecheck/type_inferencer/mod.rs b/nac3core/src/typecheck/type_inferencer/mod.rs index c72e199..9c8f10a 100644 --- a/nac3core/src/typecheck/type_inferencer/mod.rs +++ b/nac3core/src/typecheck/type_inferencer/mod.rs @@ -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 Error = String; + type Error = DiagnosticEngine; fn map_user(&mut self, _: ()) -> Result { Ok(None) } } -fn report_error(msg: &str, location: Location) -> Result { - Err(format!("{} at {}", msg, location)) -} - impl<'a> fold::Fold<()> for Inferencer<'a> { type TargetU = Option; - type Error = String; + type Error = DiagnosticEngine; fn map_user(&mut self, _: ()) -> Result { Ok(None) @@ -86,6 +82,8 @@ impl<'a> fold::Fold<()> for Inferencer<'a> { &mut self, mut node: ast::Stmt<()>, ) -> Result, 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( - top_level_defs.as_slice(), - self.unifier, - self.primitives, - annotation.as_ref(), - )?; + 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( - top_level_defs.as_slice(), - self.unifier, - self.primitives, - &type_, - )?; + 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, 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 InferenceResult = ResultOrDiagnostics; 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>, String> { + ) -> ResultOrDiagnostics>> { + 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, - ) -> Result>, String> { + ) -> ResultOrDiagnostics>> { + 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,112 +870,140 @@ impl<'a> Inferencer<'a> { func: ast::Expr<()>, mut args: Vec>, keywords: Vec>, - ) -> Result>, String> { - 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", - 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( - top_level_defs.as_slice(), - self.unifier, - self.primitives, - &arg, - )? - } 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 { - location, - custom, - node: ExprKind::Call { - func: Box::new(Located { - custom: None, - location: func.location, - node: ExprKind::Name { id, ctx }, - }), - args: vec![arg0], - keywords: vec![], - }, - }); + ) -> ResultOrDiagnostics>> { + 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() { + diagnostics.insert(DiagErr( + "`virtual` can only accept 1/2 positional arguments".into(), + func_location, + )); } - // 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 } = - &args[0].node - { - let custom = Some(self.primitives.int64); - let v: Result = (*val).try_into(); - if v.is_ok() { - return 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) - } - } - } - 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 = (*val).try_into(); - if v.is_ok() { - return 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) - } - } - } - if id == "uint64".into() && args.len() == 1 { - if let ExprKind::Constant { value: ast::Constant::Int(val), kind } = + + 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(); + 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(DiagnosticsResult::err(Located { + location, + custom, + node: ExprKind::Call { + func: Box::new(Located { + custom: None, + location: func.location, + node: ExprKind::Name { id, ctx }, + }), + 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 } = &args[0].node - { - let custom = Some(self.primitives.uint64); - let v: Result = (*val).try_into(); - if v.is_ok() { - return 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) - } - } + { + let custom = Some(self.primitives.int64); + let v: Result = (*val).try_into(); + + return v + .map(|_| DiagnosticsResult::ok(Located { + location: args[0].location, + custom, + node: ExprKind::Constant { + value: ast::Constant::Int(*val), + kind: kind.clone(), + }, + })) + .map_err(|_| { + diagnostics.insert(DiagErr( + format!("integer {} out of bound for int64", *val), + args[0].location, + )); + diagnostics + }) } - Located { location: func_location, custom, node: ExprKind::Name { id, ctx } } - } else { - func - }; + } + + 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 = (*val).try_into(); + + return v + .map(|_| DiagnosticsResult::ok(Located { + location: args[0].location, + custom, + node: ExprKind::Constant { + value: ast::Constant::Int(*val), + kind: kind.clone(), + }, + })) + .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 = (*val).try_into(); + + return v + .map(|_| DiagnosticsResult::ok(Located { + location: args[0].location, + custom, + node: ExprKind::Constant { + value: ast::Constant::Int(*val), + kind: kind.clone(), + }, + })) + .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::, _>>()?; 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 = (*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, _> = - 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>]) -> 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>]) -> 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>, 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 = (*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())) } } diff --git a/nac3core/src/typecheck/type_inferencer/test.rs b/nac3core/src/typecheck/type_inferencer/test.rs index bdd61d4..273a2cf 100644 --- a/nac3core/src/typecheck/type_inferencer/test.rs +++ b/nac3core/src/typecheck/type_inferencer/test.rs @@ -43,8 +43,10 @@ impl SymbolResolver for Resolver { unimplemented!() } - fn get_identifier_def(&self, id: StrRef) -> Result { - self.id_to_def.get(&id).cloned().ok_or_else(|| "Unknown identifier".to_string()) + fn get_identifier_def(&self, id: StrRef, loc: Option) -> ResultOrDiagnostics { + 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::, _>>() .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::, _>>() .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( diff --git a/nac3standalone/src/basic_symbol_resolver.rs b/nac3standalone/src/basic_symbol_resolver.rs index 2559b6f..8ebfe80 100644 --- a/nac3standalone/src/basic_symbol_resolver.rs +++ b/nac3standalone/src/basic_symbol_resolver.rs @@ -1,15 +1,11 @@ -use nac3core::{ - codegen::CodeGenContext, - symbol_resolver::{SymbolResolver, SymbolValue, ValueEnum}, - toplevel::{DefinitionId, TopLevelDef}, - typecheck::{ - type_inferencer::PrimitiveStore, - typedef::{Type, Unifier}, - }, -}; -use nac3parser::ast::{self, StrRef}; +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, 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>, @@ -61,8 +57,10 @@ impl SymbolResolver for Resolver { unimplemented!() } - fn get_identifier_def(&self, id: StrRef) -> Result { - 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) -> ResultOrDiagnostics { + 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 { diff --git a/nac3standalone/src/main.rs b/nac3standalone/src/main.rs index 9deb34e..f45b900 100644 --- a/nac3standalone/src/main.rs +++ b/nac3standalone/src/main.rs @@ -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::{ - concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions, - CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry, - }, - symbol_resolver::SymbolResolver, - toplevel::{ - composer::TopLevelComposer, helper::parse_parameter_default_value, type_annotation::*, - TopLevelDef, - }, - typecheck::{ - type_inferencer::PrimitiveStore, - typedef::{FunSignature, Type, Unifier}, - }, -}; +use nac3core::{codegen::{ + concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions, + CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry, +}, DiagnosticEngine, DiagnosticsResult, ResultOrDiagnostics, symbol_resolver::SymbolResolver, toplevel::{ + composer::TopLevelComposer, helper::parse_parameter_default_value, type_annotation::*, + TopLevelDef, +}, 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>], unifier: &mut Unifier, primitives: &PrimitiveStore, -) -> Result { +) -> ResultOrDiagnostics { + 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 { + .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::, _>>()?; - Ok(unifier.get_fresh_var_with_range(&constraints, None, None).0) + .collect::>(); + + 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 = 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!( - "expression {:?} cannot be handled as a TypeVar in global scope", - var - )) + Err(DiagnosticEngine::single(DiagErr( + format!( + "expression {:?} cannot be handled as a TypeVar in global scope", + var + ), + var.location + ))) } } else { - Err(format!( - "expression {:?} cannot be handled as a TypeVar in global scope", - var - )) + 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>], 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());