use crate::{
metadata::validation::{
context::{OwnedValidationContext, ValidationContext},
traits::OwnedValidator,
},
Error, Result,
};
pub struct OwnedDependencyValidator;
impl OwnedDependencyValidator {
#[must_use]
pub fn new() -> Self {
Self
}
fn validate_dependency_graph_integrity(&self, context: &OwnedValidationContext) -> Result<()> {
for type_entry in context.target_assembly_types() {
if let Some(base_type) = type_entry.base() {
if base_type.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has broken base type dependency (empty name)",
type_entry.name
),
});
}
}
for (_, interface_ref) in type_entry.interfaces.iter() {
if let Some(interface_type) = interface_ref.upgrade() {
if interface_type.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has broken interface dependency (empty name)",
type_entry.name
),
});
}
} else {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has broken interface dependency reference",
type_entry.name
),
});
}
}
for (_, nested_ref) in type_entry.nested_types.iter() {
if let Some(nested_type) = nested_ref.upgrade() {
if nested_type.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has broken nested type dependency (empty name)",
type_entry.name
),
});
}
} else {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has broken nested type dependency reference",
type_entry.name
),
});
}
}
for (_, generic_param) in type_entry.generic_params.iter() {
for (_, constraint_ref) in generic_param.constraints.iter() {
if let Some(constraint_type) = constraint_ref.upgrade() {
if constraint_type.name.is_empty() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' generic parameter '{}' has broken constraint dependency (empty name)",
type_entry.name, generic_param.name
),
});
}
} else {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' generic parameter '{}' has broken constraint dependency reference",
type_entry.name, generic_param.name
),
});
}
}
}
}
Ok(())
}
fn validate_transitive_dependency_satisfaction(
&self,
context: &OwnedValidationContext,
) -> Result<()> {
let methods = context.object().methods();
let mut dependency_graph = rustc_hash::FxHashMap::default();
for type_entry in context.target_assembly_types() {
let token = type_entry.token;
let mut dependencies = Vec::new();
if let Some(base_type) = type_entry.base() {
dependencies.push(base_type.token);
}
for (_, interface_ref) in type_entry.interfaces.iter() {
if let Some(interface_type) = interface_ref.upgrade() {
dependencies.push(interface_type.token);
}
}
for (_, nested_ref) in type_entry.nested_types.iter() {
if let Some(nested_type) = nested_ref.upgrade() {
dependencies.push(nested_type.token);
}
}
dependency_graph.insert(token, dependencies);
}
for type_entry in context.target_assembly_types() {
for (_, method_ref) in type_entry.methods.iter() {
if let Some(method_token) = method_ref.token() {
if let Some(method) = methods.get(&method_token) {
for (index, (_, param)) in method.value().params.iter().enumerate() {
if let Some(param_type_ref) = param.base.get() {
if param_type_ref.upgrade().is_none() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{}' in type '{}' has broken parameter {} type dependency",
method.value().name, type_entry.name, index
),
});
}
}
}
for (index, (_, local)) in method.value().local_vars.iter().enumerate() {
if local.base.upgrade().is_none() {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Method '{}' in type '{}' has broken local variable {} type dependency",
method.value().name, type_entry.name, index
),
});
}
}
}
}
}
}
Ok(())
}
fn validate_dependency_ordering(&self, context: &OwnedValidationContext) -> Result<()> {
for type_entry in context.target_assembly_types() {
if let Some(base_type) = type_entry.base() {
if base_type.token == type_entry.token {
let type_is_local = type_entry.get_external().is_none();
let base_is_local = base_type.get_external().is_none();
let is_same_assembly = match (type_is_local, base_is_local) {
(true, true) => true, _ => false,
};
if is_same_assembly {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has self-referential inheritance dependency",
type_entry.name
),
});
}
}
if base_type.fullname().is_empty() && !base_type.name.is_empty() {
}
}
for (_, interface_ref) in type_entry.interfaces.iter() {
if let Some(interface_type) = interface_ref.upgrade() {
if interface_type.token == type_entry.token {
let type_is_local = type_entry.get_external().is_none();
let interface_is_local = interface_type.get_external().is_none();
let is_same_assembly =
matches!((type_is_local, interface_is_local), (true, true));
if is_same_assembly {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has self-referential interface implementation dependency",
type_entry.name
),
});
}
}
}
}
for (_, nested_ref) in type_entry.nested_types.iter() {
if let Some(nested_type) = nested_ref.upgrade() {
if nested_type.token == type_entry.token {
let type_is_local = type_entry.get_external().is_none();
let nested_is_local = nested_type.get_external().is_none();
let is_same_assembly =
matches!((type_is_local, nested_is_local), (true, true));
if is_same_assembly {
return Err(Error::ValidationOwnedFailed {
validator: self.name().to_string(),
message: format!(
"Type '{}' has self-referential nested type dependency",
type_entry.name
),
});
}
}
}
}
}
Ok(())
}
}
impl OwnedValidator for OwnedDependencyValidator {
fn validate_owned(&self, context: &OwnedValidationContext) -> Result<()> {
self.validate_dependency_graph_integrity(context)?;
self.validate_transitive_dependency_satisfaction(context)?;
self.validate_dependency_ordering(context)?;
Ok(())
}
fn name(&self) -> &'static str {
"OwnedDependencyValidator"
}
fn priority(&self) -> u32 {
140
}
fn should_run(&self, context: &OwnedValidationContext) -> bool {
context.config().enable_cross_table_validation
}
}
impl Default for OwnedDependencyValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[cfg_attr(feature = "skip-expensive-tests", allow(unused_imports))]
mod tests {
use super::*;
use crate::{
metadata::validation::ValidationConfig,
test::{
factories::validation::dependency::owned_dependency_validator_file_factory,
owned_validator_test,
},
};
#[test]
#[cfg(not(feature = "skip-expensive-tests"))]
fn test_owned_dependency_validator() -> Result<()> {
let validator = OwnedDependencyValidator::new();
let config = ValidationConfig {
enable_cross_table_validation: true,
..Default::default()
};
owned_validator_test(
owned_dependency_validator_file_factory,
"OwnedDependencyValidator",
"ValidationOwnedFailed",
config,
|context| validator.validate_owned(context),
)
}
}