use std::path::{Path, PathBuf};
use serde_json::Value;
use xuanji::{Outcome, Polarity, Violation};
use crate::collect::collect_item_return_impl_traits;
use crate::crate_scope::{
ExternResolution, FileExternScope, dependency_names, extern_resolution, file_extern_scope,
};
use crate::driver::run_boundaries;
use crate::dsl::ImplTraitBoundary;
use crate::emit::{
MultiModuleViolationContext, SingleModuleViolationContext, push_multi_module_violations,
push_single_module_violations,
};
use crate::errors::unknown_module_error;
use crate::file_scope::{is_anchor_absent_from_unit, resolve_crate_units};
use crate::finding::{ExposureKind, SemanticFact, shape_finding, sort_attributed_facts};
use crate::resolve::{
ShapeExposure, UseMap, canonical_path_str, collect_uses, validate_path_operands,
};
use crate::rules::IMPL_TRAIT_RULE;
use crate::scan::walk_subtree_modules;
use crate::shape_scan::{
matches_forbidden_principal, operand_module_findings, shape_module_findings,
};
pub fn check_impl_trait(boundaries: &[ImplTraitBoundary], manifest_path: &Path) -> Outcome {
run_boundaries(boundaries, manifest_path, check_impl_trait_boundary)
}
pub(crate) fn check_impl_trait_boundary(
metadata: &Value,
boundary: &ImplTraitBoundary,
violations: &mut Vec<Violation>,
) -> Result<(), String> {
let (package, units) = resolve_crate_units(metadata, &boundary.crate_package)?;
let mut governed_somewhere = false;
let mut deferred: Option<String> = None;
for (root_file, src_dir, unit) in &units {
let unit_outcome = (|| -> Result<(), String> {
let src_dir = src_dir.as_path();
let unit = unit.as_str();
let rule_key = boundary.rule_key();
if boundary.including_submodules() {
let findings = if boundary.forbidden_operands.is_empty() {
impl_trait_subtree_findings(
src_dir,
root_file,
&boundary.module,
&boundary.crate_package,
)?
} else {
impl_trait_operand_subtree_findings(
src_dir,
root_file,
&boundary.module,
&boundary.forbidden_operands,
&boundary.crate_package,
&dependency_names(package),
)?
};
push_multi_module_violations(
violations,
MultiModuleViolationContext {
target: &boundary.module,
rule: IMPL_TRAIT_RULE,
rule_key,
reason: &boundary.reason,
severity: boundary.severity,
anchor: boundary.anchor(),
polarity: Polarity::DenyBreach,
crate_package: &boundary.crate_package,
unit,
},
findings,
);
return Ok(());
}
let findings = if boundary.forbidden_operands.is_empty() {
impl_trait_module_findings(
src_dir,
root_file,
&boundary.module,
&boundary.crate_package,
)?
} else {
impl_trait_operand_module_findings(
src_dir,
root_file,
&boundary.module,
&boundary.forbidden_operands,
&boundary.crate_package,
&dependency_names(package),
)?
};
push_single_module_violations(
violations,
SingleModuleViolationContext {
module: &boundary.module,
rule: IMPL_TRAIT_RULE,
rule_key,
reason: &boundary.reason,
severity: boundary.severity,
anchor: boundary.anchor(),
crate_package: &boundary.crate_package,
unit,
},
findings,
);
Ok(())
})();
match unit_outcome {
Ok(()) => governed_somewhere = true,
Err(reason)
if is_anchor_absent_from_unit(
&reason,
&unknown_module_error(&boundary.module, &boundary.crate_package),
) =>
{
if deferred.is_none() {
deferred = Some(reason);
}
}
Err(reason) => return Err(reason),
}
}
match deferred {
Some(reason) if !governed_somewhere => Err(reason),
_ => Ok(()),
}
}
pub(crate) fn impl_trait_subtree_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<(SemanticFact, String, PathBuf)>, String> {
let modules = walk_subtree_modules(src_dir, root_file, module, crate_package)?;
collect_impl_trait_subtree_findings(modules, &ImplTraitSubtreeFilter::Any)
}
pub(crate) fn impl_trait_operand_subtree_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
forbidden: &[String],
crate_package: &str,
dep_names: &[String],
) -> Result<Vec<(SemanticFact, String, PathBuf)>, String> {
validate_path_operands(forbidden)?;
let modules = walk_subtree_modules(src_dir, root_file, module, crate_package)?;
let resolution = extern_resolution(src_dir, root_file, crate_package, dep_names)?;
let filter = ImplTraitSubtreeFilter::Forbidden {
resolution,
paths: forbidden
.iter()
.map(|path| canonical_path_str(path))
.collect(),
};
collect_impl_trait_subtree_findings(modules, &filter)
}
enum ImplTraitSubtreeFilter {
Any,
Forbidden {
resolution: ExternResolution,
paths: Vec<String>,
},
}
impl ImplTraitSubtreeFilter {
fn retain(
&self,
module: &str,
uses: &UseMap,
file_scope: Option<&FileExternScope>,
exposures: &mut Vec<ShapeExposure>,
) {
let Self::Forbidden { resolution, paths } = self else {
return;
};
let file_scope =
file_scope.expect("a forbidden subtree filter precomputes one scope per module");
exposures.retain(|exposure| {
matches_forbidden_principal(exposure, uses, module, resolution, file_scope, paths)
});
}
}
fn collect_impl_trait_subtree_findings(
modules: Vec<(String, Vec<syn::Item>, PathBuf)>,
filter: &ImplTraitSubtreeFilter,
) -> Result<Vec<(SemanticFact, String, PathBuf)>, String> {
let mut findings = Vec::new();
let mut ordinal = 0usize;
for (mod_path, items, file) in &modules {
let uses = collect_uses(items);
let file_scope = match filter {
ImplTraitSubtreeFilter::Any => None,
ImplTraitSubtreeFilter::Forbidden { resolution, .. } => {
Some(file_extern_scope(resolution, items))
}
};
for item in items {
let mut collected = Vec::new();
collect_item_return_impl_traits(item, mod_path, &uses, ordinal, &mut collected);
ordinal += 1;
filter.retain(mod_path, &uses, file_scope.as_ref(), &mut collected);
findings.extend(collected.into_iter().map(|exposure| {
(
shape_finding(exposure, ExposureKind::ImplTrait),
mod_path.clone(),
file.clone(),
)
}));
}
}
sort_attributed_facts(&mut findings)?;
Ok(findings)
}
pub(crate) fn impl_trait_module_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<(SemanticFact, PathBuf)>, String> {
shape_module_findings(
src_dir,
root_file,
module,
crate_package,
|item, module, uses, ordinal, out| {
collect_item_return_impl_traits(item, module, uses, ordinal, out);
Ok(())
},
|exposure| shape_finding(exposure, ExposureKind::ImplTrait),
)
}
pub(crate) fn impl_trait_operand_module_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
forbidden: &[String],
crate_package: &str,
dep_names: &[String],
) -> Result<Vec<(SemanticFact, PathBuf)>, String> {
operand_module_findings(
src_dir,
root_file,
module,
forbidden,
crate_package,
dep_names,
(ExposureKind::ImplTrait, collect_item_return_impl_traits),
)
}