use super::super::*;
use super::helpers::*;
pub(super) fn dyn_findings(
name: &str,
files: &[(&str, &str)],
module: &str,
) -> Result<Vec<String>, String> {
shape_findings("dyn", name, files, module, dyn_module_findings)
}
pub(super) fn dyn_mod(name: &str, body: &str) -> Result<Vec<String>, String> {
dyn_findings(
name,
&[("lib.rs", "pub mod m;\n"), ("m.rs", body)],
"crate::m",
)
}
pub(super) fn dyn_operand_findings(
name: &str,
files: &[(&str, &str)],
module: &str,
forbidden: &[&str],
deps: &[&str],
) -> Result<Vec<String>, String> {
operand_findings(
"dyn",
name,
files,
module,
forbidden,
deps,
dyn_operand_module_findings,
)
}
pub(super) fn dyn_operand_mod(
name: &str,
body: &str,
forbidden: &[&str],
) -> Result<Vec<String>, String> {
dyn_operand_findings(
name,
&[("lib.rs", "pub mod m;\n"), ("m.rs", body)],
"crate::m",
forbidden,
&[],
)
}
#[test]
pub(super) fn a_dyn_in_a_supertrait_or_assoc_type_bound_is_observed() {
assert!(
dyn_mod(
"supertrait-dyn",
"pub trait Facade: AsRef<Box<dyn crate::ports::Port>> {}\n",
)
.unwrap()
.contains(&"dyn crate::ports::Port exposed by trait crate::m::Facade".to_string()),
"a dyn in a supertrait generic argument must be observed",
);
assert!(
dyn_mod(
"assoc-bound-dyn",
"pub trait F { type Bar: AsRef<Box<dyn crate::ports::Port>>; }\n",
)
.unwrap()
.contains(&"dyn crate::ports::Port exposed by type trait crate::m::F::Bar".to_string()),
"a dyn in an associated-type bound must be observed",
);
}
#[test]
pub(super) fn a_dyn_in_an_inherent_impl_generic_bound_is_observed() {
let out = dyn_mod(
"dyn-impl-generics",
"pub struct Foo<T>(T);\nimpl<T: AsRef<Box<dyn crate::ports::Port>>> Foo<T> { pub fn m(&self) {} }\n",
)
.unwrap();
assert!(
out.iter()
.any(|f| f.contains("dyn crate::ports::Port") && f.contains("(generics: T)")),
"a dyn in an inherent-impl generic bound must be observed and name its bound: {out:?}"
);
}
#[test]
pub(super) fn dyn_operand_flags_a_named_trait_and_passes_others() {
assert_eq!(
dyn_operand_mod(
"named",
"pub fn c() -> Box<dyn crate::ports::Port> { todo!() }\n",
&["crate::ports::Port"],
)
.unwrap(),
["dyn crate::ports::Port exposed by fn crate::m::c"],
);
assert!(
dyn_operand_mod(
"other",
"pub fn e() -> Box<dyn std::error::Error> { todo!() }\n",
&["crate::ports::Port"],
)
.unwrap()
.is_empty(),
"a dyn of an unlisted trait passes",
);
}
#[test]
pub(super) fn dyn_operand_honors_a_module_prefix() {
assert_eq!(
dyn_operand_mod(
"prefix",
"pub fn c() -> Box<dyn crate::ports::Port> { todo!() }\n",
&["crate::ports"],
)
.unwrap(),
["dyn crate::ports::Port exposed by fn crate::m::c"],
);
}
#[test]
pub(super) fn dyn_operand_matches_a_reexported_trait_by_its_defining_path() {
let files = &[
(
"lib.rs",
"pub mod ports;\npub use crate::ports::Port;\npub mod m;\n",
),
("ports.rs", "pub trait Port {}\n"),
("m.rs", "pub fn c() -> Box<dyn crate::Port> { todo!() }\n"),
];
assert_eq!(
dyn_operand_findings(
"reexport-defining",
files,
"crate::m",
&["crate::ports::Port"],
&[],
)
.unwrap(),
["dyn crate::Port exposed by fn crate::m::c"],
"a dyn written through a re-export facade matches the forbidden defining path",
);
}
#[test]
pub(super) fn a_cfg_sibling_child_module_does_not_shadow_a_different_branchs_own_extern_principal()
{
let files = &[
(
"lib.rs",
"#[cfg(feature = \"u\")] pub mod platform;\n\
#[cfg(feature = \"w\")] #[path = \"win_platform.rs\"] pub mod platform;\n",
),
(
"platform.rs",
"pub mod traits { pub trait Marker {} }\npub fn open() -> u8 { 0 }\n",
),
(
"win_platform.rs",
"pub fn f() -> Box<dyn traits::Marker> { todo!() }\n",
),
];
assert_eq!(
dyn_operand_findings(
"cfg-sibling-childmod-shadow",
files,
"crate::platform",
&["traits::Marker"],
&["traits"],
)
.unwrap(),
["dyn traits::Marker exposed by fn crate::platform::f"],
"the w branch's own genuine extern dyn-principal must react, regardless of the u \
branch's own local mod traits",
);
}
#[test]
pub(super) fn a_cfg_split_module_with_two_inline_siblings_child_module_does_not_shadow_the_others_own_extern_principal()
{
let files = &[(
"lib.rs",
"#[cfg(feature = \"u\")] pub mod platform {\n\
pub mod traits { pub trait Marker {} }\n\
pub fn open() -> u8 { 0 }\n}\n\
#[cfg(feature = \"w\")] pub mod platform {\n\
pub fn f() -> Box<dyn traits::Marker> { todo!() }\n}\n",
)];
assert_eq!(
dyn_operand_findings(
"cfg-split-inline-inline-childmod-shadow",
files,
"crate::platform",
&["traits::Marker"],
&["traits"],
)
.unwrap(),
["dyn traits::Marker exposed by fn crate::platform::f"],
"the w arm's own genuine extern dyn-principal must react, regardless of the u arm's own \
local mod traits, even though both arms are inline and share lib.rs",
);
}
#[test]
pub(super) fn dyn_operand_ignores_auto_trait_markers() {
assert_eq!(
dyn_operand_mod(
"marker-port",
"pub fn c() -> Box<dyn crate::ports::Port + Send> { todo!() }\n",
&["crate::ports::Port"],
)
.unwrap(),
["dyn crate::ports::Port + Send exposed by fn crate::m::c"],
);
assert!(
dyn_operand_mod(
"marker-send",
"pub fn c() -> Box<dyn crate::ports::Port + Send> { todo!() }\n",
&["Send"],
)
.unwrap()
.is_empty(),
"the trailing Send marker is not the operand",
);
}
#[test]
pub(super) fn dyn_operand_matches_when_an_auto_trait_is_written_before_the_principal() {
assert_eq!(
dyn_operand_mod(
"auto-first",
"pub fn c() -> Box<dyn Send + crate::ports::Port> { todo!() }\n",
&["crate::ports::Port"],
)
.unwrap(),
["dyn Send + crate::ports::Port exposed by fn crate::m::c"],
);
assert_eq!(
dyn_operand_mod(
"auto-first-2",
"pub fn c() -> Box<dyn Send + Sync + crate::ports::Port> { todo!() }\n",
&["crate::ports::Port"],
)
.unwrap(),
["dyn Send + Sync + crate::ports::Port exposed by fn crate::m::c"],
);
}
#[test]
pub(super) fn dyn_operand_matches_a_dyn_nested_deep() {
assert_eq!(
dyn_operand_mod(
"nested",
"pub fn c() -> Vec<Box<dyn crate::ports::Port>> { todo!() }\n",
&["crate::ports::Port"],
)
.unwrap(),
["dyn crate::ports::Port exposed by fn crate::m::c"],
);
}
#[test]
pub(super) fn dyn_operand_empty_set_degenerates_to_any() {
let body = "pub fn c() -> Box<dyn crate::ports::Port> { todo!() }\n";
assert_eq!(
dyn_operand_mod("empty", body, &[]).unwrap(),
dyn_mod("empty-shape", body).unwrap(),
"must_not_expose_dyn_of([]) matches exactly what shape-only must_not_expose_dyn does",
);
assert_eq!(
dyn_operand_mod("empty2", body, &[]).unwrap(),
["dyn crate::ports::Port exposed by fn crate::m::c"],
);
}
#[test]
pub(super) fn dyn_operand_boundary_carries_its_operands_and_severity() {
let b = DynTraitBoundary::in_crate("core")
.module("crate::core")
.must_not_expose_dyn_of(["crate::ports::Port"])
.warn()
.because("the core seam must not leak a dyn Port");
assert_eq!(b.forbidden_operands(), ["crate::ports::Port"]);
assert_eq!(b.severity(), Severity::Warn);
let shape = DynTraitBoundary::in_crate("core")
.module("crate::core")
.must_not_expose_dyn()
.because("no dyn at all");
assert!(shape.forbidden_operands().is_empty());
}