use lex_syntax::syntax::*;
use lex_syntax::{
load_package, load_program, load_program_from_str, load_program_with_root, LoadError,
};
use std::fs;
fn write(dir: &std::path::Path, name: &str, src: &str) {
fs::write(dir.join(name), src).unwrap();
}
fn fn_names(prog: &Program) -> Vec<String> {
prog.items
.iter()
.filter_map(|i| match i {
Item::FnDecl(fd) => Some(fd.name.clone()),
_ => None,
})
.collect()
}
fn type_names(prog: &Program) -> Vec<String> {
prog.items
.iter()
.filter_map(|i| match i {
Item::TypeDecl(td) => Some(td.name.clone()),
_ => None,
})
.collect()
}
fn unique_fn<'a>(prog: &'a Program, suffix: &str) -> &'a FnDecl {
let matches: Vec<&FnDecl> = prog
.items
.iter()
.filter_map(|i| match i {
Item::FnDecl(fd) if fd.name == suffix || fd.name.ends_with(&format!(".{suffix}")) => {
Some(fd)
}
_ => None,
})
.collect();
assert_eq!(
matches.len(),
1,
"expected exactly one fn matching `{suffix}`, found {}: {:?}",
matches.len(),
matches.iter().map(|f| &f.name).collect::<Vec<_>>(),
);
matches[0]
}
fn count_with_suffix(prog: &Program, suffix: &str) -> usize {
prog.items
.iter()
.filter(|i| match i {
Item::FnDecl(fd) => fd.name == suffix || fd.name.ends_with(&format!(".{suffix}")),
_ => false,
})
.count()
}
#[test]
fn two_file_project_mangles_imported_names() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"models.lex",
r#"type Status = Healthy | Sick
fn label(s :: Status) -> Str {
match s {
Healthy => "ok",
Sick => "nope",
}
}
"#,
);
write(
dir.path(),
"main.lex",
r#"import "./models" as m
fn main(s :: m.Status) -> Str { m.label(s) }
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
let fns = fn_names(&prog);
let types = type_names(&prog);
assert!(
fns.iter().any(|n| n.ends_with(".label") && n.contains('_')),
"expected mangled fn ending in `.label` with `_` separator, got: {fns:?}",
);
assert!(
types.iter().any(|n| n.ends_with(".Status") && n.contains('_')),
"expected mangled type ending in `.Status`, got: {types:?}",
);
assert!(fns.contains(&"main".to_string()), "got fns: {fns:?}");
}
#[test]
fn root_calls_imported_function_via_alias() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"helpers.lex",
r#"fn double(x :: Int) -> Int { x + x }
"#,
);
write(
dir.path(),
"main.lex",
r#"import "./helpers" as h
fn main(x :: Int) -> Int { h.double(x) }
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
let main_fn = unique_fn(&prog, "main");
let imported = unique_fn(&prog, "double");
if let Expr::Call { callee, .. } = &*main_fn.body.result {
if let Expr::Var(name) = &**callee {
assert_eq!(
name, &imported.name,
"main's call should reference the imported fn's mangled name"
);
return;
}
}
panic!("main body not rewritten as expected: {:?}", main_fn.body.result);
}
#[test]
fn unqualified_local_call_inside_imported_file_is_mangled() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"helpers.lex",
r#"fn inner(x :: Int) -> Int { x + 1 }
fn outer(x :: Int) -> Int { inner(x) }
"#,
);
write(
dir.path(),
"main.lex",
r#"import "./helpers" as h
fn main(x :: Int) -> Int { h.outer(x) }
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
let outer = unique_fn(&prog, "outer");
let inner = unique_fn(&prog, "inner");
if let Expr::Call { callee, .. } = &*outer.body.result {
if let Expr::Var(name) = &**callee {
assert_eq!(name, &inner.name, "outer's body should call inner via mangled name");
return;
}
}
panic!("outer body not rewritten: {:?}", outer.body.result);
}
#[test]
fn shadowed_let_binding_is_not_mangled() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"helpers.lex",
r#"fn inner(x :: Int) -> Int { x }
fn caller(x :: Int) -> Int {
let inner := x + 100
inner
}
"#,
);
write(
dir.path(),
"main.lex",
r#"import "./helpers" as h
fn main(x :: Int) -> Int { h.caller(x) }
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
let caller = unique_fn(&prog, "caller");
if let Expr::Var(name) = &*caller.body.result {
assert_eq!(name, "inner", "let-bound var should not be mangled");
return;
}
panic!("caller result not a Var: {:?}", caller.body.result);
}
#[test]
fn transitive_imports_chain() {
let dir = tempfile::tempdir().unwrap();
write(dir.path(), "c.lex", "fn z(x :: Int) -> Int { x }\n");
write(
dir.path(),
"b.lex",
r#"import "./c" as c
fn y(x :: Int) -> Int { c.z(x) }
"#,
);
write(
dir.path(),
"a.lex",
r#"import "./b" as b
fn main(x :: Int) -> Int { b.y(x) }
"#,
);
let prog = load_program(&dir.path().join("a.lex")).expect("load");
let fns = fn_names(&prog);
assert!(fns.contains(&"main".to_string()));
assert_eq!(count_with_suffix(&prog, "y"), 1);
assert_eq!(count_with_suffix(&prog, "z"), 1);
}
#[test]
fn cycle_detection_errors_with_chain() {
let dir = tempfile::tempdir().unwrap();
write(dir.path(), "a.lex", "import \"./b\" as b\nfn fa() -> Int { 1 }\n");
write(dir.path(), "b.lex", "import \"./a\" as a\nfn fb() -> Int { 2 }\n");
let err = load_program(&dir.path().join("a.lex")).expect_err("expected cycle error");
let msg = format!("{err}");
match err {
LoadError::Cycle { .. } => {
assert!(msg.contains("a.lex"), "msg: {msg}");
assert!(msg.contains("b.lex"), "msg: {msg}");
}
other => panic!("expected Cycle, got: {other:?}"),
}
}
#[test]
fn missing_file_errors_clearly() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"main.lex",
"import \"./nonexistent\" as x\nfn main() -> Int { 0 }\n",
);
let err = load_program(&dir.path().join("main.lex")).expect_err("expected missing-file error");
match err {
LoadError::NotFound { reference, .. } => assert_eq!(reference, "./nonexistent"),
other => panic!("expected NotFound, got: {other:?}"),
}
}
#[test]
fn string_source_rejects_local_imports() {
let err = load_program_from_str("import \"./foo\" as f\nfn main() -> Int { 0 }\n")
.expect_err("expected rejection");
matches!(err, LoadError::LocalImportInStringSource);
}
#[test]
fn string_source_accepts_std_imports() {
let prog = load_program_from_str("import \"std.io\" as io\nfn main() -> Int { 0 }\n")
.expect("std import in string source");
assert!(prog
.items
.iter()
.any(|i| matches!(i, Item::Import(imp) if imp.reference == "std.io")));
}
#[test]
fn diamond_imports_share_one_module_identity() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"shared.lex",
"fn util(x :: Int) -> Int { x + 1 }\n",
);
write(
dir.path(),
"left.lex",
"import \"./shared\" as s\nfn lhs(x :: Int) -> Int { s.util(x) }\n",
);
write(
dir.path(),
"right.lex",
"import \"./shared\" as s\nfn rhs(x :: Int) -> Int { s.util(x) }\n",
);
write(
dir.path(),
"main.lex",
r#"import "./left" as l
import "./right" as r
fn main(x :: Int) -> Int { l.lhs(x) + r.rhs(x) }
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
assert_eq!(
count_with_suffix(&prog, "util"),
1,
"shared.util should appear once after dedupe; got fns: {:?}",
fn_names(&prog),
);
let util = unique_fn(&prog, "util");
let lhs = unique_fn(&prog, "lhs");
let rhs = unique_fn(&prog, "rhs");
fn callee_name(body: &Block) -> &str {
if let Expr::Call { callee, .. } = &*body.result {
if let Expr::Var(name) = &**callee {
return name;
}
}
panic!("body result not a Call(Var): {:?}", body.result);
}
assert_eq!(callee_name(&lhs.body), util.name);
assert_eq!(callee_name(&rhs.body), util.name);
}
#[test]
fn diamond_with_imported_type_unifies_across_branches() {
let dir = tempfile::tempdir().unwrap();
write(dir.path(), "models.lex", "type Report = { score :: Int }\n");
write(
dir.path(),
"scorer.lex",
"import \"./models\" as m\nfn build_report(s :: Int) -> m.Report { { score: s } }\n",
);
write(
dir.path(),
"verdict.lex",
"import \"./models\" as m\nfn read_score(r :: m.Report) -> Int { r.score }\n",
);
write(
dir.path(),
"main.lex",
r#"import "./scorer" as s
import "./verdict" as v
fn main() -> Int {
let r := s.build_report(7)
v.read_score(r)
}
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
let builder = unique_fn(&prog, "build_report");
let reader = unique_fn(&prog, "read_score");
let builder_ret = match &builder.return_type {
TypeExpr::Named { name, .. } => name.clone(),
other => panic!("expected Named return type, got {other:?}"),
};
let reader_param = match &reader.params[0].ty {
TypeExpr::Named { name, .. } => name.clone(),
other => panic!("expected Named param type, got {other:?}"),
};
assert_eq!(
builder_ret, reader_param,
"diamond branches should resolve to the same nominal type",
);
}
#[test]
fn diamond_via_dotdot_paths_share_one_module_identity() {
let dir = tempfile::tempdir().unwrap();
let lib = dir.path().join("lib");
let src = dir.path().join("src");
let tests = dir.path().join("tests");
std::fs::create_dir_all(&lib).unwrap();
std::fs::create_dir_all(&src).unwrap();
std::fs::create_dir_all(&tests).unwrap();
write(&lib, "shared.lex", "fn util(x :: Int) -> Int { x + 1 }\n");
write(&src, "body.lex",
"import \"../lib/shared\" as s\nfn wrap(x :: Int) -> Int { s.util(x) }\n");
write(&tests, "test.lex",
"import \"../src/body\" as b\nimport \"../lib/shared\" as s\nfn run(x :: Int) -> Int { b.wrap(s.util(x)) }\n");
let prog = load_program(&tests.join("test.lex")).expect("load");
assert_eq!(
count_with_suffix(&prog, "util"),
1,
"shared.util should appear once after cross-dir dedupe; got fns: {:?}",
fn_names(&prog),
);
}
#[test]
fn std_import_in_imported_file_is_preserved() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"io_helper.lex",
r#"import "std.io" as io
fn say(s :: Str) -> [io] Nil { io.print(s) }
"#,
);
write(
dir.path(),
"main.lex",
r#"import "./io_helper" as h
fn main(s :: Str) -> [io] Nil { h.say(s) }
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
let std_imports: Vec<&Import> = prog
.items
.iter()
.filter_map(|i| match i {
Item::Import(imp) => Some(imp),
_ => None,
})
.collect();
assert_eq!(std_imports.len(), 1, "got: {std_imports:?}");
assert_eq!(std_imports[0].reference, "std.io");
let say = unique_fn(&prog, "say");
if let Expr::Call { callee, .. } = &*say.body.result {
if let Expr::Field { value, field } = &**callee {
if let Expr::Var(alias) = &**value {
assert_eq!(alias, "io");
assert_eq!(field, "print");
return;
}
}
}
panic!("say body not preserving io.print: {:?}", say.body.result);
}
#[test]
fn package_import_via_lex_toml_path_dep() {
let dir = tempfile::tempdir().unwrap();
let math_dir = dir.path().join("lex-math");
let math_src = math_dir.join("src");
let app_dir = dir.path().join("app");
std::fs::create_dir_all(&math_src).unwrap();
std::fs::create_dir_all(&app_dir).unwrap();
write(&math_dir, "lex.toml", "[package]\nname = \"lex-math\"\nversion = \"0.1.0\"\n");
write(&math_src, "arith.lex", "fn add(a :: Int, b :: Int) -> Int { a + b }\n");
write(&app_dir, "lex.toml", concat!(
"[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n",
"[dependencies]\nlex-math = { path = \"../lex-math\" }\n",
));
write(&app_dir, "main.lex",
"import \"lex-math/arith\" as m\nfn main(x :: Int) -> Int { m.add(x, 1) }\n");
let prog = load_program(&app_dir.join("main.lex")).expect("load");
let fns = fn_names(&prog);
assert!(fns.contains(&"main".to_string()), "got fns: {fns:?}");
assert_eq!(count_with_suffix(&prog, "add"), 1, "got fns: {fns:?}");
}
#[test]
fn package_import_missing_from_toml_errors_clearly() {
let dir = tempfile::tempdir().unwrap();
write(dir.path(), "lex.toml",
"[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\n");
write(dir.path(), "main.lex",
"import \"no-such-pkg/foo\" as x\nfn main() -> Int { 0 }\n");
let err = load_program(&dir.path().join("main.lex"))
.expect_err("expected package error");
let msg = format!("{err}");
assert!(msg.contains("no-such-pkg"), "msg: {msg}");
}
#[test]
fn string_source_rejects_package_imports() {
let err = load_program_from_str("import \"lex-schema/schema\" as s\nfn main() -> Int { 0 }\n")
.expect_err("expected rejection");
matches!(err, LoadError::LocalImportInStringSource);
}
#[test]
fn examples_in_imported_file_mangle_local_references() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"widget.lex",
r#"fn helper(x :: Int) -> Int { x * 2 }
fn use_helper(n :: Int) -> Int
examples {
use_helper(5) => helper(5),
}
{
helper(n)
}
"#,
);
write(
dir.path(),
"main.lex",
r#"import "./widget" as w
fn run() -> Int { w.use_helper(3) }
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
let use_helper = unique_fn(&prog, "use_helper");
let helper = unique_fn(&prog, "helper");
assert_eq!(use_helper.examples.len(), 1, "expected one example case");
let ex = &use_helper.examples[0];
match &ex.expected {
Expr::Call { callee, .. } => match &**callee {
Expr::Var(name) => assert_eq!(
name, &helper.name,
"example's `expected` should reference helper via its mangled name",
),
other => panic!("example expected callee not a Var: {other:?}"),
},
other => panic!("example expected not a Call: {other:?}"),
}
}
#[test]
fn examples_in_imported_file_with_self_reference_mangles() {
let dir = tempfile::tempdir().unwrap();
write(
dir.path(),
"widget.lex",
r#"fn identity(n :: Int) -> Int
examples {
identity(7) => identity(7),
}
{
n
}
"#,
);
write(
dir.path(),
"main.lex",
r#"import "./widget" as w
fn run() -> Int { w.identity(1) }
"#,
);
let prog = load_program(&dir.path().join("main.lex")).expect("load");
let identity = unique_fn(&prog, "identity");
assert_eq!(identity.examples.len(), 1);
let ex = &identity.examples[0];
match &ex.expected {
Expr::Call { callee, .. } => match &**callee {
Expr::Var(name) => assert_eq!(
name, &identity.name,
"self-reference in example should mangle to the fn's own mangled name",
),
other => panic!("example expected callee not a Var: {other:?}"),
},
other => panic!("example expected not a Call: {other:?}"),
}
}
fn write_package(root: &std::path::Path) -> std::path::PathBuf {
let src = root.join("src");
fs::create_dir_all(&src).unwrap();
write(
&src,
"error.lex",
r#"fn code_missing() -> Str { "missing" }
fn code_type() -> Str { "type" }
"#,
);
write(
&src,
"main.lex",
r#"import "./error" as e
fn describe() -> Str { e.code_missing() }
"#,
);
src.join("main.lex")
}
#[test]
fn rooted_load_mangles_identically_from_two_different_directories() {
let a = tempfile::tempdir().unwrap();
let b = tempfile::tempdir().unwrap();
let entry_a = write_package(a.path());
let entry_b = write_package(b.path());
let mut names_a = fn_names(&load_program_with_root(&entry_a, a.path()).expect("load a"));
let mut names_b = fn_names(&load_program_with_root(&entry_b, b.path()).expect("load b"));
names_a.sort();
names_b.sort();
assert_eq!(
names_a, names_b,
"byte-identical package layouts must mangle to identical names \
regardless of where they are unpacked (#826)",
);
assert!(
names_a.iter().any(|n| n.starts_with("error_") && n.ends_with(".code_missing")),
"expected a mangled `error_<hash>.code_missing`, got: {names_a:?}",
);
}
#[test]
fn rooted_load_distinguishes_same_stem_files_in_different_subdirs() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
fs::create_dir_all(root.join("src/one")).unwrap();
fs::create_dir_all(root.join("src/two")).unwrap();
write(&root.join("src/one"), "util.lex", "fn tag() -> Int { 1 }\n");
write(&root.join("src/two"), "util.lex", "fn tag() -> Int { 2 }\n");
write(
&root.join("src"),
"main.lex",
r#"import "./one/util" as a
import "./two/util" as b
fn total() -> Int { a.tag() + b.tag() }
"#,
);
let prog = load_program_with_root(&root.join("src/main.lex"), root).expect("load");
let tags: Vec<String> = fn_names(&prog).into_iter().filter(|n| n.ends_with(".tag")).collect();
assert_eq!(tags.len(), 2, "both `util.lex` files' `tag` must survive: {tags:?}");
assert_ne!(tags[0], tags[1], "same-stem files in different dirs must not collide: {tags:?}");
}
#[test]
fn rooted_load_still_resolves_imports_from_outside_the_root() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
fs::create_dir_all(root.join("pkg/src")).unwrap();
fs::create_dir_all(root.join("outside")).unwrap();
write(&root.join("outside"), "shared.lex", "fn shared_id() -> Int { 7 }\n");
write(
&root.join("pkg/src"),
"main.lex",
r#"import "../../outside/shared" as s
fn use_shared() -> Int { s.shared_id() }
"#,
);
let prog = load_program_with_root(&root.join("pkg/src/main.lex"), &root.join("pkg"))
.expect("load");
let names = fn_names(&prog);
assert!(names.contains(&"use_shared".to_string()), "got: {names:?}");
assert!(
names.iter().any(|n| n.ends_with(".shared_id")),
"out-of-root import must still be merged and mangled, got: {names:?}",
);
}
#[test]
fn unrooted_load_still_keys_on_the_absolute_path() {
let a = tempfile::tempdir().unwrap();
let b = tempfile::tempdir().unwrap();
let entry_a = write_package(a.path());
let entry_b = write_package(b.path());
let names_a = fn_names(&load_program(&entry_a).expect("load a"));
let names_b = fn_names(&load_program(&entry_b).expect("load b"));
assert_ne!(names_a, names_b);
}
fn write_dense_package(root: &std::path::Path) -> Vec<std::path::PathBuf> {
let src = root.join("src");
fs::create_dir_all(&src).unwrap();
write(&src, "error.lex", "fn code() -> Str { \"e\" }\nfn fmt() -> Str { \"f\" }\n");
for name in ["a.lex", "b.lex", "c.lex"] {
write(
&src,
name,
"import \"./error\" as e\nfn use_it() -> Str { e.code() }\n",
);
}
write(&src, "alone.lex", "fn solo() -> Int { 1 }\n");
let mut entries: Vec<std::path::PathBuf> = fs::read_dir(&src)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.extension().and_then(|x| x.to_str()) == Some("lex"))
.collect();
entries.sort();
entries
}
#[test]
fn load_package_emits_each_file_once_however_many_import_it() {
let dir = tempfile::tempdir().unwrap();
let entries = write_dense_package(dir.path());
let per_file_total: usize = entries
.iter()
.map(|e| fn_names(&load_program_with_root(e, dir.path()).expect("load")).len())
.sum();
let pkg = load_package(&entries, dir.path(), "pkg").expect("load package");
let names = fn_names(&pkg.program);
assert_eq!(
names.len(), 6,
"one entry per declaration in the package, got: {names:?}",
);
let unique: std::collections::BTreeSet<&String> = names.iter().collect();
assert_eq!(unique.len(), names.len(), "no declaration appears twice: {names:?}");
assert!(
per_file_total > names.len(),
"per-entry loads must be the redundant case this replaces \
({per_file_total} vs {})",
names.len(),
);
assert_eq!(
names.iter().filter(|n| n.starts_with("error_")).count(), 2,
"the shared file's declarations appear once each, got: {names:?}",
);
}
#[test]
fn load_package_mangles_every_file_including_the_entries() {
let dir = tempfile::tempdir().unwrap();
let entries = write_dense_package(dir.path());
let pkg = load_package(&entries, dir.path(), "pkg").expect("load package");
let names = fn_names(&pkg.program);
assert!(
names.iter().all(|n| n.contains('.')),
"every declaration carries its file's prefix, got: {names:?}",
);
assert!(
names.iter().any(|n| n.starts_with("alone_") && n.ends_with(".solo")),
"a file nobody imports is mangled too, got: {names:?}",
);
}
#[test]
fn load_package_namespaces_identical_layouts_apart() {
let a = tempfile::tempdir().unwrap();
let b = tempfile::tempdir().unwrap();
let ea = write_dense_package(a.path());
let eb = write_dense_package(b.path());
let same = fn_names(&load_package(&ea, a.path(), "same").expect("a").program);
let other = fn_names(&load_package(&eb, b.path(), "same").expect("b").program);
assert_eq!(same, other, "one namespace, one layout: identical names");
let renamed = fn_names(&load_package(&eb, b.path(), "different").expect("b").program);
assert!(
renamed.iter().zip(&same).all(|(x, y)| x != y),
"a different namespace must rename every declaration:\n{renamed:?}\n{same:?}",
);
}
#[test]
fn load_package_attributes_imports_to_the_declaring_file() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
write(&src, "helper.lex", "import \"std.str\" as str\nfn shout(s :: Str) -> Str { str.to_upper(s) }\n");
write(&src, "main.lex", "import \"./helper\" as h\nfn go() -> Str { h.shout(\"a\") }\n");
let entries = vec![src.join("helper.lex"), src.join("main.lex")];
let pkg = load_package(&entries, dir.path(), "pkg").expect("load package");
let helper = pkg.imports_by_file.get("src/helper.lex").expect("helper keyed");
let main = pkg.imports_by_file.get("src/main.lex").expect("main keyed");
assert!(helper.contains("std.str"), "helper declares std.str, got: {helper:?}");
assert!(
main.is_empty(),
"main imports only ./helper, which is not a module import: {main:?}",
);
}
#[test]
fn load_package_rejects_one_alias_bound_to_two_modules() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
write(&src, "a.lex", "import \"std.str\" as m\nfn one(s :: Str) -> Int { m.len(s) }\n");
write(&src, "b.lex", "import \"std.list\" as m\nfn two(l :: List[Int]) -> Int { m.len(l) }\n");
let entries = vec![src.join("a.lex"), src.join("b.lex")];
let err = load_package(&entries, dir.path(), "pkg").expect_err("must be rejected");
match err {
LoadError::ConflictingAlias { alias, .. } => assert_eq!(alias, "m"),
other => panic!("expected ConflictingAlias, got {other:?}"),
}
write(&src, "b.lex", "import \"std.str\" as m\nfn two(s :: Str) -> Int { m.len(s) }\n");
let pkg = load_package(&entries, dir.path(), "pkg").expect("same module is fine");
let imports = pkg.program.items.iter().filter(|i| matches!(i, Item::Import(_))).count();
assert_eq!(imports, 1, "one import item for one (module, alias) pair");
}