use std::collections::{BTreeMap, BTreeSet};
use serde::Serialize;
use crate::compiler::facts::{
CompilerFactDomain, CompilerFactEdgeKind, CompilerFactItemKind, CompilerFactMacroProvenance, CompilerFactNamespace,
CompilerFactObject, CompilerFactPackage, CompilerFactRetentionReason, CompilerFactRole, CompilerFactSourceIdentity,
CompilerFactSourcePath, CompilerFactSpan, CompilerFactTargetKind, CompilerFactVisibility, CompilerItemFact,
CompilerItemId, ValidatedCompilerFactObject, required_compiler_fact_coverage,
};
use crate::error::{RailError, RailResult};
use crate::source::ContentDigest;
const SURFACE_ITEM_IDENTITY_PREFIX: &str = "surface-item-v1-sha256-";
const SURFACE_FINDING_IDENTITY_PREFIX: &str = "surface-finding-v1-sha256-";
const SURFACE_RETENTION_EXAMPLE_LIMIT: usize = 3;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
struct SurfaceSourceIdentity {
path: CompilerFactSourcePath,
identity: CompilerFactSourceIdentity,
bytes: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
struct SurfaceSpan {
source: SurfaceSourceIdentity,
start: u64,
end: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
struct SurfaceItemKey {
span: SurfaceSpan,
source_context: String,
namespace: CompilerFactNamespace,
kind: CompilerFactItemKind,
ordinal: u16,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub(crate) struct SurfaceCompilerCrate {
pub(crate) package: CompilerFactPackage,
pub(crate) cargo_target: String,
pub(crate) crate_name: String,
pub(crate) target_kind: CompilerFactTargetKind,
pub(crate) role: CompilerFactRole,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub(crate) struct SurfaceTargetObservation {
pub(crate) platform: String,
pub(crate) cfg: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum SurfaceProductKind {
Binary,
Library,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct SurfaceProductRoot {
pub(crate) package: String,
pub(crate) target: String,
pub(crate) kind: SurfaceProductKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SurfaceProductSelection {
pub(crate) root: SurfaceProductRoot,
pub(crate) target: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct SurfaceProductObservation {
root: SurfaceProductRoot,
target: SurfaceTargetObservation,
}
impl From<&CompilerFactObject> for SurfaceCompilerCrate {
fn from(object: &CompilerFactObject) -> Self {
Self {
package: object.unit.package.clone(),
cargo_target: object.unit.cargo_target.clone(),
crate_name: object.unit.crate_name.clone(),
target_kind: object.unit.target_kind.clone(),
role: object.unit.role,
}
}
}
impl From<&CompilerFactObject> for SurfaceTargetObservation {
fn from(object: &CompilerFactObject) -> Self {
Self {
platform: object.unit.platform.clone(),
cfg: object.unit.cfg.clone(),
}
}
}
impl SurfaceItemKey {
fn identity(&self) -> RailResult<String> {
Ok(format!(
"{SURFACE_ITEM_IDENTITY_PREFIX}{}",
ContentDigest::sha256(&serde_json::to_vec(self)?)
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum SurfaceVisibility {
Private,
Restricted,
Crate,
Public,
}
impl From<&CompilerFactVisibility> for SurfaceVisibility {
fn from(visibility: &CompilerFactVisibility) -> Self {
match visibility {
CompilerFactVisibility::Private => Self::Private,
CompilerFactVisibility::Restricted(_) | CompilerFactVisibility::RestrictedCrateRoot => Self::Restricted,
CompilerFactVisibility::Crate => Self::Crate,
CompilerFactVisibility::Public => Self::Public,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum SurfaceMacroProvenance {
Written,
Expansion,
Generated,
}
impl From<&CompilerFactMacroProvenance> for SurfaceMacroProvenance {
fn from(provenance: &CompilerFactMacroProvenance) -> Self {
match provenance {
CompilerFactMacroProvenance::Written => Self::Written,
CompilerFactMacroProvenance::Expansion(_) => Self::Expansion,
CompilerFactMacroProvenance::Generated => Self::Generated,
}
}
}
pub(crate) fn retention_reason_code(object: &CompilerFactObject, reason: &CompilerFactRetentionReason) -> String {
match reason {
CompilerFactRetentionReason::AllowDeadCode => "allow-dead-code".to_string(),
CompilerFactRetentionReason::ForeignExport => "foreign-export".to_string(),
CompilerFactRetentionReason::NoMangle => "no-mangle".to_string(),
CompilerFactRetentionReason::ExportName => "export-name".to_string(),
CompilerFactRetentionReason::Used => "used".to_string(),
CompilerFactRetentionReason::ProcMacro => "proc-macro".to_string(),
CompilerFactRetentionReason::UnresolvedTraitDispatch => "unresolved-trait-dispatch".to_string(),
CompilerFactRetentionReason::RequiredImplementationInterface => "required-implementation-interface".to_string(),
CompilerFactRetentionReason::GeneratedRegistration => "generated-registration".to_string(),
CompilerFactRetentionReason::IncompleteProvenance => "incomplete-provenance".to_string(),
CompilerFactRetentionReason::ExternallyAddressed => "externally-addressed".to_string(),
CompilerFactRetentionReason::Other(detail) => format!("other:{}", object.strings[detail.0 as usize]),
}
}
fn retention_reason_predicate(code: &str) -> &'static str {
match code {
"allow-dead-code" => "rustc's effective dead_code lint level is allow for the definition",
"foreign-export" => "compiler evidence marks the definition as a foreign export",
"no-mangle" => "rustc codegen attributes include no_mangle for the definition",
"export-name" => "rustc codegen attributes assign an exported symbol name to the definition",
"used" => "rustc codegen attributes mark the definition as compiler- or linker-used",
"proc-macro" => "the definition is public in a proc-macro crate",
"unresolved-trait-dispatch" => "the definition's immediate parent is a trait or an implementation of a trait",
"required-implementation-interface" => {
"compiler evidence requires the definition to preserve an implementation interface"
}
"generated-registration" => {
"a non-production expansion-generated constant registers a written function with the same diagnostic path"
}
"incomplete-provenance" => "compiler evidence cannot prove complete source or reference provenance",
"externally-addressed" => "compiler evidence indicates that the definition may be addressed outside Rust edges",
_ => "the compiler-fact producer supplied a conservative extension reason",
}
}
#[derive(Debug)]
struct SurfaceItem {
identity: String,
names: BTreeSet<String>,
diagnostic_paths: BTreeSet<String>,
packages: BTreeSet<CompilerFactPackage>,
compiler_crates: BTreeSet<SurfaceCompilerCrate>,
target_observations: BTreeSet<SurfaceTargetObservation>,
parent_observations: BTreeSet<Option<SurfaceItemKey>>,
written_visibilities: BTreeSet<SurfaceVisibility>,
effective_visibilities: BTreeSet<SurfaceVisibility>,
visibility_spans: BTreeSet<Option<SurfaceSpan>>,
macro_provenance: BTreeSet<SurfaceMacroProvenance>,
retentions: BTreeSet<String>,
}
impl SurfaceItem {
fn new(identity: String) -> Self {
Self {
identity,
names: BTreeSet::new(),
diagnostic_paths: BTreeSet::new(),
packages: BTreeSet::new(),
compiler_crates: BTreeSet::new(),
target_observations: BTreeSet::new(),
parent_observations: BTreeSet::new(),
written_visibilities: BTreeSet::new(),
effective_visibilities: BTreeSet::new(),
visibility_spans: BTreeSet::new(),
macro_provenance: BTreeSet::new(),
retentions: BTreeSet::new(),
}
}
fn classification_is_supported(&self, omitted_retention: Option<&str>) -> bool {
self.retentions
.iter()
.all(|reason| Some(reason.as_str()) == omitted_retention)
&& self.names.len() == 1
&& self.effective_visibilities.len() == 1
&& self.macro_provenance == BTreeSet::from([SurfaceMacroProvenance::Written])
}
fn written_visibility(&self) -> Option<SurfaceVisibility> {
(self.written_visibilities.len() == 1
&& self.visibility_spans.len() == 1
&& self.visibility_spans.first().is_some_and(Option::is_some))
.then(|| self.written_visibilities.first().copied())
.flatten()
}
fn effective_visibility(&self) -> Option<SurfaceVisibility> {
(self.effective_visibilities.len() == 1)
.then(|| self.effective_visibilities.first().copied())
.flatten()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct SurfaceEdge {
source: SurfaceItemKey,
target: SurfaceItemKey,
kind: CompilerFactEdgeKind,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct SurfacePolicy {
closed_world_crates: BTreeSet<SurfaceCompilerCrate>,
unnecessary_crate_visibility: bool,
preserve_uniform_fields: bool,
products: Vec<SurfaceProductSelection>,
}
impl SurfacePolicy {
pub(crate) fn new(closed_world_crates: BTreeSet<SurfaceCompilerCrate>, unnecessary_crate_visibility: bool) -> Self {
Self {
closed_world_crates,
unnecessary_crate_visibility,
preserve_uniform_fields: false,
products: Vec::new(),
}
}
pub(crate) fn with_products(mut self, products: Vec<SurfaceProductSelection>) -> Self {
self.products = products;
self
}
pub(crate) fn preserving_uniform_fields(mut self, preserve: bool) -> Self {
self.preserve_uniform_fields = preserve;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum SurfaceFindingKind {
DeadPublic,
UnnecessaryPublic,
UnnecessaryRestrictedVisibility,
UnnecessaryCrateVisibility,
}
impl SurfaceFindingKind {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::DeadPublic => "dead-public",
Self::UnnecessaryPublic => "unnecessary-public",
Self::UnnecessaryRestrictedVisibility => "unnecessary-restricted-visibility",
Self::UnnecessaryCrateVisibility => "unnecessary-crate-visibility",
}
}
}
fn item_kind_name(kind: CompilerFactItemKind) -> &'static str {
match kind {
CompilerFactItemKind::Function => "function",
CompilerFactItemKind::Method => "method",
CompilerFactItemKind::AssociatedConstant => "associated-constant",
CompilerFactItemKind::Trait => "trait",
CompilerFactItemKind::Struct => "struct",
CompilerFactItemKind::Enum => "enum",
CompilerFactItemKind::Union => "union",
CompilerFactItemKind::TypeAlias => "type-alias",
CompilerFactItemKind::AssociatedType => "associated-type",
CompilerFactItemKind::Constant => "constant",
CompilerFactItemKind::Static => "static",
CompilerFactItemKind::Field => "field",
CompilerFactItemKind::Variant => "variant",
CompilerFactItemKind::Reexport => "reexport",
CompilerFactItemKind::Module => "module",
CompilerFactItemKind::Impl => "impl",
CompilerFactItemKind::Macro => "macro",
CompilerFactItemKind::ForeignFunction => "foreign-function",
CompilerFactItemKind::ForeignStatic => "foreign-static",
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct SurfaceFinding {
pub(crate) identity: String,
pub(crate) item_identity: String,
pub(crate) kind: SurfaceFindingKind,
pub(crate) name: String,
pub(crate) item_kind: &'static str,
pub(crate) packages: Vec<String>,
pub(crate) compiler_crates: Vec<SurfaceCompilerCrate>,
pub(crate) target_observations: Vec<SurfaceTargetObservation>,
pub(crate) diagnostic_paths: Vec<String>,
pub(crate) source: String,
pub(crate) source_generated: bool,
pub(crate) declaration_start: u64,
pub(crate) declaration_end: u64,
pub(crate) visibility_start: Option<u64>,
pub(crate) visibility_end: Option<u64>,
pub(crate) replacement: Option<&'static str>,
pub(crate) production_live: bool,
pub(crate) non_production_live: bool,
pub(crate) required_public: bool,
pub(crate) reasons: Vec<&'static str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SurfaceItemAnalysis {
pub(crate) identity: String,
pub(crate) item_kind: &'static str,
pub(crate) packages: Vec<String>,
pub(crate) compiler_crates: Vec<SurfaceCompilerCrate>,
pub(crate) target_observations: Vec<SurfaceTargetObservation>,
pub(crate) diagnostic_paths: Vec<String>,
pub(crate) source: String,
pub(crate) source_generated: bool,
pub(crate) production_live: bool,
pub(crate) non_production_live: bool,
pub(crate) required_public: bool,
pub(crate) retained: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SurfaceAnalysis {
pub(crate) items: Vec<SurfaceItemAnalysis>,
pub(crate) findings: Vec<SurfaceFinding>,
pub(crate) retention: SurfaceRetentionSummary,
pub(crate) metrics: SurfaceGraphMetrics,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct SurfaceRetentionSummary {
pub(crate) fragment_item_observations: u64,
pub(crate) merged_items: u64,
pub(crate) conservative_observations: u64,
pub(crate) unique_retained_items: u64,
pub(crate) representative_limit: usize,
pub(crate) reasons: Vec<SurfaceRetentionReasonSummary>,
pub(crate) counterfactual: Option<SurfaceRetentionCounterfactualSummary>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct SurfaceRetentionReasonSummary {
pub(crate) code: String,
pub(crate) predicate: &'static str,
pub(crate) observations: u64,
pub(crate) unique_items: u64,
pub(crate) representatives: Vec<SurfaceRetentionRepresentative>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct SurfaceRetentionRepresentative {
pub(crate) item_identity: String,
pub(crate) item_kind: &'static str,
pub(crate) packages: Vec<String>,
pub(crate) diagnostic_paths: Vec<String>,
pub(crate) source: String,
pub(crate) source_generated: bool,
pub(crate) declaration_start: u64,
pub(crate) declaration_end: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct SurfaceRetentionCounterfactualSummary {
pub(crate) basis: &'static str,
pub(crate) traversals: usize,
pub(crate) edge_visits: usize,
pub(crate) suppressed_finding_observations: u64,
pub(crate) reasons: Vec<SurfaceRetentionCounterfactualReason>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct SurfaceRetentionCounterfactualReason {
pub(crate) code: String,
pub(crate) suppressed_findings: u64,
pub(crate) finding_kinds: BTreeMap<&'static str, u64>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct SurfaceGraphMetrics {
pub(crate) nodes: usize,
pub(crate) edges: usize,
pub(crate) traversals: usize,
pub(crate) edge_visits: usize,
}
pub(crate) struct SurfaceGraph {
items: BTreeMap<SurfaceItemKey, SurfaceItem>,
adjacency: BTreeMap<SurfaceItemKey, BTreeSet<(CompilerFactEdgeKind, SurfaceItemKey)>>,
incoming: BTreeMap<SurfaceItemKey, BTreeSet<SurfaceItemKey>>,
product_roots: BTreeMap<SurfaceProductObservation, BTreeSet<SurfaceItemKey>>,
workspace_library_roots: BTreeSet<SurfaceItemKey>,
production_retention_roots_by_reason: BTreeMap<String, BTreeSet<SurfaceItemKey>>,
non_production_entry_roots: BTreeSet<SurfaceItemKey>,
non_production_retention_roots_by_reason: BTreeMap<String, BTreeSet<SurfaceItemKey>>,
required_public_roots: BTreeSet<SurfaceItemKey>,
fragment_item_observations: u64,
retention_observations: BTreeMap<String, u64>,
}
impl SurfaceGraph {
pub(crate) fn from_compiler_facts(objects: &[ValidatedCompilerFactObject]) -> RailResult<Self> {
let objects = objects
.iter()
.map(ValidatedCompilerFactObject::object)
.collect::<Vec<_>>();
Self::from_objects(&objects)
}
fn from_objects(objects: &[&CompilerFactObject]) -> RailResult<Self> {
let required_coverage = required_compiler_fact_coverage();
let mut producer = None;
let mut sources = BTreeMap::<CompilerFactSourcePath, (CompilerFactSourceIdentity, u64)>::new();
let mut fragment_item_observations = 0_u64;
for object in objects {
if !required_coverage.is_subset(&object.completion.coverage) {
return Err(RailError::message(
"surface analysis received compiler facts without complete required coverage",
));
}
if let Some(expected) = producer {
if expected != &object.producer_authority {
return Err(RailError::message(
"surface analysis cannot merge compiler facts from different compiler producers",
));
}
} else {
producer = Some(&object.producer_authority);
}
fragment_item_observations = fragment_item_observations
.checked_add(object.completion.items)
.ok_or_else(|| RailError::message("surface item observation count overflow"))?;
for source in &object.sources {
let authority = (source.identity.clone(), source.bytes);
if sources
.insert(source.path.clone(), authority.clone())
.is_some_and(|previous| previous != authority)
{
return Err(RailError::message(
"surface analysis cannot merge conflicting identities for one source path",
));
}
}
}
let mut items = BTreeMap::<SurfaceItemKey, SurfaceItem>::new();
let mut local_items = Vec::with_capacity(objects.len());
let mut compiler_items = BTreeMap::<CompilerItemId, BTreeSet<SurfaceItemKey>>::new();
for object in objects {
let mut local = BTreeMap::new();
for item in &object.items {
let key = item_key(object, item)?;
local.insert(item.id, key.clone());
compiler_items.entry(item.id).or_default().insert(key.clone());
let identity = key.identity()?;
let merged = items.entry(key.clone()).or_insert_with(|| SurfaceItem::new(identity));
merged.names.insert(object.strings[item.name.0 as usize].clone());
merged
.diagnostic_paths
.insert(object.strings[item.diagnostic_path.0 as usize].clone());
merged.packages.insert(object.unit.package.clone());
merged.compiler_crates.insert(SurfaceCompilerCrate::from(*object));
merged
.target_observations
.insert(SurfaceTargetObservation::from(*object));
merged
.written_visibilities
.insert(SurfaceVisibility::from(&item.written_visibility));
merged
.effective_visibilities
.insert(SurfaceVisibility::from(&item.effective_visibility));
merged.visibility_spans.insert(
item.visibility_span
.map(|span| surface_span(object, span))
.transpose()?,
);
merged
.macro_provenance
.insert(SurfaceMacroProvenance::from(&item.macro_provenance));
}
local_items.push(local);
}
let mut edges = BTreeSet::new();
let mut product_roots = BTreeMap::<SurfaceProductObservation, BTreeSet<SurfaceItemKey>>::new();
let mut workspace_library_roots = BTreeSet::new();
let mut production_retention_roots_by_reason = BTreeMap::<String, BTreeSet<SurfaceItemKey>>::new();
let mut non_production_entry_roots = BTreeSet::new();
let mut non_production_retention_roots_by_reason = BTreeMap::<String, BTreeSet<SurfaceItemKey>>::new();
let mut required_public_roots = BTreeSet::new();
let mut retention_observations = BTreeMap::<String, u64>::new();
for (object_index, object) in objects.iter().enumerate() {
let local = &local_items[object_index];
let product = surface_product(object).map(|root| SurfaceProductObservation {
root,
target: SurfaceTargetObservation::from(*object),
});
if let Some(product) = &product {
product_roots.entry(product.clone()).or_default();
}
for item in &object.items {
let key = &local[&item.id];
let parent = item.parent.as_ref().map(|parent| local[parent].clone());
items
.get_mut(key)
.ok_or_else(|| RailError::message("surface item disappeared during graph merge"))?
.parent_observations
.insert(parent);
}
for entry in &object.entry_points {
let root = local[&entry.item].clone();
if object.unit.domain == CompilerFactDomain::Production {
if let Some(product) = &product {
product_roots.entry(product.clone()).or_default().insert(root);
}
} else {
non_production_entry_roots.insert(root);
}
}
for retention in &object.retentions {
let root = local[&retention.item].clone();
let reason = retention_reason_code(object, &retention.reason);
items
.get_mut(&root)
.ok_or_else(|| RailError::message("surface retention root disappeared during graph merge"))?
.retentions
.insert(reason.clone());
let observations = retention_observations.entry(reason.clone()).or_default();
*observations = observations
.checked_add(1)
.ok_or_else(|| RailError::message("surface retention observation count overflow"))?;
if object.unit.domain == CompilerFactDomain::Production {
production_retention_roots_by_reason
.entry(reason)
.or_default()
.insert(root);
} else {
non_production_retention_roots_by_reason
.entry(reason)
.or_default()
.insert(root);
}
}
for edge in &object.edges {
let source = local[&edge.source].clone();
let targets = local
.get(&edge.target)
.map(|target| BTreeSet::from([target.clone()]))
.or_else(|| compiler_items.get(&edge.target).cloned())
.unwrap_or_default();
for target in targets {
let crosses_crate_boundary = items
.get(&source)
.zip(items.get(&target))
.is_some_and(|(source, target)| source.compiler_crates.is_disjoint(&target.compiler_crates));
edges.insert(SurfaceEdge {
source: source.clone(),
target: target.clone(),
kind: edge.kind,
});
if crosses_crate_boundary {
if object.unit.domain == CompilerFactDomain::Production {
workspace_library_roots.insert(target.clone());
}
required_public_roots.insert(target);
}
}
}
}
let mut adjacency = BTreeMap::<_, BTreeSet<_>>::new();
let mut incoming = BTreeMap::<_, BTreeSet<_>>::new();
for edge in edges {
adjacency
.entry(edge.source.clone())
.or_default()
.insert((edge.kind, edge.target.clone()));
incoming.entry(edge.target).or_default().insert(edge.source);
}
Ok(Self {
items,
adjacency,
incoming,
product_roots,
workspace_library_roots,
production_retention_roots_by_reason,
non_production_entry_roots,
non_production_retention_roots_by_reason,
required_public_roots,
fragment_item_observations,
retention_observations,
})
}
pub(crate) fn analyze(&self, policy: &SurfacePolicy) -> RailResult<SurfaceAnalysis> {
self.analyze_baseline(policy).map(|(analysis, _)| analysis)
}
pub(crate) fn analyze_with_counterfactuals(&self, policy: &SurfacePolicy) -> RailResult<SurfaceAnalysis> {
let (mut analysis, required_public) = self.analyze_baseline(policy)?;
let baseline_finding_identities = analysis
.findings
.iter()
.map(|finding| finding.identity.as_str())
.collect::<BTreeSet<_>>();
let mut traversals = 0_usize;
let mut edge_visits = 0_usize;
let mut suppressed_finding_observations = 0_u64;
let mut reasons = Vec::with_capacity(self.retention_observations.len());
for code in self.retention_observations.keys() {
let production_roots = self.production_roots(policy, Some(code))?;
let non_production_roots = self.non_production_roots(Some(code));
let (production_live, production_visits) = self.closure(&production_roots, |_| true);
let (non_production_live, non_production_visits) = self.closure(&non_production_roots, |_| true);
traversals = traversals
.checked_add(2)
.ok_or_else(|| RailError::message("surface counterfactual traversal count overflow"))?;
edge_visits = edge_visits
.checked_add(production_visits)
.and_then(|total| total.checked_add(non_production_visits))
.ok_or_else(|| RailError::message("surface counterfactual edge visit count overflow"))?;
let (_, findings) = self.classify(
policy,
&production_live,
&non_production_live,
&required_public,
Some(code),
)?;
let suppressed = findings
.iter()
.filter(|finding| !baseline_finding_identities.contains(finding.identity.as_str()))
.collect::<Vec<_>>();
let suppressed_findings = u64::try_from(suppressed.len())
.map_err(|_| RailError::message("surface counterfactual finding count exceeds report capacity"))?;
suppressed_finding_observations = suppressed_finding_observations
.checked_add(suppressed_findings)
.ok_or_else(|| RailError::message("surface counterfactual finding count overflow"))?;
let mut finding_kinds = BTreeMap::<&'static str, u64>::new();
for finding in suppressed {
let count = finding_kinds.entry(finding.kind.as_str()).or_default();
*count = count
.checked_add(1)
.ok_or_else(|| RailError::message("surface counterfactual finding kind count overflow"))?;
}
reasons.push(SurfaceRetentionCounterfactualReason {
code: code.clone(),
suppressed_findings,
finding_kinds,
});
}
analysis.retention.counterfactual = Some(SurfaceRetentionCounterfactualSummary {
basis: "omit-one-reason-before-diagnostic-policy",
traversals,
edge_visits,
suppressed_finding_observations,
reasons,
});
Ok(analysis)
}
fn analyze_baseline(&self, policy: &SurfacePolicy) -> RailResult<(SurfaceAnalysis, BTreeSet<SurfaceItemKey>)> {
let production_roots = self.production_roots(policy, None)?;
let non_production_roots = self.non_production_roots(None);
let required_public_roots = self.required_public_roots.clone();
let (production_live, production_edge_visits) = self.closure(&production_roots, |_| true);
let (non_production_live, non_production_edge_visits) = self.closure(&non_production_roots, |_| true);
let (required_public, required_public_edge_visits) = self.closure(&required_public_roots, |kind| {
matches!(
kind,
CompilerFactEdgeKind::Interface
| CompilerFactEdgeKind::Reexport
| CompilerFactEdgeKind::VisibilityParent
| CompilerFactEdgeKind::VisibilityRequirement
)
});
let (items, findings) =
self.classify(policy, &production_live, &non_production_live, &required_public, None)?;
let analysis = SurfaceAnalysis {
items,
findings,
retention: self.retention_summary()?,
metrics: SurfaceGraphMetrics {
nodes: self.items.len(),
edges: self.adjacency.values().map(BTreeSet::len).sum(),
traversals: 3,
edge_visits: production_edge_visits + non_production_edge_visits + required_public_edge_visits,
},
};
Ok((analysis, required_public))
}
fn production_roots(
&self,
policy: &SurfacePolicy,
omitted_retention: Option<&str>,
) -> RailResult<BTreeSet<SurfaceItemKey>> {
let mut production_roots = self
.production_retention_roots_by_reason
.iter()
.filter(|(reason, _)| Some(reason.as_str()) != omitted_retention)
.flat_map(|(_, roots)| roots.iter().cloned())
.collect::<BTreeSet<_>>();
if policy.products.is_empty() {
for (product, roots) in &self.product_roots {
if product.root.kind == SurfaceProductKind::Binary {
production_roots.extend(roots.iter().cloned());
}
}
} else {
for selection in &policy.products {
let mut matching = Vec::new();
for (product, roots) in &self.product_roots {
if product.root == selection.root
&& target_selector_matches(selection.target.as_deref(), &product.target)?
{
matching.push((product, roots));
}
}
if matching.is_empty() {
return Err(RailError::message(format!(
"configured surface product '{}:{}' was not compiled for its target selector",
selection.root.package, selection.root.target
)));
}
if selection.root.kind == SurfaceProductKind::Binary {
for (_, roots) in matching {
production_roots.extend(roots.iter().cloned());
}
} else {
production_roots.extend(
self.workspace_library_roots
.iter()
.filter(|root| {
self.items.get(*root).is_some_and(|item| {
item.compiler_crates.iter().any(|compiler_crate| {
compiler_crate.package.name == selection.root.package
&& compiler_crate.cargo_target == selection.root.target
&& compiler_crate.target_kind == CompilerFactTargetKind::Library
})
})
})
.cloned(),
);
}
}
}
Ok(production_roots)
}
fn non_production_roots(&self, omitted_retention: Option<&str>) -> BTreeSet<SurfaceItemKey> {
let mut roots = self.non_production_entry_roots.clone();
roots.extend(
self.non_production_retention_roots_by_reason
.iter()
.filter(|(reason, _)| Some(reason.as_str()) != omitted_retention)
.flat_map(|(_, retained)| retained.iter().cloned()),
);
roots
}
fn classify(
&self,
policy: &SurfacePolicy,
production_live: &BTreeSet<SurfaceItemKey>,
non_production_live: &BTreeSet<SurfaceItemKey>,
required_public: &BTreeSet<SurfaceItemKey>,
omitted_retention: Option<&str>,
) -> RailResult<(Vec<SurfaceItemAnalysis>, Vec<SurfaceFinding>)> {
let mut analyzed_items = Vec::with_capacity(self.items.len());
let mut findings = Vec::new();
for (key, item) in &self.items {
let (source, source_generated) = match &key.span.source.path {
CompilerFactSourcePath::Repository(path) => (path.clone(), false),
CompilerFactSourcePath::Generated(path) => (path.clone(), true),
};
let state = SurfaceItemAnalysis {
identity: item.identity.clone(),
item_kind: item_kind_name(key.kind),
packages: item.packages.iter().map(|package| package.name.clone()).collect(),
compiler_crates: item.compiler_crates.iter().cloned().collect(),
target_observations: item.target_observations.iter().cloned().collect(),
diagnostic_paths: item.diagnostic_paths.iter().cloned().collect(),
source: source.clone(),
source_generated,
production_live: production_live.contains(key),
non_production_live: non_production_live.contains(key),
required_public: required_public.contains(key),
retained: !item.retentions.is_empty(),
};
if self.item_has_closed_world_authority(item, policy)
&& item.classification_is_supported(omitted_retention)
&& let Some(kind) = self.classify_item(key, item, &state, policy)
{
let identity = finding_identity(key, kind)?;
let visibility_span = item.visibility_spans.first().and_then(Option::as_ref);
findings.push(SurfaceFinding {
identity,
item_identity: item.identity.clone(),
kind,
name: item.names.first().cloned().unwrap_or_default(),
item_kind: item_kind_name(key.kind),
packages: item.packages.iter().map(|package| package.name.clone()).collect(),
compiler_crates: item.compiler_crates.iter().cloned().collect(),
target_observations: item.target_observations.iter().cloned().collect(),
diagnostic_paths: item.diagnostic_paths.iter().cloned().collect(),
source,
source_generated,
declaration_start: key.span.start,
declaration_end: key.span.end,
visibility_start: visibility_span.map(|span| span.start),
visibility_end: visibility_span.map(|span| span.end),
replacement: self.replacement(key, kind, policy),
production_live: state.production_live,
non_production_live: state.non_production_live,
required_public: state.required_public,
reasons: finding_reasons(kind, &state),
});
}
analyzed_items.push(state);
}
if policy.preserve_uniform_fields {
self.preserve_uniform_field_findings(&mut findings);
}
Ok((analyzed_items, findings))
}
fn retention_summary(&self) -> RailResult<SurfaceRetentionSummary> {
let merged_items = u64::try_from(self.items.len())
.map_err(|_| RailError::message("surface merged item count exceeds report capacity"))?;
let mut unique_retained_items = 0_u64;
let mut unique_items_by_reason = BTreeMap::<String, u64>::new();
let mut representatives_by_reason = BTreeMap::<String, Vec<SurfaceRetentionRepresentative>>::new();
for (key, item) in &self.items {
if item.retentions.is_empty() {
continue;
}
unique_retained_items = unique_retained_items
.checked_add(1)
.ok_or_else(|| RailError::message("surface retained item count overflow"))?;
for code in &item.retentions {
let count = unique_items_by_reason.entry(code.clone()).or_default();
*count = count
.checked_add(1)
.ok_or_else(|| RailError::message("surface retention reason item count overflow"))?;
let representatives = representatives_by_reason.entry(code.clone()).or_default();
if representatives.len() >= SURFACE_RETENTION_EXAMPLE_LIMIT {
continue;
}
let (source, source_generated) = match &key.span.source.path {
CompilerFactSourcePath::Repository(path) => (path.clone(), false),
CompilerFactSourcePath::Generated(path) => (path.clone(), true),
};
representatives.push(SurfaceRetentionRepresentative {
item_identity: item.identity.clone(),
item_kind: item_kind_name(key.kind),
packages: item.packages.iter().map(|package| package.name.clone()).collect(),
diagnostic_paths: item.diagnostic_paths.iter().cloned().collect(),
source,
source_generated,
declaration_start: key.span.start,
declaration_end: key.span.end,
});
}
}
let conservative_observations = self.retention_observations.values().try_fold(0_u64, |total, count| {
total
.checked_add(*count)
.ok_or_else(|| RailError::message("surface retention observation count overflow"))
})?;
let mut reasons = Vec::with_capacity(self.retention_observations.len());
for (code, observations) in &self.retention_observations {
reasons.push(SurfaceRetentionReasonSummary {
code: code.clone(),
predicate: retention_reason_predicate(code),
observations: *observations,
unique_items: unique_items_by_reason.get(code).copied().unwrap_or_default(),
representatives: representatives_by_reason.remove(code).unwrap_or_default(),
});
}
Ok(SurfaceRetentionSummary {
fragment_item_observations: self.fragment_item_observations,
merged_items,
conservative_observations,
unique_retained_items,
representative_limit: SURFACE_RETENTION_EXAMPLE_LIMIT,
reasons,
counterfactual: None,
})
}
fn closure(
&self,
roots: &BTreeSet<SurfaceItemKey>,
follows: impl Fn(CompilerFactEdgeKind) -> bool,
) -> (BTreeSet<SurfaceItemKey>, usize) {
let mut reached = roots.clone();
let mut pending = roots.iter().cloned().collect::<Vec<_>>();
let mut edge_visits = 0;
while let Some(source) = pending.pop() {
let Some(edges) = self.adjacency.get(&source) else {
continue;
};
for (kind, target) in edges {
edge_visits += 1;
if follows(*kind) && reached.insert(target.clone()) {
pending.push(target.clone());
}
}
}
(reached, edge_visits)
}
fn item_has_closed_world_authority(&self, item: &SurfaceItem, policy: &SurfacePolicy) -> bool {
!item.packages.is_empty()
&& item
.compiler_crates
.iter()
.all(|compiler_crate| policy.closed_world_crates.contains(compiler_crate))
}
fn preserve_uniform_field_findings(&self, findings: &mut Vec<SurfaceFinding>) {
let finding_kinds = findings
.iter()
.filter(|finding| finding.item_kind == "field")
.map(|finding| (finding.item_identity.as_str(), finding.kind))
.collect::<BTreeMap<_, _>>();
let mut fields_by_parent = BTreeMap::<SurfaceItemKey, Vec<&str>>::new();
for (key, item) in &self.items {
if key.kind != CompilerFactItemKind::Field || item.parent_observations.len() != 1 {
continue;
}
let Some(parent) = item.parent_observations.first().and_then(Option::as_ref) else {
continue;
};
fields_by_parent.entry(parent.clone()).or_default().push(&item.identity);
}
let mut allowed = BTreeSet::new();
for fields in fields_by_parent.values() {
let kinds = fields
.iter()
.filter_map(|field| finding_kinds.get(field).copied())
.collect::<BTreeSet<_>>();
if kinds.len() == 1 && fields.iter().all(|field| finding_kinds.contains_key(field)) {
allowed.extend(fields.iter().copied());
}
}
findings.retain(|finding| finding.item_kind != "field" || allowed.contains(finding.item_identity.as_str()));
}
fn classify_item(
&self,
key: &SurfaceItemKey,
item: &SurfaceItem,
state: &SurfaceItemAnalysis,
policy: &SurfacePolicy,
) -> Option<SurfaceFindingKind> {
let Some(visibility) = item.written_visibility() else {
return (item.effective_visibility() == Some(SurfaceVisibility::Public)
&& !state.required_public
&& !state.production_live
&& !state.non_production_live)
.then_some(SurfaceFindingKind::DeadPublic);
};
match visibility {
SurfaceVisibility::Public if state.required_public => None,
SurfaceVisibility::Public if !state.production_live && !state.non_production_live => {
Some(SurfaceFindingKind::DeadPublic)
}
SurfaceVisibility::Public => Some(SurfaceFindingKind::UnnecessaryPublic),
SurfaceVisibility::Restricted
if !state.required_public
&& self
.defining_scope(key)
.is_some_and(|scope| self.all_uses_fit(key, &scope)) =>
{
Some(SurfaceFindingKind::UnnecessaryRestrictedVisibility)
}
SurfaceVisibility::Crate if !state.required_public => {
let defining = self.defining_scope(key)?;
if self.all_uses_fit(key, &defining) {
Some(SurfaceFindingKind::UnnecessaryRestrictedVisibility)
} else if policy.unnecessary_crate_visibility {
self.parent_scope(&defining)
.filter(|scope| self.all_uses_fit(key, scope))
.map(|_| SurfaceFindingKind::UnnecessaryCrateVisibility)
} else {
None
}
}
SurfaceVisibility::Private | SurfaceVisibility::Restricted | SurfaceVisibility::Crate => None,
}
}
fn replacement(
&self,
key: &SurfaceItemKey,
kind: SurfaceFindingKind,
policy: &SurfacePolicy,
) -> Option<&'static str> {
match kind {
SurfaceFindingKind::DeadPublic => None,
SurfaceFindingKind::UnnecessaryRestrictedVisibility => Some("private"),
SurfaceFindingKind::UnnecessaryPublic if !policy.unnecessary_crate_visibility => Some("pub(crate)"),
SurfaceFindingKind::UnnecessaryPublic | SurfaceFindingKind::UnnecessaryCrateVisibility => {
let defining_scope = self.defining_scope(key)?;
if self.all_uses_fit(key, &defining_scope) {
return Some("private");
}
if self
.parent_scope(&defining_scope)
.is_some_and(|parent| self.all_uses_fit(key, &parent))
{
return Some("pub(super)");
}
(kind == SurfaceFindingKind::UnnecessaryPublic).then_some("pub(crate)")
}
}
}
fn all_uses_fit(&self, target: &SurfaceItemKey, scope: &SurfaceScope) -> bool {
self.incoming
.get(target)
.is_none_or(|sources| sources.iter().all(|source| self.item_is_within(source, scope)))
}
fn defining_scope(&self, item: &SurfaceItemKey) -> Option<SurfaceScope> {
let compiler_crate = exactly_one(&self.items.get(item)?.compiler_crates)?;
let mut current = item;
let mut visited = BTreeSet::new();
loop {
if !visited.insert(current) {
return None;
}
let node = self.items.get(current)?;
if node.compiler_crates.len() != 1
|| node.compiler_crates.first() != Some(&compiler_crate)
|| node.parent_observations.len() != 1
{
return None;
}
let parent = node.parent_observations.first()?.as_ref();
let Some(parent) = parent else {
return Some(SurfaceScope::Crate(compiler_crate));
};
if parent.kind == CompilerFactItemKind::Module {
return Some(SurfaceScope::Module(parent.clone(), compiler_crate));
}
current = parent;
}
}
fn parent_scope(&self, scope: &SurfaceScope) -> Option<SurfaceScope> {
match scope {
SurfaceScope::Crate(_) => None,
SurfaceScope::Module(module, _) => self.defining_scope(module),
}
}
fn item_is_within(&self, item: &SurfaceItemKey, scope: &SurfaceScope) -> bool {
let required_crate = scope.compiler_crate();
let mut current = item;
let mut visited = BTreeSet::new();
loop {
if !visited.insert(current) {
return false;
}
let Some(node) = self.items.get(current) else {
return false;
};
if node.compiler_crates.len() != 1 || node.compiler_crates.first() != Some(required_crate) {
return false;
}
if matches!(scope, SurfaceScope::Module(module, _) if module == current) {
return true;
}
if node.parent_observations.len() != 1 {
return false;
}
let Some(parent) = node.parent_observations.first().and_then(Option::as_ref) else {
return matches!(scope, SurfaceScope::Crate(_));
};
current = parent;
}
}
}
fn finding_reasons(kind: SurfaceFindingKind, state: &SurfaceItemAnalysis) -> Vec<&'static str> {
match kind {
SurfaceFindingKind::DeadPublic => vec!["not-production-live", "not-non-production-live", "not-required-public"],
SurfaceFindingKind::UnnecessaryPublic => {
let mut reasons = Vec::with_capacity(3);
if state.production_live {
reasons.push("production-live");
}
if state.non_production_live {
reasons.push("non-production-live");
}
reasons.push("not-required-public");
reasons
}
SurfaceFindingKind::UnnecessaryRestrictedVisibility => {
vec!["all-compiled-uses-within-defining-module"]
}
SurfaceFindingKind::UnnecessaryCrateVisibility => vec!["all-compiled-uses-within-parent-module"],
}
}
#[derive(Debug)]
enum SurfaceScope {
Crate(SurfaceCompilerCrate),
Module(SurfaceItemKey, SurfaceCompilerCrate),
}
impl SurfaceScope {
fn compiler_crate(&self) -> &SurfaceCompilerCrate {
match self {
Self::Crate(compiler_crate) | Self::Module(_, compiler_crate) => compiler_crate,
}
}
}
pub(crate) fn target_selector_matches(
selector: Option<&str>,
observation: &SurfaceTargetObservation,
) -> RailResult<bool> {
let Some(selector) = selector else {
return Ok(true);
};
let platform = selector
.parse::<cargo_platform::Platform>()
.map_err(|error| RailError::message(format!("invalid surface target selector '{selector}': {error}")))?;
let cfg = observation
.cfg
.iter()
.map(|value| {
value
.parse::<cargo_platform::Cfg>()
.map_err(|error| RailError::message(format!("compiler fact contains invalid cfg '{value}': {error}")))
})
.collect::<RailResult<Vec<_>>>()?;
Ok(platform.matches(&observation.platform, &cfg))
}
fn exactly_one<T: Clone + Ord>(values: &BTreeSet<T>) -> Option<T> {
(values.len() == 1).then(|| values.first().cloned()).flatten()
}
fn surface_product(object: &CompilerFactObject) -> Option<SurfaceProductRoot> {
if object.unit.domain != CompilerFactDomain::Production {
return None;
}
let kind = match object.unit.target_kind {
CompilerFactTargetKind::Binary => SurfaceProductKind::Binary,
CompilerFactTargetKind::Library => SurfaceProductKind::Library,
_ => return None,
};
Some(SurfaceProductRoot {
package: object.unit.package.name.clone(),
target: object.unit.cargo_target.clone(),
kind,
})
}
fn item_key(object: &CompilerFactObject, item: &CompilerItemFact) -> RailResult<SurfaceItemKey> {
Ok(SurfaceItemKey {
span: surface_span(object, item.physical.span)?,
source_context: object.strings[item.physical.source_context.0 as usize].clone(),
namespace: item.physical.namespace,
kind: item.physical.kind,
ordinal: item.physical.ordinal,
})
}
fn surface_span(object: &CompilerFactObject, span: CompilerFactSpan) -> RailResult<SurfaceSpan> {
let source = object
.sources
.get(span.source as usize)
.ok_or_else(|| RailError::message("surface span names a missing compiler-fact source"))?;
Ok(SurfaceSpan {
source: SurfaceSourceIdentity {
path: source.path.clone(),
identity: source.identity.clone(),
bytes: source.bytes,
},
start: span.start,
end: span.end,
})
}
fn finding_identity(key: &SurfaceItemKey, kind: SurfaceFindingKind) -> RailResult<String> {
Ok(format!(
"{SURFACE_FINDING_IDENTITY_PREFIX}{}",
ContentDigest::sha256(&serde_json::to_vec(&(key, kind))?)
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compiler::facts::{
COMPILER_FACT_PROTOCOL_VERSION, CompilerFactCompletion, CompilerFactEdge, CompilerFactEntryPoint,
CompilerFactEntryPointKind, CompilerFactPhysicalIdentity, CompilerFactProducerAuthority, CompilerFactRetention,
CompilerFactSource, CompilerFactStringId, CompilerFactUnit,
};
const CRATE_A: u64 = 10;
const CRATE_B: u64 = 20;
fn package(name: &str) -> CompilerFactPackage {
CompilerFactPackage {
name: name.to_string(),
version: "1.0.0".to_string(),
source: None,
}
}
fn item(
id: (u64, u64),
span: (u64, u64),
kind: CompilerFactItemKind,
visibility: CompilerFactVisibility,
) -> CompilerItemFact {
let public = !matches!(visibility, CompilerFactVisibility::Private);
CompilerItemFact {
id: CompilerItemId([id.0, id.1]),
physical: CompilerFactPhysicalIdentity {
span: CompilerFactSpan {
source: 0,
start: span.0,
end: span.1,
},
source_context: CompilerFactStringId(0),
namespace: if kind == CompilerFactItemKind::Module {
CompilerFactNamespace::Type
} else {
CompilerFactNamespace::Value
},
kind,
ordinal: 0,
},
name: CompilerFactStringId(1),
diagnostic_path: CompilerFactStringId(2),
parent: None,
written_visibility: visibility.clone(),
visibility_span: public.then_some(CompilerFactSpan {
source: 0,
start: span.0,
end: span.0 + 3,
}),
effective_visibility: visibility,
macro_provenance: CompilerFactMacroProvenance::Written,
}
}
fn object(
package_name: &str,
domain: CompilerFactDomain,
items: Vec<CompilerItemFact>,
edges: Vec<CompilerFactEdge>,
entries: Vec<CompilerItemId>,
retentions: Vec<(CompilerItemId, CompilerFactRetentionReason)>,
) -> CompilerFactObject {
let coverage = required_compiler_fact_coverage();
CompilerFactObject {
version: COMPILER_FACT_PROTOCOL_VERSION,
producer_authority: CompilerFactProducerAuthority {
compiler_identity: "compiler".to_string(),
driver_identity: "driver".to_string(),
},
unit: CompilerFactUnit {
identity: format!("unit-{package_name}-{domain:?}"),
invocation_identity: "invocation".to_string(),
package: package(package_name),
cargo_target: package_name.to_string(),
crate_name: package_name.replace('-', "_"),
target_kind: if domain == CompilerFactDomain::Production && !entries.is_empty() {
CompilerFactTargetKind::Binary
} else {
CompilerFactTargetKind::Library
},
domain,
role: CompilerFactRole::Target,
platform: "host".to_string(),
features: Vec::new(),
cfg: Vec::new(),
},
strings: vec![
"hygiene:root".to_string(),
"item".to_string(),
format!("{package_name}::item"),
],
sources: vec![CompilerFactSource {
path: CompilerFactSourcePath::Repository(format!("crates/{package_name}/src/lib.rs")),
identity: CompilerFactSourceIdentity::Exact(format!("sha256:{package_name}")),
bytes: 1_000,
}],
completion: CompilerFactCompletion {
complete: true,
coverage,
strings: 3,
sources: 1,
items: items.len() as u64,
edges: edges.len() as u64,
entry_points: entries.len() as u64,
retentions: retentions.len() as u64,
},
items,
edges,
entry_points: entries
.into_iter()
.map(|item| CompilerFactEntryPoint {
item,
kind: if domain == CompilerFactDomain::Production {
CompilerFactEntryPointKind::Main
} else {
CompilerFactEntryPointKind::TestHarness
},
})
.collect(),
retentions: retentions
.into_iter()
.map(|(item, reason)| CompilerFactRetention { item, reason })
.collect(),
}
}
fn body(source: (u64, u64), target: (u64, u64)) -> CompilerFactEdge {
CompilerFactEdge {
source: CompilerItemId([source.0, source.1]),
target: CompilerItemId([target.0, target.1]),
kind: CompilerFactEdgeKind::Body,
}
}
fn policy(packages: &[&str], crate_visibility: bool) -> SurfacePolicy {
let crates = packages
.iter()
.flat_map(|name| {
[CompilerFactTargetKind::Library, CompilerFactTargetKind::Binary].map(|target_kind| {
SurfaceCompilerCrate {
package: package(name),
cargo_target: (*name).to_string(),
crate_name: name.replace('-', "_"),
target_kind,
role: CompilerFactRole::Target,
}
})
})
.collect();
SurfacePolicy::new(crates, crate_visibility)
}
#[test]
fn production_non_production_and_dead_public_classify_independently() {
let root = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Private,
);
let production = item(
(CRATE_A, 2),
(20, 30),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let dead = item(
(CRATE_A, 3),
(40, 50),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let production_object = object(
"app",
CompilerFactDomain::Production,
vec![root.clone(), production.clone(), dead.clone()],
vec![body((CRATE_A, 1), (CRATE_A, 2))],
vec![root.id],
Vec::new(),
);
let test_root = item(
(CRATE_A, 4),
(60, 70),
CompilerFactItemKind::Function,
CompilerFactVisibility::Private,
);
let non_production = item(
(CRATE_A, 5),
(80, 90),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let test_object = object(
"app",
CompilerFactDomain::NonProduction,
vec![test_root.clone(), production, dead, non_production],
vec![body((CRATE_A, 4), (CRATE_A, 5))],
vec![test_root.id],
Vec::new(),
);
let analysis = SurfaceGraph::from_objects(&[&production_object, &test_object])
.expect("merge graph")
.analyze(&policy(&["app"], false))
.expect("analyze graph");
assert_eq!(
analysis.findings.iter().map(|finding| finding.kind).collect::<Vec<_>>(),
vec![
SurfaceFindingKind::UnnecessaryPublic,
SurfaceFindingKind::DeadPublic,
SurfaceFindingKind::UnnecessaryPublic,
]
);
let states = analysis
.items
.iter()
.map(|state| (state.production_live, state.non_production_live, state.required_public))
.collect::<Vec<_>>();
assert!(states.contains(&(true, false, false)));
assert!(states.contains(&(false, true, false)));
assert!(states.contains(&(false, false, false)));
}
#[test]
fn semantic_cross_crate_reference_ignores_opaque_item_id_collisions() {
let consumer = item(
(CRATE_A, 10),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Private,
);
let consumer_object = object(
"consumer",
CompilerFactDomain::Production,
vec![consumer.clone()],
vec![body((CRATE_A, 10), (CRATE_A, 1))],
vec![consumer.id],
Vec::new(),
);
let api = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let interface = item(
(CRATE_A, 2),
(20, 30),
CompilerFactItemKind::Struct,
CompilerFactVisibility::Public,
);
let provider_object = object(
"provider",
CompilerFactDomain::Production,
vec![api, interface],
vec![CompilerFactEdge {
source: CompilerItemId([CRATE_A, 1]),
target: CompilerItemId([CRATE_A, 2]),
kind: CompilerFactEdgeKind::Interface,
}],
Vec::new(),
Vec::new(),
);
let analysis = SurfaceGraph::from_objects(&[&consumer_object, &provider_object])
.expect("merge graph")
.analyze(&policy(&["consumer", "provider"], false))
.expect("analyze graph");
assert!(analysis.findings.is_empty());
assert_eq!(analysis.items.iter().filter(|item| item.required_public).count(), 2);
}
#[test]
fn conservative_retention_keeps_an_item_without_creating_a_reduction() {
let retained = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let retained_id = retained.id;
let facts = object(
"app",
CompilerFactDomain::Production,
vec![retained],
Vec::new(),
Vec::new(),
vec![(retained_id, CompilerFactRetentionReason::IncompleteProvenance)],
);
let analysis = SurfaceGraph::from_objects(&[&facts])
.expect("merge graph")
.analyze(&policy(&["app"], false))
.expect("analyze graph");
assert!(analysis.findings.is_empty());
assert!(analysis.items[0].production_live);
assert!(analysis.items[0].retained);
}
#[test]
fn counterfactual_counts_finding_kind_transitions_on_the_same_item() {
let retained = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let child = item(
(CRATE_A, 2),
(20, 30),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let retained_id = retained.id;
let facts = object(
"app",
CompilerFactDomain::Production,
vec![retained, child],
vec![body((CRATE_A, 1), (CRATE_A, 2))],
Vec::new(),
vec![(retained_id, CompilerFactRetentionReason::AllowDeadCode)],
);
let graph = SurfaceGraph::from_objects(&[&facts]).expect("merge retained graph");
let baseline = graph.analyze(&policy(&["app"], false)).expect("analyze retained graph");
assert_eq!(baseline.findings.len(), 1);
assert_eq!(baseline.findings[0].kind, SurfaceFindingKind::UnnecessaryPublic);
let counterfactual = graph
.analyze_with_counterfactuals(&policy(&["app"], false))
.expect("measure retention counterfactual")
.retention
.counterfactual
.expect("counterfactual summary");
assert_eq!(counterfactual.suppressed_finding_observations, 2);
let allow = counterfactual
.reasons
.iter()
.find(|reason| reason.code == "allow-dead-code")
.expect("allow-dead-code counterfactual");
assert_eq!(allow.suppressed_findings, 2);
assert_eq!(allow.finding_kinds.get("dead-public"), Some(&2));
}
#[test]
fn retention_summary_distinguishes_observations_from_merged_items_and_bounds_examples() {
let production_items = (0_u64..4)
.map(|index| {
item(
(CRATE_A, index + 1),
(index * 20, index * 20 + 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
)
})
.collect::<Vec<_>>();
let non_production_items = (0_u64..4)
.map(|index| {
item(
(CRATE_B, index + 1),
(index * 20, index * 20 + 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
)
})
.collect::<Vec<_>>();
let production_retentions = production_items
.iter()
.map(|item| (item.id, CompilerFactRetentionReason::AllowDeadCode))
.chain(std::iter::once((
production_items[0].id,
CompilerFactRetentionReason::UnresolvedTraitDispatch,
)))
.collect();
let non_production_retentions = non_production_items
.iter()
.map(|item| (item.id, CompilerFactRetentionReason::AllowDeadCode))
.chain(std::iter::once((
non_production_items[0].id,
CompilerFactRetentionReason::UnresolvedTraitDispatch,
)))
.collect();
let production = object(
"app",
CompilerFactDomain::Production,
production_items,
Vec::new(),
Vec::new(),
production_retentions,
);
let non_production = object(
"app",
CompilerFactDomain::NonProduction,
non_production_items,
Vec::new(),
Vec::new(),
non_production_retentions,
);
let graph =
SurfaceGraph::from_objects(&[&production, &non_production]).expect("merge equivalent compiler views");
let retention = graph
.analyze(&policy(&["app"], false))
.expect("analyze retained items")
.retention;
assert_eq!(retention.fragment_item_observations, 8);
assert_eq!(retention.merged_items, 4);
assert_eq!(retention.conservative_observations, 10);
assert_eq!(retention.unique_retained_items, 4);
assert_eq!(retention.representative_limit, 3);
let allow = retention
.reasons
.iter()
.find(|reason| reason.code == "allow-dead-code")
.expect("allow-dead-code summary");
assert_eq!((allow.observations, allow.unique_items), (8, 4));
assert_eq!(allow.representatives.len(), 3);
assert_eq!(
allow
.representatives
.iter()
.map(|example| example.declaration_start)
.collect::<Vec<_>>(),
vec![0, 20, 40]
);
let trait_dispatch = retention
.reasons
.iter()
.find(|reason| reason.code == "unresolved-trait-dispatch")
.expect("unresolved-trait-dispatch summary");
assert_eq!((trait_dispatch.observations, trait_dispatch.unique_items), (2, 1));
assert_eq!(trait_dispatch.representatives.len(), 1);
assert!(trait_dispatch.predicate.contains("immediate parent"));
let counterfactual = graph
.analyze_with_counterfactuals(&policy(&["app"], false))
.expect("measure omit-one-reason counterfactuals")
.retention
.counterfactual
.expect("counterfactual summary");
assert_eq!(counterfactual.basis, "omit-one-reason-before-diagnostic-policy");
assert_eq!(counterfactual.traversals, 4);
assert_eq!(counterfactual.suppressed_finding_observations, 3);
let allow = counterfactual
.reasons
.iter()
.find(|reason| reason.code == "allow-dead-code")
.expect("allow-dead-code counterfactual");
assert_eq!(allow.suppressed_findings, 3);
assert_eq!(allow.finding_kinds.get("dead-public"), Some(&3));
let trait_dispatch = counterfactual
.reasons
.iter()
.find(|reason| reason.code == "unresolved-trait-dispatch")
.expect("unresolved-trait-dispatch counterfactual");
assert_eq!(trait_dispatch.suppressed_findings, 0);
assert!(trait_dispatch.finding_kinds.is_empty());
}
#[test]
fn open_world_packages_never_receive_closed_world_findings() {
let dead = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let facts = object(
"library",
CompilerFactDomain::Production,
vec![dead],
Vec::new(),
Vec::new(),
Vec::new(),
);
let analysis = SurfaceGraph::from_objects(&[&facts])
.expect("merge graph")
.analyze(&SurfacePolicy::default())
.expect("analyze graph");
assert!(analysis.findings.is_empty());
}
#[test]
fn restricted_visibility_reduces_only_when_every_use_is_inside_the_defining_module() {
let module = item(
(CRATE_A, 1),
(0, 100),
CompilerFactItemKind::Module,
CompilerFactVisibility::Private,
);
let mut local_user = item(
(CRATE_A, 2),
(10, 20),
CompilerFactItemKind::Function,
CompilerFactVisibility::Private,
);
local_user.parent = Some(module.id);
let mut restricted = item(
(CRATE_A, 3),
(30, 40),
CompilerFactItemKind::Function,
CompilerFactVisibility::Restricted(module.id),
);
restricted.parent = Some(module.id);
let local = object(
"app",
CompilerFactDomain::Production,
vec![module.clone(), local_user.clone(), restricted.clone()],
vec![body((CRATE_A, 2), (CRATE_A, 3))],
vec![local_user.id],
Vec::new(),
);
let local_analysis = SurfaceGraph::from_objects(&[&local])
.expect("merge graph")
.analyze(&policy(&["app"], false))
.expect("analyze graph");
assert_eq!(
local_analysis
.findings
.iter()
.map(|finding| finding.kind)
.collect::<Vec<_>>(),
vec![SurfaceFindingKind::UnnecessaryRestrictedVisibility]
);
assert_eq!(local_analysis.findings[0].replacement, Some("private"));
let outside = item(
(CRATE_A, 4),
(110, 120),
CompilerFactItemKind::Function,
CompilerFactVisibility::Private,
);
let external_use = object(
"app",
CompilerFactDomain::Production,
vec![module, local_user, restricted, outside.clone()],
vec![body((CRATE_A, 4), (CRATE_A, 3))],
vec![outside.id],
Vec::new(),
);
let external_analysis = SurfaceGraph::from_objects(&[&external_use])
.expect("merge graph")
.analyze(&policy(&["app"], false))
.expect("analyze graph");
assert!(external_analysis.findings.is_empty());
}
#[test]
fn crate_visibility_reduction_is_opt_in_and_requires_a_parent_scope() {
let parent = item(
(CRATE_A, 1),
(0, 200),
CompilerFactItemKind::Module,
CompilerFactVisibility::Private,
);
let mut module = item(
(CRATE_A, 2),
(10, 100),
CompilerFactItemKind::Module,
CompilerFactVisibility::Private,
);
module.parent = Some(parent.id);
let mut user = item(
(CRATE_A, 3),
(20, 30),
CompilerFactItemKind::Function,
CompilerFactVisibility::Private,
);
user.parent = Some(parent.id);
let mut crate_visible = item(
(CRATE_A, 4),
(40, 50),
CompilerFactItemKind::Function,
CompilerFactVisibility::Crate,
);
crate_visible.parent = Some(module.id);
let facts = object(
"app",
CompilerFactDomain::Production,
vec![parent, module, user.clone(), crate_visible],
vec![body((CRATE_A, 3), (CRATE_A, 4))],
vec![user.id],
Vec::new(),
);
let graph = SurfaceGraph::from_objects(&[&facts]).expect("merge graph");
assert!(
graph
.analyze(&policy(&["app"], false))
.expect("disabled lint")
.findings
.is_empty()
);
assert_eq!(
graph
.analyze(&policy(&["app"], true))
.expect("enabled lint")
.findings
.iter()
.map(|finding| finding.kind)
.collect::<Vec<_>>(),
vec![SurfaceFindingKind::UnnecessaryCrateVisibility]
);
assert_eq!(
graph.analyze(&policy(&["app"], true)).expect("enabled lint").findings[0].replacement,
Some("pub(super)")
);
}
#[test]
fn unused_crate_visibility_reduces_to_private_as_restricted_visibility() {
let crate_visible = item(
(CRATE_A, 1),
(10, 30),
CompilerFactItemKind::Function,
CompilerFactVisibility::Crate,
);
let facts = object(
"app",
CompilerFactDomain::Production,
vec![crate_visible],
Vec::new(),
Vec::new(),
Vec::new(),
);
let findings = SurfaceGraph::from_objects(&[&facts])
.expect("merge graph")
.analyze(&policy(&["app"], false))
.expect("analyze graph")
.findings;
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].kind, SurfaceFindingKind::UnnecessaryRestrictedVisibility);
assert_eq!(findings[0].replacement, Some("private"));
}
#[test]
fn implicit_public_enum_variant_remains_a_report_only_dead_finding() {
let enumeration = item(
(CRATE_A, 1),
(10, 50),
CompilerFactItemKind::Enum,
CompilerFactVisibility::Public,
);
let mut variant = item(
(CRATE_A, 2),
(30, 40),
CompilerFactItemKind::Variant,
CompilerFactVisibility::Private,
);
variant.parent = Some(enumeration.id);
variant.visibility_span = None;
variant.effective_visibility = CompilerFactVisibility::Public;
let facts = object(
"app",
CompilerFactDomain::Production,
vec![enumeration, variant],
Vec::new(),
Vec::new(),
Vec::new(),
);
let findings = SurfaceGraph::from_objects(&[&facts])
.expect("merge graph")
.analyze(&policy(&["app"], false))
.expect("analyze graph")
.findings;
let variant = findings
.iter()
.find(|finding| finding.item_kind == "variant")
.expect("dead variant finding");
assert_eq!(variant.kind, SurfaceFindingKind::DeadPublic);
assert_eq!(variant.replacement, None);
assert_eq!(variant.visibility_start, None);
assert_eq!(variant.visibility_end, None);
}
#[test]
fn public_fix_reaches_the_narrowest_enabled_visibility_in_one_plan() {
let root = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Private,
);
let live_public = item(
(CRATE_A, 2),
(20, 30),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let facts = object(
"app",
CompilerFactDomain::Production,
vec![root.clone(), live_public],
vec![body((CRATE_A, 1), (CRATE_A, 2))],
vec![root.id],
Vec::new(),
);
let graph = SurfaceGraph::from_objects(&[&facts]).expect("merge graph");
assert_eq!(
graph
.analyze(&policy(&["app"], false))
.expect("preserved crate visibility")
.findings[0]
.replacement,
Some("pub(crate)")
);
assert_eq!(
graph
.analyze(&policy(&["app"], true))
.expect("narrowest visibility")
.findings[0]
.replacement,
Some("private")
);
}
#[test]
fn equivalent_views_merge_while_distinct_physical_declarations_remain_distinct() {
let first = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let second = item(
(CRATE_A, 2),
(20, 30),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let production = object(
"app",
CompilerFactDomain::Production,
vec![first.clone(), second.clone()],
Vec::new(),
Vec::new(),
Vec::new(),
);
let another_view = object(
"app",
CompilerFactDomain::NonProduction,
vec![
item(
(CRATE_B, 1),
(first.physical.span.start, first.physical.span.end),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
),
item(
(CRATE_B, 2),
(second.physical.span.start, second.physical.span.end),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
),
],
Vec::new(),
Vec::new(),
Vec::new(),
);
let graph = SurfaceGraph::from_objects(&[&production, &another_view]).expect("merge graph");
assert_eq!(graph.items.len(), 2);
let analysis = graph.analyze(&policy(&["app"], false)).expect("analyze graph");
assert_eq!(analysis.findings.len(), 2);
assert_ne!(analysis.findings[0].item_identity, analysis.findings[1].item_identity);
let reversed = SurfaceGraph::from_objects(&[&another_view, &production])
.expect("merge reversed graph")
.analyze(&policy(&["app"], false))
.expect("analyze reversed graph");
assert_eq!(analysis, reversed);
}
#[test]
fn conflicting_source_identities_cannot_be_merged() {
let definition = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let first = object(
"app",
CompilerFactDomain::Production,
vec![definition.clone()],
Vec::new(),
Vec::new(),
Vec::new(),
);
let mut second = object(
"app",
CompilerFactDomain::NonProduction,
vec![definition],
Vec::new(),
Vec::new(),
Vec::new(),
);
second.sources[0].identity = CompilerFactSourceIdentity::Exact("sha256:changed".to_string());
let error = SurfaceGraph::from_objects(&[&first, &second])
.err()
.expect("conflicting sources must fail");
assert!(error.to_string().contains("conflicting identities"));
}
#[test]
fn configured_products_replace_the_implicit_all_binary_root_set() {
let first_root = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let second_root = item(
(CRATE_B, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let first = object(
"first",
CompilerFactDomain::Production,
vec![first_root.clone()],
Vec::new(),
vec![first_root.id],
Vec::new(),
);
let second = object(
"second",
CompilerFactDomain::Production,
vec![second_root.clone()],
Vec::new(),
vec![second_root.id],
Vec::new(),
);
let graph = SurfaceGraph::from_objects(&[&first, &second]).expect("merge products");
let implicit = graph
.analyze(&policy(&["first", "second"], false))
.expect("analyze implicit products");
assert!(
implicit
.findings
.iter()
.all(|finding| finding.kind == SurfaceFindingKind::UnnecessaryPublic)
);
let selected = policy(&["first", "second"], false).with_products(vec![SurfaceProductSelection {
root: SurfaceProductRoot {
package: "first".to_string(),
target: "first".to_string(),
kind: SurfaceProductKind::Binary,
},
target: None,
}]);
let selected = graph.analyze(&selected).expect("analyze selected product");
assert_eq!(
selected.findings.iter().map(|finding| finding.kind).collect::<Vec<_>>(),
vec![SurfaceFindingKind::UnnecessaryPublic, SurfaceFindingKind::DeadPublic]
);
}
#[test]
fn selected_internal_library_is_seeded_by_real_workspace_consumers() {
let used = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let dead = item(
(CRATE_A, 2),
(20, 30),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let mut provider = object(
"provider",
CompilerFactDomain::Production,
vec![used, dead],
Vec::new(),
Vec::new(),
Vec::new(),
);
provider.unit.cargo_target = "provider_lib".to_string();
provider.unit.crate_name = "provider_lib".to_string();
let consumer_root = item(
(CRATE_B, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Private,
);
let consumer = object(
"consumer",
CompilerFactDomain::Production,
vec![consumer_root.clone()],
vec![body((CRATE_B, 1), (CRATE_A, 1))],
vec![consumer_root.id],
Vec::new(),
);
let closed = BTreeSet::from([SurfaceCompilerCrate::from(&provider)]);
let policy = SurfacePolicy::new(closed, false).with_products(vec![SurfaceProductSelection {
root: SurfaceProductRoot {
package: "provider".to_string(),
target: "provider_lib".to_string(),
kind: SurfaceProductKind::Library,
},
target: None,
}]);
let analysis = SurfaceGraph::from_objects(&[&provider, &consumer])
.expect("merge library consumers")
.analyze(&policy)
.expect("analyze selected library");
assert_eq!(analysis.findings.len(), 1);
assert_eq!(analysis.findings[0].kind, SurfaceFindingKind::DeadPublic);
assert_eq!(analysis.findings[0].declaration_start, 20);
assert!(
analysis
.items
.iter()
.any(|item| item.production_live && item.required_public),
"the compiled production consumer must seed and protect its provider declaration"
);
}
#[test]
fn selected_internal_library_without_consumers_reports_public_items_dead() {
let public = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let mut provider = object(
"provider",
CompilerFactDomain::Production,
vec![public],
Vec::new(),
Vec::new(),
Vec::new(),
);
provider.unit.cargo_target = "provider_lib".to_string();
provider.unit.crate_name = "provider_lib".to_string();
let policy =
SurfacePolicy::new(BTreeSet::from([SurfaceCompilerCrate::from(&provider)]), false).with_products(vec![
SurfaceProductSelection {
root: SurfaceProductRoot {
package: "provider".to_string(),
target: "provider_lib".to_string(),
kind: SurfaceProductKind::Library,
},
target: None,
},
]);
let analysis = SurfaceGraph::from_objects(&[&provider])
.expect("merge library")
.analyze(&policy)
.expect("analyze selected library");
assert_eq!(analysis.findings.len(), 1);
assert_eq!(analysis.findings[0].kind, SurfaceFindingKind::DeadPublic);
}
#[test]
fn one_open_compiler_crate_observation_preserves_a_shared_declaration() {
let declaration = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let mut library = object(
"mixed",
CompilerFactDomain::Production,
vec![declaration.clone()],
Vec::new(),
Vec::new(),
Vec::new(),
);
library.unit.cargo_target = "mixed_lib".to_string();
library.unit.crate_name = "mixed_lib".to_string();
let mut binary = object(
"mixed",
CompilerFactDomain::Production,
vec![declaration.clone()],
Vec::new(),
vec![declaration.id],
Vec::new(),
);
binary.unit.cargo_target = "mixed_bin".to_string();
binary.unit.crate_name = "mixed_bin".to_string();
let policy =
SurfacePolicy::new(BTreeSet::from([SurfaceCompilerCrate::from(&binary)]), false).with_products(vec![
SurfaceProductSelection {
root: SurfaceProductRoot {
package: "mixed".to_string(),
target: "mixed_bin".to_string(),
kind: SurfaceProductKind::Binary,
},
target: None,
},
]);
let analysis = SurfaceGraph::from_objects(&[&library, &binary])
.expect("merge mixed package observations")
.analyze(&policy)
.expect("analyze mixed package");
assert!(analysis.findings.is_empty());
assert_eq!(analysis.items[0].compiler_crates.len(), 2);
}
#[test]
fn every_conservative_retention_reason_suppresses_reduction() {
let reasons = [
CompilerFactRetentionReason::AllowDeadCode,
CompilerFactRetentionReason::ForeignExport,
CompilerFactRetentionReason::NoMangle,
CompilerFactRetentionReason::ExportName,
CompilerFactRetentionReason::Used,
CompilerFactRetentionReason::ProcMacro,
CompilerFactRetentionReason::UnresolvedTraitDispatch,
CompilerFactRetentionReason::RequiredImplementationInterface,
CompilerFactRetentionReason::GeneratedRegistration,
CompilerFactRetentionReason::IncompleteProvenance,
CompilerFactRetentionReason::ExternallyAddressed,
CompilerFactRetentionReason::Other(CompilerFactStringId(1)),
];
for reason in reasons {
let retained = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
let retained_id = retained.id;
let facts = object(
"app",
CompilerFactDomain::Production,
vec![retained],
Vec::new(),
Vec::new(),
vec![(retained_id, reason)],
);
let analysis = SurfaceGraph::from_objects(&[&facts])
.expect("merge retained item")
.analyze(&policy(&["app"], false))
.expect("analyze retained item");
assert!(analysis.findings.is_empty());
assert!(analysis.items[0].retained);
}
}
#[test]
fn generated_and_macro_expansion_declarations_never_produce_findings() {
for provenance in [
CompilerFactMacroProvenance::Generated,
CompilerFactMacroProvenance::Expansion(Some(CompilerFactSpan {
source: 0,
start: 0,
end: 10,
})),
] {
let mut declaration = item(
(CRATE_A, 1),
(0, 10),
CompilerFactItemKind::Function,
CompilerFactVisibility::Public,
);
declaration.macro_provenance = provenance;
let facts = object(
"app",
CompilerFactDomain::Production,
vec![declaration],
Vec::new(),
Vec::new(),
Vec::new(),
);
assert!(
SurfaceGraph::from_objects(&[&facts])
.expect("merge unsupported provenance")
.analyze(&policy(&["app"], false))
.expect("analyze unsupported provenance")
.findings
.is_empty()
);
}
}
#[test]
fn uniform_field_policy_suppresses_partial_struct_field_rewrites() {
let parent = item(
(CRATE_A, 1),
(0, 100),
CompilerFactItemKind::Struct,
CompilerFactVisibility::Private,
);
let mut public_field = item(
(CRATE_A, 2),
(10, 20),
CompilerFactItemKind::Field,
CompilerFactVisibility::Public,
);
public_field.parent = Some(parent.id);
let mut private_field = item(
(CRATE_A, 3),
(30, 40),
CompilerFactItemKind::Field,
CompilerFactVisibility::Private,
);
private_field.parent = Some(parent.id);
let facts = object(
"app",
CompilerFactDomain::Production,
vec![parent, public_field, private_field],
Vec::new(),
Vec::new(),
Vec::new(),
);
let graph = SurfaceGraph::from_objects(&[&facts]).expect("merge fields");
assert_eq!(
graph
.analyze(&policy(&["app"], false))
.expect("ordinary field policy")
.findings
.iter()
.filter(|finding| finding.item_kind == "field")
.count(),
1
);
assert_eq!(
graph
.analyze(&policy(&["app"], false).preserving_uniform_fields(true))
.expect("uniform field policy")
.findings
.iter()
.filter(|finding| finding.item_kind == "field")
.count(),
0
);
}
}