Compare commits

..

9 Commits

22 changed files with 521 additions and 378 deletions

7
Cargo.lock generated
View File

@ -502,9 +502,9 @@ dependencies = [
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.11.0" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
dependencies = [ dependencies = [
"either", "either",
] ]
@ -641,6 +641,7 @@ name = "nac3artiq"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"inkwell", "inkwell",
"itertools 0.12.0",
"nac3core", "nac3core",
"nac3ld", "nac3ld",
"nac3parser", "nac3parser",
@ -667,7 +668,7 @@ dependencies = [
"indoc", "indoc",
"inkwell", "inkwell",
"insta", "insta",
"itertools 0.11.0", "itertools 0.12.0",
"nac3parser", "nac3parser",
"parking_lot", "parking_lot",
"rayon", "rayon",

View File

@ -94,8 +94,8 @@
(pkgs.fetchFromGitHub { (pkgs.fetchFromGitHub {
owner = "m-labs"; owner = "m-labs";
repo = "artiq"; repo = "artiq";
rev = "4c189f8c0576111733bb6ff934035c080c8ccc58"; rev = "8b4572f9cad34ac0c2b6f6bba9382e7b59b2f93b";
sha256 = "sha256-gYGzmfaIoftKFDwn8AybUenYtIpux+tHGMu51WgwA8A="; sha256 = "sha256-O/0sUSxxXU1AL9cmT9qdzCkzdOKREBNftz22/8ouQcc=";
}) })
]; ];
buildInputs = [ buildInputs = [

View File

@ -9,6 +9,7 @@ name = "nac3artiq"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
itertools = "0.12"
pyo3 = { version = "0.20", features = ["extension-module"] } pyo3 = { version = "0.20", features = ["extension-module"] }
parking_lot = "0.12" parking_lot = "0.12"
tempfile = "3.8" tempfile = "3.8"

View File

@ -86,7 +86,13 @@ def ceil64(x):
import device_db import device_db
core_arguments = device_db.device_db["core"]["arguments"] core_arguments = device_db.device_db["core"]["arguments"]
compiler = nac3artiq.NAC3(core_arguments["target"]) artiq_builtins = {
"none": none,
"virtual": virtual,
"_ConstGenericMarker": _ConstGenericMarker,
"Option": Option,
}
compiler = nac3artiq.NAC3(core_arguments["target"], artiq_builtins)
allow_registration = True allow_registration = True
# Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side. # Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side.
registered_functions = set() registered_functions = set()

View File

@ -13,6 +13,7 @@ use inkwell::{
targets::*, targets::*,
OptimizationLevel, OptimizationLevel,
}; };
use itertools::Itertools;
use nac3core::codegen::{CodeGenLLVMOptions, CodeGenTargetMachineOptions, gen_func_impl}; use nac3core::codegen::{CodeGenLLVMOptions, CodeGenTargetMachineOptions, gen_func_impl};
use nac3core::toplevel::builtins::get_exn_constructor; use nac3core::toplevel::builtins::get_exn_constructor;
use nac3core::typecheck::typedef::{TypeEnum, Unifier}; use nac3core::typecheck::typedef::{TypeEnum, Unifier};
@ -75,7 +76,7 @@ pub struct PrimitivePythonId {
list: u64, list: u64,
tuple: u64, tuple: u64,
typevar: u64, typevar: u64,
const_generic_dummy: u64, const_generic_marker: u64,
none: u64, none: u64,
exception: u64, exception: u64,
generic_alias: (u64, u64), generic_alias: (u64, u64),
@ -470,7 +471,7 @@ impl Nac3 {
if let Err(e) = composer.start_analysis(true) { if let Err(e) = composer.start_analysis(true) {
// report error of __modinit__ separately // report error of __modinit__ separately
return if e.contains("<nac3_synthesized_modinit>") { return if e.iter().any(|err| err.contains("<nac3_synthesized_modinit>")) {
let msg = Self::report_modinit( let msg = Self::report_modinit(
&arg_names, &arg_names,
method_name, method_name,
@ -481,12 +482,15 @@ impl Nac3 {
); );
Err(CompileError::new_err(format!( Err(CompileError::new_err(format!(
"compilation failed\n----------\n{}", "compilation failed\n----------\n{}",
msg.unwrap_or(e) msg.unwrap_or(e.iter().sorted().join("\n----------\n"))
))) )))
} else { } else {
Err(CompileError::new_err(format!( Err(CompileError::new_err(
"compilation failed\n----------\n{e}" format!(
))) "compilation failed\n----------\n{}",
e.iter().sorted().join("\n----------\n"),
),
))
} }
} }
let top_level = Arc::new(composer.make_top_level_context()); let top_level = Arc::new(composer.make_top_level_context());
@ -775,7 +779,7 @@ fn add_exceptions(
#[pymethods] #[pymethods]
impl Nac3 { impl Nac3 {
#[new] #[new]
fn new(isa: &str, py: Python) -> PyResult<Self> { fn new(isa: &str, artiq_builtins: &PyDict, py: Python) -> PyResult<Self> {
let isa = match isa { let isa = match isa {
"host" => Isa::Host, "host" => Isa::Host,
"rv32g" => Isa::RiscV32G, "rv32g" => Isa::RiscV32G,
@ -841,44 +845,18 @@ impl Nac3 {
let typing_mod = PyModule::import(py, "typing").unwrap(); let typing_mod = PyModule::import(py, "typing").unwrap();
let types_mod = PyModule::import(py, "types").unwrap(); let types_mod = PyModule::import(py, "types").unwrap();
let get_id = |x| id_fn.call1((x,)).unwrap().extract().unwrap(); let get_id = |x: &PyAny| id_fn.call1((x,)).and_then(PyAny::extract).unwrap();
let get_attr_id = |obj: &PyModule, attr| id_fn.call1((obj.getattr(attr).unwrap(),)) let get_attr_id = |obj: &PyModule, attr| id_fn.call1((obj.getattr(attr).unwrap(),))
.unwrap().extract().unwrap(); .unwrap().extract().unwrap();
let primitive_ids = PrimitivePythonId { let primitive_ids = PrimitivePythonId {
virtual_id: get_id( virtual_id: get_id(artiq_builtins.get_item("virtual").ok().flatten().unwrap()),
builtins_mod
.getattr("globals")
.unwrap()
.call0()
.unwrap()
.get_item("virtual")
.unwrap(
)),
generic_alias: ( generic_alias: (
get_attr_id(typing_mod, "_GenericAlias"), get_attr_id(typing_mod, "_GenericAlias"),
get_attr_id(types_mod, "GenericAlias"), get_attr_id(types_mod, "GenericAlias"),
), ),
none: id_fn none: get_id(artiq_builtins.get_item("none").ok().flatten().unwrap()),
.call1((builtins_mod
.getattr("globals")
.unwrap()
.call0()
.unwrap()
.get_item("none")
.unwrap(),))
.unwrap()
.extract()
.unwrap(),
typevar: get_attr_id(typing_mod, "TypeVar"), typevar: get_attr_id(typing_mod, "TypeVar"),
const_generic_dummy: id_fn const_generic_marker: get_id(artiq_builtins.get_item("_ConstGenericMarker").ok().flatten().unwrap()),
.call1((
builtins_mod.getattr("globals")
.and_then(|v| v.call0())
.and_then(|v| v.get_item("_ConstGenericMarker"))
.unwrap(),
))
.and_then(PyAny::extract)
.unwrap(),
int: get_attr_id(builtins_mod, "int"), int: get_attr_id(builtins_mod, "int"),
int32: get_attr_id(numpy_mod, "int32"), int32: get_attr_id(numpy_mod, "int32"),
int64: get_attr_id(numpy_mod, "int64"), int64: get_attr_id(numpy_mod, "int64"),
@ -890,17 +868,7 @@ impl Nac3 {
list: get_attr_id(builtins_mod, "list"), list: get_attr_id(builtins_mod, "list"),
tuple: get_attr_id(builtins_mod, "tuple"), tuple: get_attr_id(builtins_mod, "tuple"),
exception: get_attr_id(builtins_mod, "Exception"), exception: get_attr_id(builtins_mod, "Exception"),
option: id_fn option: get_id(artiq_builtins.get_item("Option").ok().flatten().unwrap()),
.call1((builtins_mod
.getattr("globals")
.unwrap()
.call0()
.unwrap()
.get_item("Option")
.unwrap(),))
.unwrap()
.extract()
.unwrap(),
}; };
let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap(); let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap();

View File

@ -15,7 +15,7 @@ use pyo3::{
PyAny, PyObject, PyResult, Python, PyAny, PyObject, PyResult, Python,
}; };
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
sync::{ sync::{
Arc, Arc,
atomic::{AtomicBool, Ordering::Relaxed} atomic::{AtomicBool, Ordering::Relaxed}
@ -353,7 +353,7 @@ impl InnerResolver {
for i in 0usize.. { for i in 0usize.. {
if let Ok(constr) = constraints.get_item(i) { if let Ok(constr) = constraints.get_item(i) {
let constr_id: u64 = self.helper.id_fn.call1(py, (constr,))?.extract(py)?; let constr_id: u64 = self.helper.id_fn.call1(py, (constr,))?.extract(py)?;
if constr_id == self.primitive_ids.const_generic_dummy { if constr_id == self.primitive_ids.const_generic_marker {
is_const_generic = true; is_const_generic = true;
continue continue
} }
@ -1169,17 +1169,21 @@ impl SymbolResolver for Resolver {
}) })
} }
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> { fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> {
{ {
let id_to_def = self.0.id_to_def.read(); let id_to_def = self.0.id_to_def.read();
id_to_def.get(&id).copied().ok_or_else(String::new) id_to_def.get(&id).copied().ok_or_else(String::new)
} }
.or_else(|_| { .or_else(|_| {
let py_id = let py_id = self.0.name_to_pyid.get(&id)
self.0.name_to_pyid.get(&id).ok_or(format!("Undefined identifier `{id}`"))?; .ok_or_else(|| HashSet::from([
let result = self.0.pyid_to_def.read().get(py_id).copied().ok_or(format!( format!("Undefined identifier `{id}`"),
"`{id}` is not registered with NAC3 (@nac3 decorator missing?)" ]))?;
))?; let result = self.0.pyid_to_def.read().get(py_id)
.copied()
.ok_or_else(|| HashSet::from([
format!("`{id}` is not registered with NAC3 (@nac3 decorator missing?)"),
]))?;
self.0.id_to_def.write().insert(id, result); self.0.id_to_def.write().insert(id, result);
Ok(result) Ok(result)
}) })

View File

@ -1,8 +1,9 @@
//! Datatypes to support source location information. //! Datatypes to support source location information.
use std::cmp::Ordering;
use crate::ast_gen::StrRef; use crate::ast_gen::StrRef;
use std::fmt; use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FileName(pub StrRef); pub struct FileName(pub StrRef);
impl Default for FileName { impl Default for FileName {
fn default() -> Self { fn default() -> Self {
@ -17,7 +18,7 @@ impl From<String> for FileName {
} }
/// A location somewhere in the sourcecode. /// A location somewhere in the sourcecode.
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Location { pub struct Location {
pub row: usize, pub row: usize,
pub column: usize, pub column: usize,
@ -30,6 +31,28 @@ impl fmt::Display for Location {
} }
} }
impl Ord for Location {
fn cmp(&self, other: &Self) -> Ordering {
let file_cmp = self.file.0.to_string().cmp(&other.file.0.to_string());
if file_cmp != Ordering::Equal {
return file_cmp
}
let row_cmp = self.row.cmp(&other.row);
if row_cmp != Ordering::Equal {
return row_cmp
}
self.column.cmp(&other.column)
}
}
impl PartialOrd for Location {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Location { impl Location {
pub fn visualize<'a>( pub fn visualize<'a>(
&self, &self,

View File

@ -5,7 +5,7 @@ authors = ["M-Labs"]
edition = "2021" edition = "2021"
[dependencies] [dependencies]
itertools = "0.11" itertools = "0.12"
crossbeam = "0.8" crossbeam = "0.8"
parking_lot = "0.12" parking_lot = "0.12"
rayon = "1.5" rayon = "1.5"

View File

@ -60,6 +60,10 @@ pub enum ConcreteTypeEnum {
ret: ConcreteType, ret: ConcreteType,
vars: HashMap<u32, ConcreteType>, vars: HashMap<u32, ConcreteType>,
}, },
TConstant {
value: SymbolValue,
ty: ConcreteType,
},
} }
impl ConcreteTypeStore { impl ConcreteTypeStore {
@ -198,7 +202,11 @@ impl ConcreteTypeStore {
TypeEnum::TFunc(signature) => { TypeEnum::TFunc(signature) => {
self.from_signature(unifier, primitives, signature, cache) self.from_signature(unifier, primitives, signature, cache)
} }
_ => unreachable!(), TypeEnum::TConstant { value, ty, .. } => ConcreteTypeEnum::TConstant {
value: value.clone(),
ty: self.from_unifier_type(unifier, primitives, *ty, cache),
},
_ => unreachable!("{:?}", ty_enum.get_type_name()),
}; };
let index = if let Some(ConcreteType(index)) = cache.get(&ty).unwrap() { let index = if let Some(ConcreteType(index)) = cache.get(&ty).unwrap() {
self.store[*index] = result; self.store[*index] = result;
@ -285,6 +293,11 @@ impl ConcreteTypeStore {
.map(|(id, cty)| (*id, self.to_unifier_type(unifier, primitives, *cty, cache))) .map(|(id, cty)| (*id, self.to_unifier_type(unifier, primitives, *cty, cache)))
.collect::<HashMap<_, _>>(), .collect::<HashMap<_, _>>(),
}), }),
ConcreteTypeEnum::TConstant { value, ty } => TypeEnum::TConstant {
value: value.clone(),
ty: self.to_unifier_type(unifier, primitives, *ty, cache),
loc: None,
}
}; };
let result = unifier.add_ty(result); let result = unifier.add_ty(result);
if let Some(ty) = cache.get(&cty).unwrap() { if let Some(ty) = cache.get(&cty).unwrap() {

View File

@ -1645,7 +1645,7 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
let fun = ctx let fun = ctx
.resolver .resolver
.get_identifier_def(*id) .get_identifier_def(*id)
.map_err(|e| format!("{} (at {})", e, func.location))?; .map_err(|e| format!("{} (at {})", e.iter().next().unwrap(), func.location))?;
return Ok(generator return Ok(generator
.gen_call(ctx, None, (&signature, fun), params)? .gen_call(ctx, None, (&signature, fun), params)?
.map(Into::into)); .map(Into::into));

View File

@ -63,12 +63,14 @@ impl SymbolResolver for Resolver {
unimplemented!() unimplemented!()
} }
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> { fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> {
self.id_to_def self.id_to_def
.read() .read()
.get(&id) .get(&id)
.cloned() .cloned()
.ok_or_else(|| format!("cannot find symbol `{}`", id)) .ok_or_else(|| HashSet::from([
format!("cannot find symbol `{}`", id),
]))
} }
fn get_string_id(&self, _: &str) -> i32 { fn get_string_id(&self, _: &str) -> i32 {

View File

@ -1,6 +1,6 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Arc; use std::sync::Arc;
use std::{collections::HashMap, fmt::Display}; use std::{collections::HashMap, collections::HashSet, fmt::Display};
use std::rc::Rc; use std::rc::Rc;
use crate::typecheck::typedef::TypeEnum; use crate::typecheck::typedef::TypeEnum;
@ -296,7 +296,7 @@ pub trait SymbolResolver {
) -> Result<Type, String>; ) -> Result<Type, String>;
/// Get the top-level definition of identifiers. /// Get the top-level definition of identifiers.
fn get_identifier_def(&self, str: StrRef) -> Result<DefinitionId, String>; fn get_identifier_def(&self, str: StrRef) -> Result<DefinitionId, HashSet<String>>;
fn get_symbol_value<'ctx>( fn get_symbol_value<'ctx>(
&self, &self,
@ -341,7 +341,7 @@ pub fn parse_type_annotation<T>(
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
expr: &Expr<T>, expr: &Expr<T>,
) -> Result<Type, String> { ) -> Result<Type, HashSet<String>> {
use nac3parser::ast::ExprKind::*; use nac3parser::ast::ExprKind::*;
let ids = IDENTIFIER_ID.with(|ids| *ids); let ids = IDENTIFIER_ID.with(|ids| *ids);
let int32_id = ids[0]; let int32_id = ids[0];
@ -379,10 +379,12 @@ pub fn parse_type_annotation<T>(
let def = top_level_defs[obj_id.0].read(); let def = top_level_defs[obj_id.0].read();
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def { if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
if !type_vars.is_empty() { if !type_vars.is_empty() {
return Err(format!( return Err(HashSet::from([
"Unexpected number of type parameters: expected {} but got 0", format!(
type_vars.len() "Unexpected number of type parameters: expected {} but got 0",
)); type_vars.len()
),
]))
} }
let fields = chain( let fields = chain(
fields.iter().map(|(k, v, m)| (*k, (*v, *m))), fields.iter().map(|(k, v, m)| (*k, (*v, *m))),
@ -395,16 +397,22 @@ pub fn parse_type_annotation<T>(
params: HashMap::default(), params: HashMap::default(),
})) }))
} else { } else {
Err(format!("Cannot use function name as type at {loc}")) Err(HashSet::from([
format!("Cannot use function name as type at {loc}"),
]))
} }
} else { } else {
let ty = resolver let ty = resolver
.get_symbol_type(unifier, top_level_defs, primitives, *id) .get_symbol_type(unifier, top_level_defs, primitives, *id)
.map_err(|e| format!("Unknown type annotation at {loc}: {e}"))?; .map_err(|e| HashSet::from([
format!("Unknown type annotation at {loc}: {e}"),
]))?;
if let TypeEnum::TVar { .. } = &*unifier.get_ty(ty) { if let TypeEnum::TVar { .. } = &*unifier.get_ty(ty) {
Ok(ty) Ok(ty)
} else { } else {
Err(format!("Unknown type annotation {id} at {loc}")) Err(HashSet::from([
format!("Unknown type annotation {id} at {loc}"),
]))
} }
} }
} }
@ -427,7 +435,9 @@ pub fn parse_type_annotation<T>(
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Ok(unifier.add_ty(TypeEnum::TTuple { ty })) Ok(unifier.add_ty(TypeEnum::TTuple { ty }))
} else { } else {
Err("Expected multiple elements for tuple".into()) Err(HashSet::from([
"Expected multiple elements for tuple".into()
]))
} }
} else { } else {
let types = if let Tuple { elts, .. } = &slice.node { let types = if let Tuple { elts, .. } = &slice.node {
@ -444,11 +454,13 @@ pub fn parse_type_annotation<T>(
let def = top_level_defs[obj_id.0].read(); let def = top_level_defs[obj_id.0].read();
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def { if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
if types.len() != type_vars.len() { if types.len() != type_vars.len() {
return Err(format!( return Err(HashSet::from([
"Unexpected number of type parameters: expected {} but got {}", format!(
type_vars.len(), "Unexpected number of type parameters: expected {} but got {}",
types.len() type_vars.len(),
)); types.len()
),
]))
} }
let mut subst = HashMap::new(); let mut subst = HashMap::new();
for (var, ty) in izip!(type_vars.iter(), types.iter()) { for (var, ty) in izip!(type_vars.iter(), types.iter()) {
@ -472,7 +484,9 @@ pub fn parse_type_annotation<T>(
})); }));
Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: subst })) Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: subst }))
} else { } else {
Err("Cannot use function name as type".into()) Err(HashSet::from([
"Cannot use function name as type".into(),
]))
} }
} }
}; };
@ -483,10 +497,14 @@ pub fn parse_type_annotation<T>(
if let Name { id, .. } = &value.node { if let Name { id, .. } = &value.node {
subscript_name_handle(id, slice, unifier) subscript_name_handle(id, slice, unifier)
} else { } else {
Err(format!("unsupported type expression at {}", expr.location)) Err(HashSet::from([
format!("unsupported type expression at {}", expr.location),
]))
} }
} }
_ => Err(format!("unsupported type expression at {}", expr.location)), _ => Err(HashSet::from([
format!("unsupported type expression at {}", expr.location),
])),
} }
} }
@ -497,7 +515,7 @@ impl dyn SymbolResolver + Send + Sync {
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
expr: &Expr<T>, expr: &Expr<T>,
) -> Result<Type, String> { ) -> Result<Type, HashSet<String>> {
parse_type_annotation(self, top_level_defs, unifier, primitives, expr) parse_type_annotation(self, top_level_defs, unifier, primitives, expr)
} }

