use super::*;
#[test]
fn overlong_total_path_is_rejected_without_panic() {
use std::panic::{AssertUnwindSafe, catch_unwind};
use crate::testing::TestContext;
let component = "a".repeat(255);
let num_components: usize = 300;
let mut path_str = String::with_capacity(num_components * (component.len() + 2));
for i in 0..num_components {
if i > 0 {
path_str.push_str("::");
}
path_str.push_str(&component);
}
let context = TestContext::default();
let lib_src = format!(
r#"
namespace {path_str}
pub proc add
add.1
end
"#
);
let parsed = catch_unwind(AssertUnwindSafe(|| context.parse_module(lib_src.as_str())));
assert!(
parsed.is_ok(),
"overlong total path caused a panic; expected a structured error"
);
let parsed = parsed.unwrap();
let err = parsed.expect_err("expected overlong path to be rejected");
assert_diagnostic!(err, "invalid item path: too long");
}
#[test]
fn public_item_import_exports_without_alias_symbol() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
r#"
namespace root
pub use {foo as bar} from dep
"#
))?;
let dep = context.parse_module(source_file!(
&context,
r#"
namespace dep
pub proc foo
push.1
end
"#
))?;
let library = Assembler::new(context.source_manager()).assemble_library("pkg", root, [dep])?;
let exports = library.manifest.exports().map(PackageExport::path).collect::<BTreeSet<_>>();
assert_eq!(exports.len(), 1);
assert!(exports.contains(&Arc::from(Path::new("::root::bar"))));
Ok(())
}
#[test]
fn link_import_module_and_item_forms_resolve() -> TestResult {
let context = TestContext::new();
let dep = context.parse_module(source_file!(
&context,
r#"
namespace lib::math
pub const VALUE = 7
pub type WordType = felt
pub proc procedure
nop
end
"#
))?;
let consumer = context.parse_module(source_file!(
&context,
r#"
namespace app
use lib::math
use lib::math as m
use {procedure as imported_proc, VALUE, WordType} from lib::math
pub const LOCAL = VALUE + 1
pub proc entry(value: WordType)
exec.imported_proc
exec.math::procedure
exec.m::procedure
push.LOCAL
drop
end
"#
))?;
let package =
Assembler::new(context.source_manager()).assemble_library("app", consumer, [dep])?;
let exports = package.manifest.exports().map(PackageExport::path).collect::<BTreeSet<_>>();
assert!(exports.contains(&Arc::from(Path::new("::app::entry"))));
Ok(())
}
#[test]
fn link_import_single_segment_module_import_resolves() -> TestResult {
let context = TestContext::new();
let dep = context.parse_module(source_file!(
&context,
r#"
namespace foo
pub proc procedure
nop
end
"#
))?;
let consumer = context.parse_module(source_file!(
&context,
r#"
namespace app
use foo
pub proc entry
exec.foo::procedure
end
"#
))?;
Assembler::new(context.source_manager()).assemble_library("app", consumer, [dep])?;
Ok(())
}
#[test]
fn link_import_item_form_rejects_submodule_target() -> TestResult {
let context = TestContext::new().with_warnings_as_errors(false);
let dep = context.parse_module(source_file!(
&context,
r#"
namespace dep
pub mod child
"#
))?;
let child = context.parse_module(source_file!(
&context,
r#"
namespace dep::child
pub proc entry
nop
end
"#
))?;
let consumer = context.parse_module(source_file!(
&context,
r#"
namespace app
use {child} from dep
pub proc entry
nop
end
"#
))?;
let err = Assembler::new(context.source_manager())
.assemble_library("app", consumer, [dep, child])
.expect_err("item import of a submodule should be rejected");
assert_diagnostic!(&err, "item import target '::dep::child' resolved to a module");
Ok(())
}
#[test]
fn link_import_public_item_reexport_chain_resolves_order_independently() -> TestResult {
let context = TestContext::new();
let dep = context.parse_module(source_file!(
&context,
r#"
namespace dep
pub const VALUE = 1
"#
))?;
let mid = context.parse_module(source_file!(
&context,
r#"
namespace mid
pub use {VALUE as MID_VALUE} from dep
"#
))?;
let consumer = context.parse_module(source_file!(
&context,
r#"
namespace app
use {MID_VALUE as VALUE} from mid
pub proc entry
push.VALUE
drop
end
"#
))?;
Assembler::new(context.source_manager()).assemble_library("app", consumer, [mid, dep])?;
Ok(())
}
#[test]
fn link_import_self_relative_public_item_reexport_resolves() -> TestResult {
let context = TestContext::new();
let dep = context.parse_module(source_file!(
&context,
r#"
namespace dep
pub proc helper
nop
end
"#
))?;
let root = context.parse_module(source_file!(
&context,
r#"
namespace app
pub mod child
use {ALIAS as imported} from self::child
pub proc entry
exec.imported
exec.self::child::ALIAS
end
"#
))?;
let child = context.parse_module(source_file!(
&context,
r#"
namespace app::child
pub use {helper as ALIAS} from dep
"#
))?;
Assembler::new(context.source_manager()).assemble_library("app", root, [child, dep])?;
Ok(())
}
#[test]
fn link_import_public_item_reexport_cycle_is_rejected() -> TestResult {
let context = TestContext::new();
let a = context.parse_module(source_file!(
&context,
r#"
namespace a
pub use {B as A} from b
"#
))?;
let b = context.parse_module(source_file!(
&context,
r#"
namespace b
pub use {A as B} from a
"#
))?;
let consumer = context.parse_module(source_file!(
&context,
r#"
namespace app
use {A} from a
pub proc entry
push.A
drop
end
"#
))?;
let err = Assembler::new(context.source_manager())
.assemble_library("app", consumer, [a, b])
.expect_err("public item re-export cycle should be rejected");
assert_diagnostic!(&err, "import re-export cycle");
Ok(())
}
#[test]
fn link_import_public_item_reexport_cycle_with_self_relative_target_is_rejected() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
r#"
namespace root
pub mod child
pub use {B as A} from self::child
"#
))?;
let child = context.parse_module(source_file!(
&context,
r#"
namespace root::child
pub use {A as B} from root
"#
))?;
let consumer = context.parse_module(source_file!(
&context,
r#"
namespace app
use {A} from root
pub proc entry
push.A
drop
end
"#
))?;
let err = Assembler::new(context.source_manager())
.assemble_library("app", consumer, [root, child])
.expect_err("public item re-export cycle should be rejected");
assert_diagnostic!(&err, "import re-export cycle");
Ok(())
}
#[test]
fn private_procedure_reexport_is_callable_from_another_module() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
"namespace root\npub mod api\nuse {exposed as imported} from self::api\npub proc entry exec.api::exposed exec.imported call.api::exposed procref.api::exposed drop end\n"
))?;
let api = context.parse_module(source_file!(
&context,
"namespace root::api\nproc helper push.1 drop end\npub use {helper as exposed} from self\n"
))?;
let library = Assembler::new(context.source_manager()).assemble_library("pkg", root, [api])?;
let exports = library.manifest.exports().map(PackageExport::path).collect::<BTreeSet<_>>();
assert_eq!(exports.len(), 2);
assert!(exports.contains(&Arc::from(Path::new("::root::api::exposed"))));
Ok(())
}
#[test]
fn private_items_can_be_reexported_from_their_own_module() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
r#"
namespace root
type Hidden = felt
const VALUE = 7
proc helper nop end
pub use {Hidden as Exposed, VALUE as EXPOSED_VALUE, helper as exposed} from self
"#
))?;
let library = Assembler::new(context.source_manager()).assemble_library(
"pkg",
root,
None::<Box<Module>>,
)?;
let exports = library.manifest.exports().map(PackageExport::path).collect::<BTreeSet<_>>();
assert_eq!(exports.len(), 3);
assert!(exports.contains(&Arc::from(Path::new("::root::Exposed"))));
assert!(exports.contains(&Arc::from(Path::new("::root::EXPOSED_VALUE"))));
assert!(exports.contains(&Arc::from(Path::new("::root::exposed"))));
Ok(())
}
#[test]
fn private_import_can_be_reexported_from_its_own_module() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
"namespace root\nuse {foo as local} from dep\npub use {local as exposed} from self\n"
))?;
let dep =
context.parse_module(source_file!(&context, "namespace dep\npub proc foo nop end\n"))?;
let library = Assembler::new(context.source_manager()).assemble_library("pkg", root, [dep])?;
assert_eq!(library.manifest.exports().count(), 1);
assert_eq!(
library.manifest.exports().next().unwrap().path().as_ref(),
Path::new("::root::exposed")
);
Ok(())
}
#[test]
fn inaccessible_private_item_cannot_be_reexported() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
"namespace root\npub use {helper as exposed} from dep\n"
))?;
let dep =
context.parse_module(source_file!(&context, "namespace dep\nproc helper nop end\n"))?;
let err = Assembler::new(context.source_manager())
.assemble_library("pkg", root, [dep])
.expect_err("a re-export must not bypass declaration-site visibility");
assert_diagnostic!(err, "private symbol reference");
Ok(())
}
#[test]
fn public_alias_of_private_type_can_appear_in_public_signatures() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
"namespace root\npub mod api\nuse {Exposed as Word} from self::api\npub proc entry(value: Word) nop end\n"
))?;
let api = context.parse_module(source_file!(
&context,
"namespace root::api\ntype Hidden = felt\npub use {Hidden as Exposed} from ::root::api\npub proc local(value: Exposed) nop end\n"
))?;
let library = Assembler::new(context.source_manager()).assemble_library("pkg", root, [api])?;
assert_eq!(library.manifest.exports().count(), 3);
Ok(())
}
#[test]
fn package_visibility_allows_access_without_exporting_items() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
r#"
namespace root
pub mod api
pub(package) mod internal
pub(package) use {helper as local} from self::api
use {VALUE, Hidden} from self::api
pub use {helper as exposed, Hidden as Exposed} from self::api
pub proc entry exec.local exec.internal::helper push.VALUE drop end
proc typed(value: Hidden) nop end
"#
))?;
let api = context.parse_module(source_file!(
&context,
r#"
namespace root::api
pub(package) const VALUE = 1
pub(package) type Hidden = felt
pub(package) proc helper push.VALUE drop end
pub proc entry exec.helper end
"#
))?;
let internal = context.parse_module(source_file!(
&context,
"namespace root::internal\npub proc helper nop end\n"
))?;
let library =
Assembler::new(context.source_manager()).assemble_library("pkg", root, [api, internal])?;
let exports = library.manifest.exports().map(PackageExport::path).collect::<BTreeSet<_>>();
assert_eq!(exports.len(), 4);
assert!(library.manifest.get_module(Path::new("::root::api")).is_some());
assert!(library.manifest.get_module(Path::new("::root::internal")).is_none());
assert_eq!(library.manifest.get_module(Path::new("::root")).unwrap().submodules().len(), 1);
for path in ["::root::entry", "::root::exposed", "::root::Exposed", "::root::api::entry"] {
assert!(exports.contains(&Arc::from(Path::new(path))), "missing {path}");
}
Ok(())
}
#[test]
fn package_reexport_is_accessible_from_another_module() -> TestResult {
let context = TestContext::new();
let root = context.parse_module(source_file!(
&context,
r#"
namespace root
pub mod api
use {LOCAL_VALUE, LocalWord} from self::api
pub proc entry
exec.api::local_helper
push.LOCAL_VALUE drop
end
proc typed(value: LocalWord) nop end
"#
))?;
let api = context.parse_module(source_file!(
&context,
r#"
namespace root::api
const VALUE = 7
type Word = felt
proc helper nop end
pub(package) use {helper as local_helper, VALUE as LOCAL_VALUE, Word as LocalWord} from self
"#
))?;
let library = Assembler::new(context.source_manager()).assemble_library("pkg", root, [api])?;
assert_eq!(library.manifest.exports().count(), 1);
assert_eq!(
library.manifest.exports().next().unwrap().path().as_ref(),
Path::new("::root::entry")
);
Ok(())
}