nac3/nac3core/src/toplevel/test.rs
David Mak 8fd7216243 [core] toplevel/composer: Add lateinit_builtins
This is required for the new core_log and rtio_log functions, which take
a generic type as its parameter. However, in ARTIQ builtins are
initialized using one unifier and then actually used by another unifier.

lateinit_builtins workaround this issue by deferring the initialization
of functions requiring type variables until the actual unifier is ready.
2024-08-13 15:19:03 +08:00

835 lines
22 KiB
Rust

use super::*;
use crate::toplevel::helper::PrimDef;
use crate::typecheck::typedef::into_var_map;
use crate::{
codegen::CodeGenContext,
symbol_resolver::{SymbolResolver, ValueEnum},
toplevel::DefinitionId,
typecheck::{
type_inferencer::PrimitiveStore,
typedef::{Type, Unifier},
},
};
use indoc::indoc;
use nac3parser::ast::FileName;
use nac3parser::{ast::fold::Fold, parser::parse_program};
use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc};
use test_case::test_case;
struct ResolverInternal {
id_to_type: Mutex<HashMap<StrRef, Type>>,
id_to_def: Mutex<HashMap<StrRef, DefinitionId>>,
class_names: Mutex<HashMap<StrRef, Type>>,
}
impl ResolverInternal {
fn add_id_def(&self, id: StrRef, def: DefinitionId) {
self.id_to_def.lock().insert(id, def);
}
fn add_id_type(&self, id: StrRef, ty: Type) {
self.id_to_type.lock().insert(id, ty);
}
}
struct Resolver(Arc<ResolverInternal>);
impl SymbolResolver for Resolver {
fn get_default_param_value(
&self,
_: &ast::Expr,
) -> Option<crate::symbol_resolver::SymbolValue> {
unimplemented!()
}
fn get_symbol_type(
&self,
_: &mut Unifier,
_: &[Arc<RwLock<TopLevelDef>>],
_: &PrimitiveStore,
str: StrRef,
) -> Result<Type, String> {
self.0
.id_to_type
.lock()
.get(&str)
.copied()
.ok_or_else(|| format!("cannot find symbol `{str}`"))
}
fn get_symbol_value<'ctx>(
&self,
_: StrRef,
_: &mut CodeGenContext<'ctx, '_>,
) -> Option<ValueEnum<'ctx>> {
unimplemented!()
}
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> {
self.0
.id_to_def
.lock()
.get(&id)
.copied()
.ok_or_else(|| HashSet::from(["Unknown identifier".to_string()]))
}
fn get_string_id(&self, _: &str) -> i32 {
unimplemented!()
}
fn get_exception_id(&self, _tyid: usize) -> usize {
unimplemented!()
}
}
#[test_case(
vec![
indoc! {"
def fun(a: int32) -> int32:
return a
"},
indoc! {"
class A:
def __init__(self):
self.a: int32 = 3
"},
indoc! {"
class B:
def __init__(self):
self.b: float = 4.3
def fun(self):
self.b = self.b + 3.0
"},
indoc! {"
def foo(a: float):
a + 1.0
"},
indoc! {"
class C(B):
def __init__(self):
self.c: int32 = 4
self.a: bool = True
"},
];
"register"
)]
fn test_simple_register(source: Vec<&str>) {
let mut composer =
TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0;
for s in source {
let ast = parse_program(s, FileName::default()).unwrap();
let ast = ast[0].clone();
composer.register_top_level(ast, None, "", false).unwrap();
}
}
#[test_case(
indoc! {"
class A:
def foo(self):
pass
a = A()
"};
"register"
)]
fn test_simple_register_without_constructor(source: &str) {
let mut composer =
TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0;
let ast = parse_program(source, FileName::default()).unwrap();
let ast = ast[0].clone();
composer.register_top_level(ast, None, "", true).unwrap();
}
#[test_case(
&[
indoc! {"
def fun(a: int32) -> int32:
return a
"},
indoc! {"
def foo(a: float):
a + 1.0
"},
indoc! {"
def f(b: int64) -> int32:
return 3
"},
],
&[
"fn[[a:0], 0]",
"fn[[a:2], 4]",
"fn[[b:1], 0]",
],
&[
"fun",
"foo",
"f"
];
"function compose"
)]
fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
let mut composer =
TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0;
let internal_resolver = Arc::new(ResolverInternal {
id_to_def: Mutex::default(),
id_to_type: Mutex::default(),
class_names: Mutex::default(),
});
let resolver =
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
for s in source {
let ast = parse_program(s, FileName::default()).unwrap();
let ast = ast[0].clone();
let (id, def_id, ty) =
composer.register_top_level(ast, Some(resolver.clone()), "", false).unwrap();
internal_resolver.add_id_def(id, def_id);
if let Some(ty) = ty {
internal_resolver.add_id_type(id, ty);
}
}
composer.start_analysis(true).unwrap();
for (i, (def, _)) in composer.definition_ast_list.iter().skip(composer.builtin_num).enumerate()
{
let def = &*def.read();
if let TopLevelDef::Function { signature, name, .. } = def {
let ty_str = composer.unifier.internal_stringify(
*signature,
&mut |id| id.to_string(),
&mut |id| id.to_string(),
&mut None,
);
assert_eq!(ty_str, tys[i]);
assert_eq!(name, names[i]);
}
}
}
#[test_case(
&[
indoc! {"
class A():
a: int32
def __init__(self):
self.a = 3
def fun(self, b: B):
pass
def foo(self, a: T, b: V):
pass
"},
indoc! {"
class B(C):
def __init__(self):
pass
"},
indoc! {"
class C(A):
def __init__(self):
pass
def fun(self, b: B):
a = 1
pass
"},
indoc! {"
def foo(a: A):
pass
"},
indoc! {"
def ff(a: T) -> V:
pass
"}
],
&[];
"simple class compose"
)]
#[test_case(
&[
indoc! {"
class Generic_A(Generic[V], B):
a: int64
def __init__(self):
self.a = 123123123123
def fun(self, a: int32) -> V:
pass
"},
indoc! {"
class B:
aa: bool
def __init__(self):
self.aa = False
def foo(self, b: T):
pass
"}
],
&[];
"generic class"
)]
#[test_case(
&[
indoc! {"
def foo(a: list[int32], b: tuple[T, float]) -> A[B, bool]:
pass
"},
indoc! {"
class A(Generic[T, V]):
a: T
b: V
def __init__(self, v: V):
self.a = 1
self.b = v
def fun(self, a: T) -> V:
pass
"},
indoc! {"
def gfun(a: A[list[float], int32]):
pass
"},
indoc! {"
class B:
def __init__(self):
pass
"}
],
&[];
"list tuple generic"
)]
#[test_case(
&[
indoc! {"
class A(Generic[T, V]):
a: A[float, bool]
b: B
def __init__(self, a: A[float, bool], b: B):
self.a = a
self.b = b
def fun(self, a: A[float, bool]) -> A[bool, int32]:
pass
"},
indoc! {"
class B(A[int64, bool]):
def __init__(self):
pass
def foo(self, b: B) -> B:
pass
def bar(self, a: A[list[B], int32]) -> tuple[A[virtual[A[B, int32]], bool], B]:
pass
"}
],
&[];
"self1"
)]
#[test_case(
&[
indoc! {"
class A(Generic[T]):
a: int32
b: T
c: A[int64]
def __init__(self, t: T):
self.a = 3
self.b = T
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
pass
def foo(self, c: C):
pass
"},
indoc! {"
class B(Generic[V], A[float]):
d: C
def __init__(self):
pass
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
# override
pass
"},
indoc! {"
class C(B[bool]):
e: int64
def __init__(self):
pass
"}
],
&[];
"inheritance_override"
)]
#[test_case(
&[
indoc! {"
class A(Generic[T]):
def __init__(self):
pass
def fun(self, a: A[T]) -> A[T]:
pass
"}
],
&["application of type vars to generic class is not currently supported (at unknown:4:24)"];
"err no type var in generic app"
)]
#[test_case(
&[
indoc! {"
class A(B):
def __init__(self):
pass
"},
indoc! {"
class B(A):
def __init__(self):
pass
"}
],
&["cyclic inheritance detected"];
"cyclic1"
)]
#[test_case(
&[
indoc! {"
class A(B[bool, int64]):
def __init__(self):
pass
"},
indoc! {"
class B(Generic[V, T], C[int32]):
def __init__(self):
pass
"},
indoc! {"
class C(Generic[T], A):
def __init__(self):
pass
"},
],
&["cyclic inheritance detected"];
"cyclic2"
)]
#[test_case(
&[
indoc! {"
class A:
pass
"}
],
&["5: Class {\nname: \"A\",\ndef_id: DefinitionId(5),\nancestors: [CustomClassKind { id: DefinitionId(5), params: [] }],\nfields: [],\nmethods: [],\ntype_vars: []\n}"];
"simple pass in class"
)]
#[test_case(
&[indoc! {"
class A:
def __init__():
pass
"}],
&["__init__ method must have a `self` parameter (at unknown:2:5)"];
"err no self_1"
)]
#[test_case(
&[
indoc! {"
class A(B, Generic[T], C):
def __init__(self):
pass
"},
indoc! {"
class B:
def __init__(self):
pass
"},
indoc! {"
class C:
def __init__(self):
pass
"}
],
&["a class definition can only have at most one base class declaration and one generic declaration (at unknown:1:24)"];
"err multiple inheritance"
)]
#[test_case(
&[
indoc! {"
class A(Generic[T]):
a: int32
b: T
c: A[int64]
def __init__(self, t: T):
self.a = 3
self.b = T
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
pass
"},
indoc! {"
class B(Generic[V], A[float]):
def __init__(self):
pass
def fun(self, a: int32, b: T) -> list[virtual[B[int32]]]:
# override
pass
"}
],
&["method fun has same name as ancestors' method, but incompatible type"];
"err_incompatible_inheritance_method"
)]
#[test_case(
&[
indoc! {"
class A(Generic[T]):
a: int32
b: T
c: A[int64]
def __init__(self, t: T):
self.a = 3
self.b = T
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
pass
"},
indoc! {"
class B(Generic[V], A[float]):
a: int32
def __init__(self):
pass
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
# override
pass
"}
],
&["field `a` has already declared in the ancestor classes"];
"err_incompatible_inheritance_field"
)]
#[test_case(
&[
indoc! {"
class A:
def __init__(self):
pass
"},
indoc! {"
class A:
a: int32
def __init__(self):
pass
"}
],
&["duplicate definition of class `A` (at unknown:1:1)"];
"class same name"
)]
fn test_analyze(source: &[&str], res: &[&str]) {
let print = false;
let mut composer =
TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0;
let internal_resolver = make_internal_resolver_with_tvar(
vec![
("T".into(), vec![]),
("V".into(), vec![composer.primitives_ty.bool, composer.primitives_ty.int32]),
("G".into(), vec![composer.primitives_ty.bool, composer.primitives_ty.int64]),
],
&mut composer.unifier,
print,
);
let resolver =
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
for s in source {
let ast = parse_program(s, FileName::default()).unwrap();
let ast = ast[0].clone();
let (id, def_id, ty) = {
match composer.register_top_level(ast, Some(resolver.clone()), "", false) {
Ok(x) => x,
Err(msg) => {
if print {
println!("{msg}");
} else {
assert_eq!(res[0], msg);
}
return;
}
}
};
internal_resolver.add_id_def(id, def_id);
if let Some(ty) = ty {
internal_resolver.add_id_type(id, ty);
}
}
if let Err(msg) = composer.start_analysis(false) {
if print {
println!("{}", msg.iter().sorted().join("\n----------\n"));
} else {
assert_eq!(res[0], msg.iter().next().unwrap());
}
} else {
// skip 5 to skip primitives
let mut res_vec: Vec<String> = Vec::new();
for (def, _) in composer.definition_ast_list.iter().skip(composer.builtin_num) {
let def = &*def.read();
res_vec.push(format!("{}\n", def.to_string(composer.unifier.borrow_mut())));
}
insta::assert_debug_snapshot!(res_vec);
}
}
#[test_case(
vec![
indoc! {"
def fun(a: int32, b: int32) -> int32:
return a + b
"},
indoc! {"
def fib(n: int32) -> int32:
if n <= 2:
return 1
a = fib(n - 1)
b = fib(n - 2)
return fib(n - 1)
"}
],
&[];
"simple function"
)]
#[test_case(
vec![
indoc! {"
class A:
a: int32
def __init__(self):
self.a = 3
def fun(self) -> int32:
b = self.a + 3
return b * self.a
def clone(self) -> A:
SELF = self
return SELF
def sum(self) -> int32:
if self.a == 0:
return self.a
else:
a = self.a
self.a = self.a - 1
return a + self.sum()
def fib(self, a: int32) -> int32:
if a <= 2:
return 1
return self.fib(a - 1) + self.fib(a - 2)
"},
indoc! {"
def fun(a: A) -> int32:
return a.fun() + 2
"}
],
&[];
"simple class body"
)]
#[test_case(
vec![
indoc! {"
def fun(a: V, c: G, t: T) -> V:
b = a
cc = c
ret = fun(b, cc, t)
return ret * ret
"},
indoc! {"
def sum_three(l: list[V]) -> V:
return l[0] + l[1] + l[2]
"},
indoc! {"
def sum_sq_pair(p: tuple[V, V]) -> list[V]:
a = p[0]
b = p[1]
a = a**a
b = b**b
return [a, b]
"}
],
&[];
"type var fun"
)]
#[test_case(
vec![
indoc! {"
class A(Generic[G]):
a: G
b: bool
def __init__(self, aa: G):
self.a = aa
if 2 > 1:
self.b = True
else:
# self.b = False
pass
def fun(self, a: G) -> list[G]:
ret = [a, self.a]
return ret if self.b else self.fun(self.a)
"}
],
&[];
"type var class"
)]
#[test_case(
vec![
indoc! {"
class A:
def fun(self):
pass
"},
indoc!{"
class B:
a: int32
b: bool
def __init__(self):
# self.b = False
if 3 > 2:
self.a = 3
self.b = False
else:
self.a = 4
self.b = True
"}
],
&[];
"no_init_inst_check"
)]
fn test_inference(source: Vec<&str>, res: &[&str]) {
let print = true;
let mut composer =
TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0;
let internal_resolver = make_internal_resolver_with_tvar(
vec![
("T".into(), vec![]),
(
"V".into(),
vec![
composer.primitives_ty.float,
composer.primitives_ty.int32,
composer.primitives_ty.int64,
],
),
("G".into(), vec![composer.primitives_ty.bool, composer.primitives_ty.int64]),
],
&mut composer.unifier,
print,
);
let resolver =
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
for s in source {
let ast = parse_program(s, FileName::default()).unwrap();
let ast = ast[0].clone();
let (id, def_id, ty) = {
match composer.register_top_level(ast, Some(resolver.clone()), "", false) {
Ok(x) => x,
Err(msg) => {
if print {
println!("{msg}");
} else {
assert_eq!(res[0], msg);
}
return;
}
}
};
internal_resolver.add_id_def(id, def_id);
if let Some(ty) = ty {
internal_resolver.add_id_type(id, ty);
}
}
if let Err(msg) = composer.start_analysis(true) {
if print {
println!("{}", msg.iter().sorted().join("\n----------\n"));
} else {
assert_eq!(res[0], msg.iter().next().unwrap());
}
} else {
// skip 5 to skip primitives
let mut stringify_folder = TypeToStringFolder { unifier: &mut composer.unifier };
for (def, _) in composer.definition_ast_list.iter().skip(composer.builtin_num) {
let def = &*def.read();
if let TopLevelDef::Function { instance_to_stmt, name, .. } = def {
println!(
"=========`{}`: number of instances: {}===========",
name,
instance_to_stmt.len()
);
for inst in instance_to_stmt {
let ast = &inst.1.body;
for b in ast.iter() {
println!("{:?}", stringify_folder.fold_stmt(b.clone()).unwrap());
println!("--------------------");
}
println!("\n");
}
}
}
}
}
fn make_internal_resolver_with_tvar(
tvars: Vec<(StrRef, Vec<Type>)>,
unifier: &mut Unifier,
print: bool,
) -> Arc<ResolverInternal> {
let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None);
let list = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::List.id(),
fields: HashMap::new(),
params: into_var_map([list_elem_tvar]),
});
let res: Arc<ResolverInternal> = ResolverInternal {
id_to_def: Mutex::new(HashMap::from([("list".into(), PrimDef::List.id())])),
id_to_type: tvars
.into_iter()
.map(|(name, range)| {
(name, {
let tvar = unifier.get_fresh_var_with_range(range.as_slice(), None, None);
if print {
println!("{}: {:?}, typevar{}", name, tvar.ty, tvar.id);
}
tvar.ty
})
})
.collect::<HashMap<_, _>>()
.into(),
class_names: Mutex::new(HashMap::from([("list".into(), list)])),
}
.into();
if print {
println!();
}
res
}
struct TypeToStringFolder<'a> {
unifier: &'a mut Unifier,
}
impl<'a> Fold<Option<Type>> for TypeToStringFolder<'a> {
type TargetU = String;
type Error = String;
fn map_user(&mut self, user: Option<Type>) -> Result<Self::TargetU, Self::Error> {
Ok(if let Some(ty) = user {
self.unifier.internal_stringify(
ty,
&mut |id| format!("class{id}"),
&mut |id| format!("typevar{id}"),
&mut None,
)
} else {
"None".into()
})
}
}