View File

@ -362,7 +362,7 @@ impl TopLevelComposer {
} }
} }
pub fn start_analysis(&mut self, inference: bool) -> Result<(), String> { pub fn start_analysis(&mut self, inference: bool) -> Result<(), HashSet<String>> {
self.analyze_top_level_class_type_var()?; self.analyze_top_level_class_type_var()?;
self.analyze_top_level_class_bases()?; self.analyze_top_level_class_bases()?;
self.analyze_top_level_class_fields_methods()?; self.analyze_top_level_class_fields_methods()?;
@ -374,7 +374,7 @@ impl TopLevelComposer {
} }
/// step 1, analyze the type vars associated with top level class /// step 1, analyze the type vars associated with top level class
fn analyze_top_level_class_type_var(&mut self) -> Result<(), String> { fn analyze_top_level_class_type_var(&mut self) -> Result<(), HashSet<String>> {
let def_list = &self.definition_ast_list; let def_list = &self.definition_ast_list;
let temp_def_list = self.extract_def_list(); let temp_def_list = self.extract_def_list();
let unifier = self.unifier.borrow_mut(); let unifier = self.unifier.borrow_mut();
@ -416,10 +416,12 @@ impl TopLevelComposer {
} => } =>
{ {
if is_generic { if is_generic {
return Err(format!( return Err(HashSet::from([
"only single Generic[...] is allowed (at {})", format!(
b.location "only single Generic[...] is allowed (at {})",
)); b.location
),
]))
} }
is_generic = true; is_generic = true;
@ -458,10 +460,12 @@ impl TopLevelComposer {
}) })
}; };
if !all_unique_type_var { if !all_unique_type_var {
return Err(format!( return Err(HashSet::from([
"duplicate type variable occurs (at {})", format!(
slice.location "duplicate type variable occurs (at {})",
)); slice.location
),
]))
} }
// add to TopLevelDef // add to TopLevelDef
@ -480,11 +484,11 @@ impl TopLevelComposer {
continue; continue;
} }
if let Err(e) = analyze(class_def, class_ast) { if let Err(e) = analyze(class_def, class_ast) {
errors.insert(e); errors.extend(e);
} }
} }
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors.into_iter().sorted().join("\n----------\n")); return Err(errors)
} }
Ok(()) Ok(())
} }
@ -492,7 +496,7 @@ impl TopLevelComposer {
/// step 2, base classes. /// step 2, base classes.
/// now that the type vars of all classes are done, handle base classes and /// 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 /// put Self class into the ancestors list. We only allow single inheritance
fn analyze_top_level_class_bases(&mut self) -> Result<(), String> { fn analyze_top_level_class_bases(&mut self) -> Result<(), HashSet<String>> {
if self.unifier.top_level.is_none() { if self.unifier.top_level.is_none() {
let ctx = Arc::new(self.make_top_level_context()); let ctx = Arc::new(self.make_top_level_context());
self.unifier.top_level = Some(ctx); self.unifier.top_level = Some(ctx);
@ -540,11 +544,13 @@ impl TopLevelComposer {
} }
if has_base { if has_base {
return Err(format!( return Err(HashSet::from([
"a class definition can only have at most one base class \ format!(
declaration and one generic declaration (at {})", "a class definition can only have at most one base class \
b.location declaration and one generic declaration (at {})",
)); b.location
),
]))
} }
has_base = true; has_base = true;
@ -563,10 +569,12 @@ impl TopLevelComposer {
if let TypeAnnotation::CustomClass { .. } = &base_ty { if let TypeAnnotation::CustomClass { .. } = &base_ty {
class_ancestors.push(base_ty); class_ancestors.push(base_ty);
} else { } else {
return Err(format!( return Err(HashSet::from([
"class base declaration can only be custom class (at {})", format!(
b.location, "class base declaration can only be custom class (at {})",
)); b.location,
),
]))
} }
} }
Ok(()) Ok(())
@ -579,16 +587,16 @@ impl TopLevelComposer {
continue; continue;
} }
if let Err(e) = get_direct_parents(class_def, class_ast) { if let Err(e) = get_direct_parents(class_def, class_ast) {
errors.insert(e); errors.extend(e);
} }
} }
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors.into_iter().sorted().join("\n----------\n")); return Err(errors)
} }
// second, get all ancestors // second, get all ancestors
let mut ancestors_store: HashMap<DefinitionId, Vec<TypeAnnotation>> = HashMap::default(); let mut ancestors_store: HashMap<DefinitionId, Vec<TypeAnnotation>> = HashMap::default();
let mut get_all_ancestors = |class_def: &Arc<RwLock<TopLevelDef>>| { let mut get_all_ancestors = |class_def: &Arc<RwLock<TopLevelDef>>| -> Result<(), HashSet<String>> {
let class_def = class_def.read(); let class_def = class_def.read();
let (class_ancestors, class_id) = { let (class_ancestors, class_id) = {
if let TopLevelDef::Class { ancestors, object_id, .. } = &*class_def { if let TopLevelDef::Class { ancestors, object_id, .. } = &*class_def {
@ -613,11 +621,11 @@ impl TopLevelComposer {
continue; continue;
} }
if let Err(e) = get_all_ancestors(class_def) { if let Err(e) = get_all_ancestors(class_def) {
errors.insert(e); errors.extend(e);
} }
} }
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors.into_iter().sorted().join("\n----------\n")); return Err(errors)
} }
// insert the ancestors to the def list // insert the ancestors to the def list
@ -658,7 +666,9 @@ impl TopLevelComposer {
stmt.node, stmt.node,
ast::StmtKind::FunctionDef { .. } | ast::StmtKind::AnnAssign { .. } ast::StmtKind::FunctionDef { .. } | ast::StmtKind::AnnAssign { .. }
) { ) {
return Err("Classes inherited from exception should have no custom fields/methods".into()); return Err(HashSet::from([
"Classes inherited from exception should have no custom fields/methods".into()
]))
} }
} }
} }
@ -677,7 +687,7 @@ impl TopLevelComposer {
} }
/// step 3, class fields and methods /// step 3, class fields and methods
fn analyze_top_level_class_fields_methods(&mut self) -> Result<(), String> { fn analyze_top_level_class_fields_methods(&mut self) -> Result<(), HashSet<String>> {
let temp_def_list = self.extract_def_list(); let temp_def_list = self.extract_def_list();
let primitives = &self.primitives_ty; let primitives = &self.primitives_ty;
let def_ast_list = &self.definition_ast_list; let def_ast_list = &self.definition_ast_list;
@ -700,12 +710,12 @@ impl TopLevelComposer {
&mut type_var_to_concrete_def, &mut type_var_to_concrete_def,
(&self.keyword_list, &self.core_config), (&self.keyword_list, &self.core_config),
) { ) {
errors.insert(e); errors.extend(e);
} }
} }
} }
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors.into_iter().sorted().join("\n----------\n")); return Err(errors)
} }
// handle the inherited methods and fields // handle the inherited methods and fields
@ -749,15 +759,16 @@ impl TopLevelComposer {
let mut subst_list = Some(Vec::new()); let mut subst_list = Some(Vec::new());
// unification of previously assigned typevar // unification of previously assigned typevar
let mut unification_helper = |ty, def| { let mut unification_helper = |ty, def| -> Result<(), HashSet<String>> {
let target_ty = let target_ty =
get_type_from_type_annotation_kinds(&temp_def_list, unifier, &def, &mut subst_list)?; get_type_from_type_annotation_kinds(&temp_def_list, unifier, &def, &mut subst_list)?;
unifier.unify(ty, target_ty).map_err(|e| e.to_display(unifier).to_string())?; unifier.unify(ty, target_ty)
.map_err(|e| HashSet::from([e.to_display(unifier).to_string()]))?;
Ok(()) Ok(())
}; };
for (ty, def) in type_var_to_concrete_def { for (ty, def) in type_var_to_concrete_def {
if let Err(e) = unification_helper(ty, def) { if let Err(e) = unification_helper(ty, def) {
errors.insert(e); errors.extend(e);
} }
} }
for ty in subst_list.unwrap() { for ty in subst_list.unwrap() {
@ -784,7 +795,7 @@ impl TopLevelComposer {
} }
} }
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors.into_iter().sorted().join("\n----------\n")); return Err(errors)
} }
for (def, _) in def_ast_list.iter().skip(self.builtin_num) { for (def, _) in def_ast_list.iter().skip(self.builtin_num) {
@ -803,7 +814,7 @@ impl TopLevelComposer {
} }
/// step 4, after class methods are done, top level functions have nothing unknown /// step 4, after class methods are done, top level functions have nothing unknown
fn analyze_top_level_function(&mut self) -> Result<(), String> { fn analyze_top_level_function(&mut self) -> Result<(), HashSet<String>> {
let def_list = &self.definition_ast_list; let def_list = &self.definition_ast_list;
let keyword_list = &self.keyword_list; let keyword_list = &self.keyword_list;
let temp_def_list = self.extract_def_list(); let temp_def_list = self.extract_def_list();
@ -844,13 +855,13 @@ impl TopLevelComposer {
if !defined_parameter_name.insert(x.node.arg) if !defined_parameter_name.insert(x.node.arg)
|| keyword_list.contains(&x.node.arg) || keyword_list.contains(&x.node.arg)
{ {
return Err(format!( return Err(HashSet::from([format!(
"top level function must have unique parameter names \ "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 (at {})",
x.location x.location
)); ),
} ]))
} }}
let arg_with_default: Vec<( let arg_with_default: Vec<(
&ast::Located<ast::ArgData<()>>, &ast::Located<ast::ArgData<()>>,
@ -871,17 +882,17 @@ impl TopLevelComposer {
arg_with_default arg_with_default
.iter() .iter()
.rev() .rev()
.map(|(x, default)| -> Result<FuncArg, String> { .map(|(x, default)| -> Result<FuncArg, HashSet<String>> {
let annotation = x let annotation = x
.node .node
.annotation .annotation
.as_ref() .as_ref()
.ok_or_else(|| { .ok_or_else(|| HashSet::from([
format!( format!(
"function parameter `{}` needs type annotation at {}", "function parameter `{}` needs type annotation at {}",
x.node.arg, x.location x.node.arg, x.location
) ),
})? ]))?
.as_ref(); .as_ref();
let type_annotation = parse_ast_to_type_annotation_kinds( let type_annotation = parse_ast_to_type_annotation_kinds(
@ -899,7 +910,7 @@ impl TopLevelComposer {
let type_vars_within = let type_vars_within =
get_type_var_contained_in_type_annotation(&type_annotation) get_type_var_contained_in_type_annotation(&type_annotation)
.into_iter() .into_iter()
.map(|x| -> Result<(u32, Type), String> { .map(|x| -> Result<(u32, Type), HashSet<String>> {
let TypeAnnotation::TypeVar(ty) = x else { let TypeAnnotation::TypeVar(ty) = x else {
unreachable!("must be type var annotation kind") unreachable!("must be type var annotation kind")
}; };
@ -937,8 +948,8 @@ impl TopLevelComposer {
unifier, unifier,
) )
.map_err( .map_err(
|err| format!("{} (at {})", err, x.location), |err| HashSet::from([format!("{} (at {})", err, x.location),
)?; ]))?;
v v
}), }),
}, },
@ -967,7 +978,7 @@ impl TopLevelComposer {
let type_vars_within = let type_vars_within =
get_type_var_contained_in_type_annotation(&return_ty_annotation) get_type_var_contained_in_type_annotation(&return_ty_annotation)
.into_iter() .into_iter()
.map(|x| -> Result<(u32, Type), String> { .map(|x| -> Result<(u32, Type), HashSet<String>> {
let TypeAnnotation::TypeVar(ty) = x else { let TypeAnnotation::TypeVar(ty) = x else {
unreachable!("must be type var here") unreachable!("must be type var here")
}; };
@ -1009,10 +1020,9 @@ impl TopLevelComposer {
ret: return_ty, ret: return_ty,
vars: function_var_map, vars: function_var_map,
})); }));
unifier.unify(*dummy_ty, function_ty).map_err(|e| { unifier.unify(*dummy_ty, function_ty).map_err(|e| HashSet::from([
e.at(Some(function_ast.location)).to_display(unifier).to_string() e.at(Some(function_ast.location)).to_display(unifier).to_string(),
})?; ]))?;
Ok(()) Ok(())
}; };
for (function_def, function_ast) in def_list.iter().skip(self.builtin_num) { for (function_def, function_ast) in def_list.iter().skip(self.builtin_num) {
@ -1020,11 +1030,11 @@ impl TopLevelComposer {
continue; continue;
} }
if let Err(e) = analyze(function_def, function_ast) { if let Err(e) = analyze(function_def, function_ast) {
errors.insert(e); errors.extend(e);
} }
} }
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors.into_iter().sorted().join("\n----------\n")); return Err(errors)
} }
Ok(()) Ok(())
} }
@ -1037,7 +1047,7 @@ impl TopLevelComposer {
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
type_var_to_concrete_def: &mut HashMap<Type, TypeAnnotation>, type_var_to_concrete_def: &mut HashMap<Type, TypeAnnotation>,
core_info: (&HashSet<StrRef>, &ComposerConfig), core_info: (&HashSet<StrRef>, &ComposerConfig),
) -> Result<(), String> { ) -> Result<(), HashSet<String>> {
let (keyword_list, core_config) = core_info; let (keyword_list, core_config) = core_info;
let mut class_def = class_def.write(); let mut class_def = class_def.write();
let TopLevelDef::Class { let TopLevelDef::Class {
@ -1087,25 +1097,23 @@ impl TopLevelComposer {
if !defined_parameter_name.insert(x.node.arg) if !defined_parameter_name.insert(x.node.arg)
|| (keyword_list.contains(&x.node.arg) && x.node.arg != zelf) || (keyword_list.contains(&x.node.arg) && x.node.arg != zelf)
{ {
return Err(format!( return Err(HashSet::from([
"top level function must have unique parameter names \ format!("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 (at {})",
x.location x.location),
)); ]))
} }
} }
if name == &"__init__".into() && !defined_parameter_name.contains(&zelf) { if name == &"__init__".into() && !defined_parameter_name.contains(&zelf) {
return Err(format!( return Err(HashSet::from([
"__init__ method must have a `self` parameter (at {})", format!("__init__ method must have a `self` parameter (at {})", b.location),
b.location ]))
));
} }
if !defined_parameter_name.contains(&zelf) { if !defined_parameter_name.contains(&zelf) {
return Err(format!( return Err(HashSet::from([
"class method must have a `self` parameter (at {})", format!("class method must have a `self` parameter (at {})", b.location),
b.location ]))
));
} }
let mut result = Vec::new(); let mut result = Vec::new();
@ -1134,12 +1142,12 @@ impl TopLevelComposer {
.node .node
.annotation .annotation
.as_ref() .as_ref()
.ok_or_else(|| { .ok_or_else(|| HashSet::from([
format!( format!(
"type annotation needed for `{}` at {}", "type annotation needed for `{}` at {}",
x.node.arg, x.location x.node.arg, x.location
) ),
})? ]))?
.as_ref(); .as_ref();
parse_ast_to_type_annotation_kinds( parse_ast_to_type_annotation_kinds(
class_resolver, class_resolver,
@ -1176,7 +1184,9 @@ impl TopLevelComposer {
None => None, None => None,
Some(default) => { Some(default) => {
if name == "self".into() { if name == "self".into() {
return Err(format!("`self` parameter cannot take default value (at {})", x.location)); return Err(HashSet::from([
format!("`self` parameter cannot take default value (at {})", x.location),
]));
} }
Some({ Some({
let v = Self::parse_parameter_default_value( let v = Self::parse_parameter_default_value(
@ -1186,9 +1196,9 @@ impl TopLevelComposer {
Self::check_default_param_type( Self::check_default_param_type(
&v, &type_ann, primitives, unifier, &v, &type_ann, primitives, unifier,
) )
.map_err(|err| { .map_err(|err| HashSet::from([
format!("{} (at {})", err, x.location) format!("{} (at {})", err, x.location),
})?; ]))?;
v v
}) })
} }
@ -1272,7 +1282,7 @@ impl TopLevelComposer {
// which should be fine since type within method_type will be subst later // which should be fine since type within method_type will be subst later
unifier unifier
.unify(method_dummy_ty, method_type) .unify(method_dummy_ty, method_type)
.map_err(|e| e.to_display(unifier).to_string())?; .map_err(|e| HashSet::from([e.to_display(unifier).to_string()]))?;
} }
ast::StmtKind::AnnAssign { target, annotation, value: None, .. } => { ast::StmtKind::AnnAssign { target, annotation, value: None, .. } => {
if let ast::ExprKind::Name { id: attr, .. } = &target.node { if let ast::ExprKind::Name { id: attr, .. } = &target.node {
@ -1321,35 +1331,43 @@ impl TopLevelComposer {
}; };
if !class_type_vars_def.contains(&t) { if !class_type_vars_def.contains(&t) {
return Err(format!( return Err(HashSet::from([
"class fields can only use type \ format!(
vars over which the class is generic (at {})", "class fields can only use type \
annotation.location vars over which the class is generic (at {})",
)); annotation.location
),
]))
} }
} }
type_var_to_concrete_def.insert(dummy_field_type, parsed_annotation); type_var_to_concrete_def.insert(dummy_field_type, parsed_annotation);
} else { } else {
return Err(format!( return Err(HashSet::from([
"same class fields `{}` defined twice (at {})", format!(
attr, target.location "same class fields `{}` defined twice (at {})",
)); attr, target.location
),
]))
} }
} else { } else {
return Err(format!( return Err(HashSet::from([
"unsupported statement type in class definition body (at {})", format!(
target.location "unsupported statement type in class definition body (at {})",
)); target.location
),
]))
} }
} }
ast::StmtKind::Assign { .. } => {}, // we don't class attributes ast::StmtKind::Assign { .. } => {}, // we don't class attributes
ast::StmtKind::Pass { .. } => {} ast::StmtKind::Pass { .. } => {}
ast::StmtKind::Expr { value: _, .. } => {} // typically a docstring; ignoring all expressions matches CPython behavior ast::StmtKind::Expr { value: _, .. } => {} // typically a docstring; ignoring all expressions matches CPython behavior
_ => { _ => {
return Err(format!( return Err(HashSet::from([
"unsupported statement in class definition body (at {})", format!(
b.location "unsupported statement in class definition body (at {})",
)) b.location
),
]))
} }
} }
} }
@ -1362,7 +1380,7 @@ impl TopLevelComposer {
unifier: &mut Unifier, unifier: &mut Unifier,
_primitives: &PrimitiveStore, _primitives: &PrimitiveStore,
type_var_to_concrete_def: &mut HashMap<Type, TypeAnnotation>, type_var_to_concrete_def: &mut HashMap<Type, TypeAnnotation>,
) -> Result<(), String> { ) -> Result<(), HashSet<String>> {
let TopLevelDef::Class { let TopLevelDef::Class {
object_id, object_id,
ancestors, ancestors,
@ -1417,9 +1435,9 @@ impl TopLevelComposer {
type_var_to_concrete_def, type_var_to_concrete_def,
); );
if !ok { if !ok {
return Err(format!( return Err(HashSet::from([format!(
"method {class_method_name} has same name as ancestors' method, but incompatible type" "method {class_method_name} has same name as ancestors' method, but incompatible type"),
)); ]))
} }
// mark it as added // mark it as added
is_override.insert(*class_method_name); is_override.insert(*class_method_name);
@ -1454,9 +1472,9 @@ impl TopLevelComposer {
// find if there is a fields with the same name in the child class // find if there is a fields with the same name in the child class
for (class_field_name, ..) in &*class_fields_def { for (class_field_name, ..) in &*class_fields_def {
if class_field_name == anc_field_name { if class_field_name == anc_field_name {
return Err(format!( return Err(HashSet::from([format!(
"field `{class_field_name}` has already declared in the ancestor classes" "field `{class_field_name}` has already declared in the ancestor classes"),
)); ]))
} }
} }
new_child_fields.push(to_be_added); new_child_fields.push(to_be_added);
@ -1474,7 +1492,7 @@ impl TopLevelComposer {
/// step 5, analyze and call type inferencer to fill the `instance_to_stmt` of /// step 5, analyze and call type inferencer to fill the `instance_to_stmt` of
/// [`TopLevelDef::Function`] /// [`TopLevelDef::Function`]
fn analyze_function_instance(&mut self) -> Result<(), String> { fn analyze_function_instance(&mut self) -> Result<(), HashSet<String>> {
// first get the class constructor type correct for the following type check in function body // first get the class constructor type correct for the following type check in function body
// also do class field instantiation check // also do class field instantiation check
let init_str_id = "__init__".into(); let init_str_id = "__init__".into();
@ -1579,9 +1597,9 @@ impl TopLevelComposer {
}; };
constructors.push((i, signature, definition_extension.len())); constructors.push((i, signature, definition_extension.len()));
definition_extension.push((Arc::new(RwLock::new(cons_fun)), None)); definition_extension.push((Arc::new(RwLock::new(cons_fun)), None));
unifier.unify(constructor.unwrap(), signature).map_err(|e| { unifier.unify(constructor.unwrap(), signature).map_err(|e| HashSet::from([
e.at(Some(ast.as_ref().unwrap().location)).to_display(unifier).to_string() e.at(Some(ast.as_ref().unwrap().location)).to_display(unifier).to_string()
})?; ]))?;
return Ok(()); return Ok(());
} }
let mut init_id: Option<DefinitionId> = None; let mut init_id: Option<DefinitionId> = None;
@ -1609,9 +1627,9 @@ impl TopLevelComposer {
ret: self_type, ret: self_type,
vars: contor_type_vars, vars: contor_type_vars,
})); }));
unifier.unify(constructor.unwrap(), contor_type).map_err(|e| { unifier.unify(constructor.unwrap(), contor_type).map_err(|e| HashSet::from([
e.at(Some(ast.as_ref().unwrap().location)).to_display(unifier).to_string() e.at(Some(ast.as_ref().unwrap().location)).to_display(unifier).to_string()
})?; ]))?;
// class field instantiation check // class field instantiation check
if let (Some(init_id), false) = (init_id, fields.is_empty()) { if let (Some(init_id), false) = (init_id, fields.is_empty()) {
@ -1623,12 +1641,14 @@ impl TopLevelComposer {
let all_inited = Self::get_all_assigned_field(body.as_slice())?; let all_inited = Self::get_all_assigned_field(body.as_slice())?;
for (f, _, _) in fields { for (f, _, _) in fields {
if !all_inited.contains(f) { if !all_inited.contains(f) {
return Err(format!( return Err(HashSet::from([
"fields `{}` of class `{}` not fully initialized in the initializer (at {})", format!(
f, "fields `{}` of class `{}` not fully initialized in the initializer (at {})",
class_name, f,
body[0].location, class_name,
)); body[0].location,
),
]))
} }
} }
} }
@ -1641,11 +1661,11 @@ impl TopLevelComposer {
continue; continue;
} }
if let Err(e) = analyze(i, def, ast) { if let Err(e) = analyze(i, def, ast) {
errors.insert(e); errors.extend(e);
} }
} }
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors.into_iter().sorted().join("\n---------\n")); return Err(errors)
} }
for (i, signature, id) in constructors { for (i, signature, id) in constructors {
@ -1860,9 +1880,9 @@ impl TopLevelComposer {
if let TypeEnum::TObj { obj_id, .. } = &*base { if let TypeEnum::TObj { obj_id, .. } = &*base {
*obj_id *obj_id
} else { } else {
return Err(format!( return Err(HashSet::from([
"Base type should be a class (at {loc})" format!("Base type should be a class (at {loc})"),
)); ]))
} }
}; };
let subtype_id = { let subtype_id = {
@ -1872,9 +1892,9 @@ impl TopLevelComposer {
} else { } else {
let base_repr = inferencer.unifier.stringify(*base); let base_repr = inferencer.unifier.stringify(*base);
let subtype_repr = inferencer.unifier.stringify(*subtype); let subtype_repr = inferencer.unifier.stringify(*subtype);
return Err(format!( return Err(HashSet::from([format!(
"Expected a subtype of {base_repr}, but got {subtype_repr} (at {loc})" "Expected a subtype of {base_repr}, but got {subtype_repr} (at {loc})"),
)); ]))
} }
}; };
let subtype_entry = defs[subtype_id.0].read(); let subtype_entry = defs[subtype_id.0].read();
@ -1887,9 +1907,9 @@ impl TopLevelComposer {
if m.is_none() { if m.is_none() {
let base_repr = inferencer.unifier.stringify(*base); let base_repr = inferencer.unifier.stringify(*base);
let subtype_repr = inferencer.unifier.stringify(*subtype); let subtype_repr = inferencer.unifier.stringify(*subtype);
return Err(format!( return Err(HashSet::from([format!(
"Expected a subtype of {base_repr}, but got {subtype_repr} (at {loc})" "Expected a subtype of {base_repr}, but got {subtype_repr} (at {loc})"),
)); ]))
} }
} }
} }
@ -1906,13 +1926,14 @@ impl TopLevelComposer {
&mut |id| format!("typevar{id}"), &mut |id| format!("typevar{id}"),
&mut None, &mut None,
); );
return Err(format!( return Err(HashSet::from([format!(
"expected return type of `{}` in function `{}` (at {})", "expected return type of `{}` in function `{}` (at {})",
ret_str, ret_str,
name, name,
ast.as_ref().unwrap().location ast.as_ref().unwrap().location
)); ),
} ]))
}
instance_to_stmt.insert( instance_to_stmt.insert(
get_subst_key(unifier, self_type, &subst, Some(&vars.keys().copied().collect())), get_subst_key(unifier, self_type, &subst, Some(&vars.keys().copied().collect())),
@ -1933,11 +1954,11 @@ impl TopLevelComposer {
continue; continue;
} }
if let Err(e) = analyze_2(id, def, ast) { if let Err(e) = analyze_2(id, def, ast) {
errors.insert(e); errors.extend(e);
} }
} }
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors.into_iter().sorted().join("\n----------\n")); return Err(errors)
} }
Ok(()) Ok(())
} }

View File

@ -204,13 +204,13 @@ impl TopLevelComposer {
pub fn get_class_method_def_info( pub fn get_class_method_def_info(
class_methods_def: &[(StrRef, Type, DefinitionId)], class_methods_def: &[(StrRef, Type, DefinitionId)],
method_name: StrRef, method_name: StrRef,
) -> Result<(Type, DefinitionId), String> { ) -> Result<(Type, DefinitionId), HashSet<String>> {
for (name, ty, def_id) in class_methods_def { for (name, ty, def_id) in class_methods_def {
if name == &method_name { if name == &method_name {
return Ok((*ty, *def_id)); return Ok((*ty, *def_id));
} }
} }
Err(format!("no method {method_name} in the current class")) Err(HashSet::from([format!("no method {method_name} in the current class")]))
} }
/// get all base class def id of a class, excluding itself. \ /// get all base class def id of a class, excluding itself. \
@ -221,7 +221,7 @@ impl TopLevelComposer {
pub fn get_all_ancestors_helper( pub fn get_all_ancestors_helper(
child: &TypeAnnotation, child: &TypeAnnotation,
temp_def_list: &[Arc<RwLock<TopLevelDef>>], temp_def_list: &[Arc<RwLock<TopLevelDef>>],
) -> Result<Vec<TypeAnnotation>, String> { ) -> Result<Vec<TypeAnnotation>, HashSet<String>> {
let mut result: Vec<TypeAnnotation> = Vec::new(); let mut result: Vec<TypeAnnotation> = Vec::new();
let mut parent = Self::get_parent(child, temp_def_list); let mut parent = Self::get_parent(child, temp_def_list);
while let Some(p) = parent { while let Some(p) = parent {
@ -242,7 +242,7 @@ impl TopLevelComposer {
if no_cycle { if no_cycle {
result.push(p); result.push(p);
} else { } else {
return Err("cyclic inheritance detected".into()); return Err(HashSet::from(["cyclic inheritance detected".into()]));
} }
} }
Ok(result) Ok(result)
@ -272,11 +272,13 @@ impl TopLevelComposer {
} }
/// get the `var_id` of a given `TVar` type /// get the `var_id` of a given `TVar` type
pub fn get_var_id(var_ty: Type, unifier: &mut Unifier) -> Result<u32, String> { pub fn get_var_id(var_ty: Type, unifier: &mut Unifier) -> Result<u32, HashSet<String>> {
if let TypeEnum::TVar { id, .. } = unifier.get_ty(var_ty).as_ref() { if let TypeEnum::TVar { id, .. } = unifier.get_ty(var_ty).as_ref() {
Ok(*id) Ok(*id)
} else { } else {
Err("not type var".to_string()) Err(HashSet::from([
"not type var".to_string(),
]))
} }
} }
@ -337,7 +339,7 @@ impl TopLevelComposer {
) )
} }
pub fn get_all_assigned_field(stmts: &[Stmt<()>]) -> Result<HashSet<StrRef>, String> { pub fn get_all_assigned_field(stmts: &[Stmt<()>]) -> Result<HashSet<StrRef>, HashSet<String>> {
let mut result = HashSet::new(); let mut result = HashSet::new();
for s in stmts { for s in stmts {
match &s.node { match &s.node {
@ -354,10 +356,12 @@ impl TopLevelComposer {
} }
} => } =>
{ {
return Err(format!( return Err(HashSet::from([
"redundant type annotation for class fields at {}", format!(
s.location "redundant type annotation for class fields at {}",
)) s.location
),
]))
} }
ast::StmtKind::Assign { targets, .. } => { ast::StmtKind::Assign { targets, .. } => {
for t in targets { for t in targets {
@ -409,7 +413,7 @@ impl TopLevelComposer {
pub fn parse_parameter_default_value( pub fn parse_parameter_default_value(
default: &ast::Expr, default: &ast::Expr,
resolver: &(dyn SymbolResolver + Send + Sync), resolver: &(dyn SymbolResolver + Send + Sync),
) -> Result<SymbolValue, String> { ) -> Result<SymbolValue, HashSet<String>> {
parse_parameter_default_value(default, resolver) parse_parameter_default_value(default, resolver)
} }
@ -466,14 +470,14 @@ impl TopLevelComposer {
pub fn parse_parameter_default_value( pub fn parse_parameter_default_value(
default: &ast::Expr, default: &ast::Expr,
resolver: &(dyn SymbolResolver + Send + Sync), resolver: &(dyn SymbolResolver + Send + Sync),
) -> Result<SymbolValue, String> { ) -> Result<SymbolValue, HashSet<String>> {
fn handle_constant(val: &Constant, loc: &Location) -> Result<SymbolValue, String> { fn handle_constant(val: &Constant, loc: &Location) -> Result<SymbolValue, HashSet<String>> {
match val { match val {
Constant::Int(v) => { Constant::Int(v) => {
if let Ok(v) = (*v).try_into() { if let Ok(v) = (*v).try_into() {
Ok(SymbolValue::I32(v)) Ok(SymbolValue::I32(v))
} else { } else {
Err(format!("integer value out of range at {loc}")) Err(HashSet::from([format!("integer value out of range at {loc}")]))
} }
} }
Constant::Float(v) => Ok(SymbolValue::Double(*v)), Constant::Float(v) => Ok(SymbolValue::Double(*v)),
@ -481,9 +485,11 @@ pub fn parse_parameter_default_value(
Constant::Tuple(tuple) => Ok(SymbolValue::Tuple( Constant::Tuple(tuple) => Ok(SymbolValue::Tuple(
tuple.iter().map(|x| handle_constant(x, loc)).collect::<Result<Vec<_>, _>>()?, tuple.iter().map(|x| handle_constant(x, loc)).collect::<Result<Vec<_>, _>>()?,
)), )),
Constant::None => Err(format!( Constant::None => Err(HashSet::from([
"`None` is not supported, use `none` for option type instead ({loc})" format!(
)), "`None` is not supported, use `none` for option type instead ({loc})"
),
])),
_ => unimplemented!("this constant is not supported at {}", loc), _ => unimplemented!("this constant is not supported at {}", loc),
} }
} }
@ -496,37 +502,51 @@ pub fn parse_parameter_default_value(
let v: Result<i64, _> = (*v).try_into(); let v: Result<i64, _> = (*v).try_into();
match v { match v {
Ok(v) => Ok(SymbolValue::I64(v)), Ok(v) => Ok(SymbolValue::I64(v)),
_ => Err(format!("default param value out of range at {}", default.location)), _ => Err(HashSet::from([
format!("default param value out of range at {}", default.location)
])),
} }
} }
_ => Err(format!("only allow constant integer here at {}", default.location)) _ => Err(HashSet::from([
format!("only allow constant integer here at {}", default.location),
]))
} }
ast::ExprKind::Name { id, .. } if *id == "uint32".into() => match &args[0].node { ast::ExprKind::Name { id, .. } if *id == "uint32".into() => match &args[0].node {
ast::ExprKind::Constant { value: Constant::Int(v), .. } => { ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
let v: Result<u32, _> = (*v).try_into(); let v: Result<u32, _> = (*v).try_into();
match v { match v {
Ok(v) => Ok(SymbolValue::U32(v)), Ok(v) => Ok(SymbolValue::U32(v)),
_ => Err(format!("default param value out of range at {}", default.location)), _ => Err(HashSet::from([
format!("default param value out of range at {}", default.location),
])),
} }
} }
_ => Err(format!("only allow constant integer here at {}", default.location)) _ => Err(HashSet::from([
format!("only allow constant integer here at {}", default.location),
]))
} }
ast::ExprKind::Name { id, .. } if *id == "uint64".into() => match &args[0].node { ast::ExprKind::Name { id, .. } if *id == "uint64".into() => match &args[0].node {
ast::ExprKind::Constant { value: Constant::Int(v), .. } => { ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
let v: Result<u64, _> = (*v).try_into(); let v: Result<u64, _> = (*v).try_into();
match v { match v {
Ok(v) => Ok(SymbolValue::U64(v)), Ok(v) => Ok(SymbolValue::U64(v)),
_ => Err(format!("default param value out of range at {}", default.location)), _ => Err(HashSet::from([
format!("default param value out of range at {}", default.location),
])),
} }
} }
_ => Err(format!("only allow constant integer here at {}", default.location)) _ => Err(HashSet::from([
format!("only allow constant integer here at {}", default.location),
]))
} }
ast::ExprKind::Name { id, .. } if *id == "Some".into() => Ok( ast::ExprKind::Name { id, .. } if *id == "Some".into() => Ok(
SymbolValue::OptionSome( SymbolValue::OptionSome(
Box::new(parse_parameter_default_value(&args[0], resolver)?) Box::new(parse_parameter_default_value(&args[0], resolver)?)
) )
), ),
_ => Err(format!("unsupported default parameter at {}", default.location)), _ => Err(HashSet::from([
format!("unsupported default parameter at {}", default.location),
])),
} }
} }
ast::ExprKind::Tuple { elts, .. } => Ok(SymbolValue::Tuple(elts ast::ExprKind::Tuple { elts, .. } => Ok(SymbolValue::Tuple(elts
@ -537,17 +557,21 @@ pub fn parse_parameter_default_value(
ast::ExprKind::Name { id, .. } if id == &"none".into() => Ok(SymbolValue::OptionNone), ast::ExprKind::Name { id, .. } if id == &"none".into() => Ok(SymbolValue::OptionNone),
ast::ExprKind::Name { id, .. } => { ast::ExprKind::Name { id, .. } => {
resolver.get_default_param_value(default).ok_or_else( resolver.get_default_param_value(default).ok_or_else(
|| format!( || HashSet::from([
"`{}` cannot be used as a default parameter at {} \ format!(
(not primitive type, option or tuple / not defined?)", "`{}` cannot be used as a default parameter at {} \
id, (not primitive type, option or tuple / not defined?)",
default.location id,
) default.location
),
])
) )
} }
_ => Err(format!( _ => Err(HashSet::from([
"unsupported default parameter (not primitive type, option or tuple) at {}", format!(
default.location "unsupported default parameter (not primitive type, option or tuple) at {}",
)) default.location
),
]))
} }
} }

View File

@ -64,8 +64,9 @@ impl SymbolResolver for Resolver {
unimplemented!() unimplemented!()
} }
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> { fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> {
self.0.id_to_def.lock().get(&id).cloned().ok_or_else(|| "Unknown identifier".to_string()) self.0.id_to_def.lock().get(&id).cloned()
.ok_or_else(|| HashSet::from(["Unknown identifier".to_string()]))
} }
fn get_string_id(&self, _: &str) -> i32 { fn get_string_id(&self, _: &str) -> i32 {
@ -551,9 +552,9 @@ fn test_analyze(source: Vec<&str>, res: Vec<&str>) {
if let Err(msg) = composer.start_analysis(false) { if let Err(msg) = composer.start_analysis(false) {
if print { if print {
println!("{}", msg); println!("{}", msg.iter().sorted().join("\n----------\n"));
} else { } else {
assert_eq!(res[0], msg); assert_eq!(res[0], msg.iter().next().unwrap());
} }
} else { } else {
// skip 5 to skip primitives // skip 5 to skip primitives
@ -735,9 +736,9 @@ fn test_inference(source: Vec<&str>, res: Vec<&str>) {
if let Err(msg) = composer.start_analysis(true) { if let Err(msg) = composer.start_analysis(true) {
if print { if print {
println!("{}", msg); println!("{}", msg.iter().sorted().join("\n----------\n"));
} else { } else {
assert_eq!(res[0], msg); assert_eq!(res[0], msg.iter().next().unwrap());
} }
} else { } else {
// skip 5 to skip primitives // skip 5 to skip primitives

View File

@ -82,7 +82,7 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
// the key stores the type_var of this topleveldef::class, we only need this field here // the key stores the type_var of this topleveldef::class, we only need this field here
locked: HashMap<DefinitionId, Vec<Type>>, locked: HashMap<DefinitionId, Vec<Type>>,
type_var: Option<Type>, type_var: Option<Type>,
) -> Result<TypeAnnotation, String> { ) -> Result<TypeAnnotation, HashSet<String>> {
let name_handle = |id: &StrRef, let name_handle = |id: &StrRef,
unifier: &mut Unifier, unifier: &mut Unifier,
locked: HashMap<DefinitionId, Vec<Type>>| { locked: HashMap<DefinitionId, Vec<Type>>| {
@ -109,10 +109,12 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
if let TopLevelDef::Class { type_vars, .. } = &*def_read { if let TopLevelDef::Class { type_vars, .. } = &*def_read {
type_vars.clone() type_vars.clone()
} else { } else {
return Err(format!( return Err(HashSet::from([
"function cannot be used as a type (at {})", format!(
expr.location "function cannot be used as a type (at {})",
)); expr.location
),
]))
} }
} else { } else {
locked.get(&obj_id).unwrap().clone() locked.get(&obj_id).unwrap().clone()
@ -120,11 +122,13 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
}; };
// check param number here // check param number here
if !type_vars.is_empty() { if !type_vars.is_empty() {
return Err(format!( return Err(HashSet::from([
"expect {} type variable parameter but got 0 (at {})", format!(
type_vars.len(), "expect {} type variable parameter but got 0 (at {})",
expr.location, type_vars.len(),
)); expr.location,
),
]))
} }
Ok(TypeAnnotation::CustomClass { id: obj_id, params: vec![] }) Ok(TypeAnnotation::CustomClass { id: obj_id, params: vec![] })
} else if let Ok(ty) = resolver.get_symbol_type(unifier, top_level_defs, primitives, *id) { } else if let Ok(ty) = resolver.get_symbol_type(unifier, top_level_defs, primitives, *id) {
@ -133,10 +137,14 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
unifier.unify(var, ty).unwrap(); unifier.unify(var, ty).unwrap();
Ok(TypeAnnotation::TypeVar(ty)) Ok(TypeAnnotation::TypeVar(ty))
} else { } else {
Err(format!("`{}` is not a valid type annotation (at {})", id, expr.location)) Err(HashSet::from([
format!("`{}` is not a valid type annotation (at {})", id, expr.location),
]))
} }
} else { } else {
Err(format!("`{}` is not a valid type annotation (at {})", id, expr.location)) Err(HashSet::from([
format!("`{}` is not a valid type annotation (at {})", id, expr.location),
]))
} }
}; };
@ -147,7 +155,9 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
mut locked: HashMap<DefinitionId, Vec<Type>>| { mut locked: HashMap<DefinitionId, Vec<Type>>| {
if ["virtual".into(), "Generic".into(), "list".into(), "tuple".into(), "Option".into()].contains(id) if ["virtual".into(), "Generic".into(), "list".into(), "tuple".into(), "Option".into()].contains(id)
{ {
return Err(format!("keywords cannot be class name (at {})", expr.location)); return Err(HashSet::from([
format!("keywords cannot be class name (at {})", expr.location),
]))
} }
let obj_id = resolver.get_identifier_def(*id)?; let obj_id = resolver.get_identifier_def(*id)?;
let type_vars = { let type_vars = {
@ -170,12 +180,14 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
vec![slice] vec![slice]
}; };
if type_vars.len() != params_ast.len() { if type_vars.len() != params_ast.len() {
return Err(format!( return Err(HashSet::from([
"expect {} type parameters but got {} (at {})", format!(
type_vars.len(), "expect {} type parameters but got {} (at {})",
params_ast.len(), type_vars.len(),
params_ast[0].location, params_ast.len(),
)); params_ast[0].location,
),
]))
} }
let result = params_ast let result = params_ast
.iter() .iter()
@ -201,11 +213,12 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
if no_type_var { if no_type_var {
result result
} else { } else {
return Err(format!( return Err(HashSet::from([
"application of type vars to generic class \ format!(
is not currently supported (at {})", "application of type vars to generic class is not currently supported (at {})",
params_ast[0].location params_ast[0].location
)); ),
]))
} }
}; };
Ok(TypeAnnotation::CustomClass { id: obj_id, params: param_type_infos }) Ok(TypeAnnotation::CustomClass { id: obj_id, params: param_type_infos })
@ -311,7 +324,9 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
if let ast::ExprKind::Name { id, .. } = &value.node { if let ast::ExprKind::Name { id, .. } = &value.node {
class_name_handle(id, slice, unifier, locked) class_name_handle(id, slice, unifier, locked)
} else { } else {
Err(format!("unsupported expression type for class name (at {})", value.location)) Err(HashSet::from([
format!("unsupported expression type for class name (at {})", value.location)
]))
} }
} }
@ -324,13 +339,16 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
}; };
let underlying_ty = underlying_ty[0]; let underlying_ty = underlying_ty[0];
let value = SymbolValue::from_constant(value, underlying_ty, primitives, unifier)?; let value = SymbolValue::from_constant(value, underlying_ty, primitives, unifier)
.map_err(|err| HashSet::from([err]))?;
if matches!(value, SymbolValue::Str(_) | SymbolValue::Tuple(_) | SymbolValue::OptionSome(_)) { if matches!(value, SymbolValue::Str(_) | SymbolValue::Tuple(_) | SymbolValue::OptionSome(_)) {
return Err(format!( return Err(HashSet::from([
"expression {value} is not allowed for constant type annotation (at {})", format!(
expr.location "expression {value} is not allowed for constant type annotation (at {})",
)) expr.location
),
]))
} }
Ok(TypeAnnotation::Constant { Ok(TypeAnnotation::Constant {
@ -339,7 +357,9 @@ pub fn parse_ast_to_type_annotation_kinds<T>(
}) })
} }
_ => Err(format!("unsupported expression for type annotation (at {})", expr.location)), _ => Err(HashSet::from([
format!("unsupported expression for type annotation (at {})", expr.location),
])),
} }
} }
@ -351,7 +371,7 @@ pub fn get_type_from_type_annotation_kinds(
unifier: &mut Unifier, unifier: &mut Unifier,
ann: &TypeAnnotation, ann: &TypeAnnotation,
subst_list: &mut Option<Vec<Type>> subst_list: &mut Option<Vec<Type>>
) -> Result<Type, String> { ) -> Result<Type, HashSet<String>> {
match ann { match ann {
TypeAnnotation::CustomClass { id: obj_id, params } => { TypeAnnotation::CustomClass { id: obj_id, params } => {
let def_read = top_level_defs[obj_id.0].read(); let def_read = top_level_defs[obj_id.0].read();
@ -361,11 +381,13 @@ pub fn get_type_from_type_annotation_kinds(
}; };
if type_vars.len() != params.len() { if type_vars.len() != params.len() {
return Err(format!( return Err(HashSet::from([
"unexpected number of type parameters: expected {} but got {}", format!(
type_vars.len(), "unexpected number of type parameters: expected {} but got {}",
params.len() type_vars.len(),
)) params.len()
),
]))
} }
let param_ty = params let param_ty = params
@ -401,16 +423,18 @@ pub fn get_type_from_type_annotation_kinds(
if ok { if ok {
result.insert(*id, p); result.insert(*id, p);
} else { } else {
return Err(format!( return Err(HashSet::from([
"cannot apply type {} to type variable with id {:?}", format!(
unifier.internal_stringify( "cannot apply type {} to type variable with id {:?}",
p, unifier.internal_stringify(
&mut |id| format!("class{id}"), p,
&mut |id| format!("typevar{id}"), &mut |id| format!("class{id}"),
&mut None &mut |id| format!("typevar{id}"),
), &mut None
*id ),
)); *id
)
]))
} }
} }
@ -430,11 +454,13 @@ pub fn get_type_from_type_annotation_kinds(
if ok { if ok {
result.insert(*id, p); result.insert(*id, p);
} else { } else {
return Err(format!( return Err(HashSet::from([
"cannot apply type {} to type variable {}", format!(
unifier.stringify(p), "cannot apply type {} to type variable {}",
name.unwrap_or_else(|| format!("typevar{id}").into()), unifier.stringify(p),
)) name.unwrap_or_else(|| format!("typevar{id}").into()),
),
]))
} }
} }

View File

@ -6,9 +6,11 @@ use nac3parser::ast::{self, Constant, Expr, ExprKind, Operator::{LShift, RShift}
use std::{collections::HashSet, iter::once}; use std::{collections::HashSet, iter::once};
impl<'a> Inferencer<'a> { impl<'a> Inferencer<'a> {
fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> Result<(), String> { fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> Result<(), HashSet<String>> {
if matches!(expr.custom, Some(ty) if self.unifier.unioned(ty, self.primitives.none)) { 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(HashSet::from([
format!("Error at {}: cannot have value none", expr.location),
]))
} else { } else {
Ok(()) Ok(())
} }
@ -18,10 +20,11 @@ impl<'a> Inferencer<'a> {
&mut self, &mut self,
pattern: &Expr<Option<Type>>, pattern: &Expr<Option<Type>>,
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<(), String> { ) -> Result<(), HashSet<String>> {
match &pattern.node { match &pattern.node {
ExprKind::Name { id, .. } if id == &"none".into() => ExprKind::Name { id, .. } if id == &"none".into() => Err(HashSet::from([
Err(format!("cannot assign to a `none` (at {})", pattern.location)), format!("cannot assign to a `none` (at {})", pattern.location),
])),
ExprKind::Name { id, .. } => { ExprKind::Name { id, .. } => {
if !defined_identifiers.contains(id) { if !defined_identifiers.contains(id) {
defined_identifiers.insert(*id); defined_identifiers.insert(*id);
@ -41,15 +44,19 @@ impl<'a> Inferencer<'a> {
self.should_have_value(value)?; self.should_have_value(value)?;
self.check_expr(slice, defined_identifiers)?; self.check_expr(slice, defined_identifiers)?;
if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty(value.custom.unwrap()) { if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty(value.custom.unwrap()) {
return Err(format!( return Err(HashSet::from([
"Error at {}: cannot assign to tuple element", format!(
value.location "Error at {}: cannot assign to tuple element",
)); value.location
),
]))
} }
Ok(()) Ok(())
} }
ExprKind::Constant { .. } => { ExprKind::Constant { .. } => {
Err(format!("cannot assign to a constant (at {})", pattern.location)) Err(HashSet::from([
format!("cannot assign to a constant (at {})", pattern.location),
]))
} }
_ => self.check_expr(pattern, defined_identifiers), _ => self.check_expr(pattern, defined_identifiers),
} }
@ -59,15 +66,17 @@ impl<'a> Inferencer<'a> {
&mut self, &mut self,
expr: &Expr<Option<Type>>, expr: &Expr<Option<Type>>,
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<(), String> { ) -> Result<(), HashSet<String>> {
// there are some cases where the custom field is None // there are some cases where the custom field is None
if let Some(ty) = &expr.custom { if let Some(ty) = &expr.custom {
if !matches!(&expr.node, ExprKind::Constant { value: Constant::Ellipsis, .. }) && !self.unifier.is_concrete(*ty, &self.function_data.bound_variables) { if !matches!(&expr.node, ExprKind::Constant { value: Constant::Ellipsis, .. }) && !self.unifier.is_concrete(*ty, &self.function_data.bound_variables) {
return Err(format!( return Err(HashSet::from([
"expected concrete type at {} but got {}", format!(
expr.location, "expected concrete type at {} but got {}",
self.unifier.get_ty(*ty).get_type_name() expr.location,
)); self.unifier.get_ty(*ty).get_type_name()
)
]))
} }
} }
match &expr.node { match &expr.node {
@ -87,10 +96,12 @@ impl<'a> Inferencer<'a> {
self.defined_identifiers.insert(*id); self.defined_identifiers.insert(*id);
} }
Err(e) => { Err(e) => {
return Err(format!( return Err(HashSet::from([
"type error at identifier `{}` ({}) at {}", format!(
id, e, expr.location "type error at identifier `{}` ({}) at {}",
)); id, e, expr.location
)
]))
} }
} }
} }
@ -121,10 +132,12 @@ impl<'a> Inferencer<'a> {
}; };
if *rhs_val < 0 { if *rhs_val < 0 {
return Err(format!( return Err(HashSet::from([
"shift count is negative at {}", format!(
right.location "shift count is negative at {}",
)); right.location
),
]))
} }
} }
} }
@ -200,7 +213,7 @@ impl<'a> Inferencer<'a> {
&mut self, &mut self,
stmt: &Stmt<Option<Type>>, stmt: &Stmt<Option<Type>>,
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<bool, String> { ) -> Result<bool, HashSet<String>> {
match &stmt.node { match &stmt.node {
StmtKind::For { target, iter, body, orelse, .. } => { StmtKind::For { target, iter, body, orelse, .. } => {
self.check_expr(iter, defined_identifiers)?; self.check_expr(iter, defined_identifiers)?;
@ -307,11 +320,11 @@ impl<'a> Inferencer<'a> {
&mut self, &mut self,
block: &[Stmt<Option<Type>>], block: &[Stmt<Option<Type>>],
defined_identifiers: &mut HashSet<StrRef>, defined_identifiers: &mut HashSet<StrRef>,
) -> Result<bool, String> { ) -> Result<bool, HashSet<String>> {
let mut ret = false; let mut ret = false;
for stmt in block { for stmt in block {
if ret { if ret {
println!("warning: dead code at {:?}\n", stmt.location); eprintln!("warning: dead code at {:?}\n", stmt.location);
} }
if self.check_stmt(stmt, defined_identifiers)? { if self.check_stmt(stmt, defined_identifiers)? {
ret = true; ret = true;

View File

@ -64,19 +64,19 @@ pub struct Inferencer<'a> {
struct NaiveFolder(); struct NaiveFolder();
impl Fold<()> for NaiveFolder { impl Fold<()> for NaiveFolder {
type TargetU = Option<Type>; type TargetU = Option<Type>;
type Error = String; type Error = HashSet<String>;
fn map_user(&mut self, (): ()) -> Result<Self::TargetU, Self::Error> { fn map_user(&mut self, (): ()) -> Result<Self::TargetU, Self::Error> {
Ok(None) Ok(None)
} }
} }
fn report_error<T>(msg: &str, location: Location) -> Result<T, String> { fn report_error<T>(msg: &str, location: Location) -> Result<T, HashSet<String>> {
Err(format!("{msg} at {location}")) Err(HashSet::from([format!("{msg} at {location}")]))
} }
impl<'a> Fold<()> for Inferencer<'a> { impl<'a> Fold<()> for Inferencer<'a> {
type TargetU = Option<Type>; type TargetU = Option<Type>;
type Error = String; type Error = HashSet<String>;
fn map_user(&mut self, (): ()) -> Result<Self::TargetU, Self::Error> { fn map_user(&mut self, (): ()) -> Result<Self::TargetU, Self::Error> {
Ok(None) Ok(None)
@ -159,9 +159,9 @@ impl<'a> Fold<()> for Inferencer<'a> {
} }
if let Some(old_typ) = self.variable_mapping.insert(name, typ) { if let Some(old_typ) = self.variable_mapping.insert(name, typ) {
let loc = handler.location; let loc = handler.location;
self.unifier.unify(old_typ, typ).map_err(|e| { self.unifier.unify(old_typ, typ).map_err(|e| HashSet::from([
e.at(Some(loc)).to_display(self.unifier).to_string() e.at(Some(loc)).to_display(self.unifier).to_string(),
})?; ]))?;
} }
} }
let mut type_ = naive_folder.fold_expr(*type_)?; let mut type_ = naive_folder.fold_expr(*type_)?;
@ -274,7 +274,7 @@ impl<'a> Fold<()> for Inferencer<'a> {
.collect(); .collect();
let loc = node.location; let loc = node.location;
let targets = targets let targets = targets
.map_err(|e| e.at(Some(loc)).to_display(self.unifier).to_string())?; .map_err(|e| HashSet::from([e.at(Some(loc)).to_display(self.unifier).to_string()]))?;
return Ok(Located { return Ok(Located {
location: node.location, location: node.location,
node: ast::StmtKind::Assign { node: ast::StmtKind::Assign {
@ -528,22 +528,24 @@ impl<'a> Fold<()> for Inferencer<'a> {
} }
} }
type InferenceResult = Result<Type, String>; type InferenceResult = Result<Type, HashSet<String>>;
impl<'a> Inferencer<'a> { impl<'a> Inferencer<'a> {
/// Constrain a <: b /// Constrain a <: b
/// Currently implemented as unification /// 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) -> Result<(), HashSet<String>> {
self.unify(a, b, location) 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) -> Result<(), HashSet<String>> {
self.unifier self.unifier
.unify(a, b) .unify(a, b)
.map_err(|e| e.at(Some(*location)).to_display(self.unifier).to_string()) .map_err(|e| HashSet::from([
e.at(Some(*location)).to_display(self.unifier).to_string(),
]))
} }
fn infer_pattern(&mut self, pattern: &ast::Expr<()>) -> Result<(), String> { fn infer_pattern(&mut self, pattern: &ast::Expr<()>) -> Result<(), HashSet<String>> {
match &pattern.node { match &pattern.node {
ExprKind::Name { id, .. } => { ExprKind::Name { id, .. } => {
if !self.defined_identifiers.contains(id) { if !self.defined_identifiers.contains(id) {
@ -592,9 +594,9 @@ impl<'a> Inferencer<'a> {
.map(|v| v.name) .map(|v| v.name)
.rev() .rev()
.collect(); .collect();
self.unifier.unify_call(&call, ty, sign, &required).map_err(|e| { self.unifier.unify_call(&call, ty, sign, &required).map_err(|e| HashSet::from([
e.at(Some(location)).to_display(self.unifier).to_string() e.at(Some(location)).to_display(self.unifier).to_string(),
})?; ]))?;
return Ok(sign.ret); return Ok(sign.ret);
} }
} }
@ -623,7 +625,7 @@ impl<'a> Inferencer<'a> {
location: Location, location: Location,
args: Arguments, args: Arguments,
body: ast::Expr<()>, body: ast::Expr<()>,
) -> Result<ast::Expr<Option<Type>>, String> { ) -> Result<ast::Expr<Option<Type>>, HashSet<String>> {
if !args.posonlyargs.is_empty() if !args.posonlyargs.is_empty()
|| args.vararg.is_some() || args.vararg.is_some()
|| !args.kwonlyargs.is_empty() || !args.kwonlyargs.is_empty()
@ -692,7 +694,7 @@ impl<'a> Inferencer<'a> {
location: Location, location: Location,
elt: ast::Expr<()>, elt: ast::Expr<()>,
mut generators: Vec<Comprehension>, mut generators: Vec<Comprehension>,
) -> Result<ast::Expr<Option<Type>>, String> { ) -> Result<ast::Expr<Option<Type>>, HashSet<String>> {
if generators.len() != 1 { if generators.len() != 1 {
return report_error( return report_error(
"Only 1 generator statement for list comprehension is supported", "Only 1 generator statement for list comprehension is supported",
@ -765,7 +767,7 @@ impl<'a> Inferencer<'a> {
func: ast::Expr<()>, func: ast::Expr<()>,
mut args: Vec<ast::Expr<()>>, mut args: Vec<ast::Expr<()>>,
keywords: Vec<Located<ast::KeywordData>>, keywords: Vec<Located<ast::KeywordData>>,
) -> Result<ast::Expr<Option<Type>>, String> { ) -> Result<ast::Expr<Option<Type>>, HashSet<String>> {
let func = let func =
if let Located { location: func_location, custom, node: ExprKind::Name { id, ctx } } = if let Located { location: func_location, custom, node: ExprKind::Name { id, ctx } } =
func func
@ -899,7 +901,9 @@ impl<'a> Inferencer<'a> {
.collect(); .collect();
self.unifier self.unifier
.unify_call(&call, func.custom.unwrap(), sign, &required) .unify_call(&call, func.custom.unwrap(), sign, &required)
.map_err(|e| e.at(Some(location)).to_display(self.unifier).to_string())?; .map_err(|e| HashSet::from([
e.at(Some(location)).to_display(self.unifier).to_string(),
]))?;
return Ok(Located { return Ok(Located {
location, location,
custom: Some(sign.ret), custom: Some(sign.ret),
@ -1073,8 +1077,11 @@ impl<'a> Inferencer<'a> {
) -> InferenceResult { ) -> InferenceResult {
let boolean = self.primitives.bool; let boolean = self.primitives.bool;
for (a, b, c) in izip!(once(left).chain(comparators), comparators, ops) { for (a, b, c) in izip!(once(left).chain(comparators), comparators, ops) {
let method = let method = comparison_name(c)
comparison_name(c).ok_or_else(|| "unsupported comparator".to_string())?.into(); .ok_or_else(|| HashSet::from([
"unsupported comparator".to_string()
]))?
.into();
self.build_method_call( self.build_method_call(
a.location, a.location,
method, method,
@ -1105,7 +1112,7 @@ impl<'a> Inferencer<'a> {
ExprKind::Constant { value: ast::Constant::Int(val), .. } => { ExprKind::Constant { value: ast::Constant::Int(val), .. } => {
// the index is a constant, so value can be a sequence. // the index is a constant, so value can be a sequence.
let ind: Option<i32> = (*val).try_into().ok(); let ind: Option<i32> = (*val).try_into().ok();
let ind = ind.ok_or_else(|| "Index must be int32".to_string())?; let ind = ind.ok_or_else(|| HashSet::from(["Index must be int32".to_string()]))?;
let map = once(( let map = once((
ind.into(), ind.into(),
RecordField::new(ty, ctx == &ExprContext::Store, Some(value.location)), RecordField::new(ty, ctx == &ExprContext::Store, Some(value.location)),

View File

@ -43,8 +43,9 @@ impl SymbolResolver for Resolver {
unimplemented!() unimplemented!()
} }
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> { fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> {
self.id_to_def.get(&id).cloned().ok_or_else(|| "Unknown identifier".to_string()) self.id_to_def.get(&id).cloned()
.ok_or_else(|| HashSet::from(["Unknown identifier".to_string()]))
} }
fn get_string_id(&self, _: &str) -> i32 { fn get_string_id(&self, _: &str) -> i32 {

View File

@ -743,7 +743,6 @@ impl Unifier {
(TConstant { value: val1, ty: ty1, .. }, TConstant { value: val2, ty: ty2, .. }) => { (TConstant { value: val1, ty: ty1, .. }, TConstant { value: val2, ty: ty2, .. }) => {
if val1 != val2 { if val1 != val2 {
eprintln!("VALUE MISMATCH: lhs={val1:?} rhs={val2:?} eq={}", val1 == val2);
return self.incompatible_types(a, b) return self.incompatible_types(a, b)
} }
self.unify_impl(*ty1, *ty2, false)?; self.unify_impl(*ty1, *ty2, false)?;

View File

@ -10,6 +10,7 @@ use nac3core::{
use nac3parser::ast::{self, StrRef}; use nac3parser::ast::{self, StrRef};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use std::collections::HashSet;
pub struct ResolverInternal { pub struct ResolverInternal {
pub id_to_type: Mutex<HashMap<StrRef, Type>>, pub id_to_type: Mutex<HashMap<StrRef, Type>>,
@ -61,8 +62,9 @@ impl SymbolResolver for Resolver {
unimplemented!() unimplemented!()
} }
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> { fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> {
self.0.id_to_def.lock().get(&id).copied().ok_or_else(|| "Undefined identifier".to_string()) self.0.id_to_def.lock().get(&id).copied()
.ok_or_else(|| HashSet::from(["Undefined identifier".to_string()]))
} }
fn get_string_id(&self, s: &str) -> i32 { fn get_string_id(&self, s: &str) -> i32 {

View File

@ -8,6 +8,7 @@ use inkwell::{
}; };
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, fs, path::Path, sync::Arc}; use std::{collections::HashMap, fs, path::Path, sync::Arc};
use std::collections::HashSet;
use nac3core::{ use nac3core::{
codegen::{ codegen::{
@ -74,24 +75,28 @@ fn handle_typevar_definition(
def_list: &[Arc<RwLock<TopLevelDef>>], def_list: &[Arc<RwLock<TopLevelDef>>],
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
) -> Result<Type, String> { ) -> Result<Type, HashSet<String>> {
let ExprKind::Call { func, args, .. } = &var.node else { let ExprKind::Call { func, args, .. } = &var.node else {
return Err(format!( return Err(HashSet::from([
"expression {var:?} cannot be handled as a generic parameter in global scope" format!(
)) "expression {var:?} cannot be handled as a generic parameter in global scope"
),
]))
}; };
match &func.node { match &func.node {
ExprKind::Name { id, .. } if id == &"TypeVar".into() => { ExprKind::Name { id, .. } if id == &"TypeVar".into() => {
let ExprKind::Constant { value: Constant::Str(ty_name), .. } = &args[0].node else { let ExprKind::Constant { value: Constant::Str(ty_name), .. } = &args[0].node else {
return Err(format!("Expected string constant for first parameter of `TypeVar`, got {:?}", &args[0].node)) return Err(HashSet::from([
format!("Expected string constant for first parameter of `TypeVar`, got {:?}", &args[0].node),
]))
}; };
let generic_name: StrRef = ty_name.to_string().into(); let generic_name: StrRef = ty_name.to_string().into();
let constraints = args let constraints = args
.iter() .iter()
.skip(1) .skip(1)
.map(|x| -> Result<Type, String> { .map(|x| -> Result<Type, HashSet<String>> {
let ty = parse_ast_to_type_annotation_kinds( let ty = parse_ast_to_type_annotation_kinds(
resolver, resolver,
def_list, def_list,
@ -109,7 +114,9 @@ fn handle_typevar_definition(
let loc = func.location; let loc = func.location;
if constraints.len() == 1 { if constraints.len() == 1 {
return Err(format!("A single constraint is not allowed (at {loc})")) return Err(HashSet::from([
format!("A single constraint is not allowed (at {loc})"),
]))
} }
Ok(unifier.get_fresh_var_with_range(&constraints, Some(generic_name), Some(loc)).0) Ok(unifier.get_fresh_var_with_range(&constraints, Some(generic_name), Some(loc)).0)
@ -117,14 +124,18 @@ fn handle_typevar_definition(
ExprKind::Name { id, .. } if id == &"ConstGeneric".into() => { ExprKind::Name { id, .. } if id == &"ConstGeneric".into() => {
if args.len() != 2 { if args.len() != 2 {
return Err(format!("Expected 2 arguments for `ConstGeneric`, got {}", args.len())) return Err(HashSet::from([
format!("Expected 2 arguments for `ConstGeneric`, got {}", args.len()),
]))
} }
let ExprKind::Constant { value: Constant::Str(ty_name), .. } = &args[0].node else { let ExprKind::Constant { value: Constant::Str(ty_name), .. } = &args[0].node else {
return Err(format!( return Err(HashSet::from([
"Expected string constant for first parameter of `ConstGeneric`, got {:?}", format!(
&args[0].node "Expected string constant for first parameter of `ConstGeneric`, got {:?}",
)) &args[0].node
),
]))
}; };
let generic_name: StrRef = ty_name.to_string().into(); let generic_name: StrRef = ty_name.to_string().into();
@ -145,9 +156,11 @@ fn handle_typevar_definition(
Ok(unifier.get_fresh_const_generic_var(constraint, Some(generic_name), Some(loc)).0) Ok(unifier.get_fresh_const_generic_var(constraint, Some(generic_name), Some(loc)).0)
} }
_ => Err(format!( _ => Err(HashSet::from([
"expression {var:?} cannot be handled as a generic parameter in global scope" format!(
)) "expression {var:?} cannot be handled as a generic parameter in global scope"
),
]))
} }
} }