use crate::{
file_format::{AbilitySet, StructTypeParameter, Visibility},
file_format_common::VERSION_5,
normalized::Module,
};
use std::collections::BTreeSet;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub struct Compatibility {
pub struct_and_function_linking: bool,
pub struct_layout: bool,
}
impl Compatibility {
pub fn is_fully_compatible(&self) -> bool {
self.struct_and_function_linking && self.struct_layout
}
pub fn check(old_module: &Module, new_module: &Module) -> Compatibility {
let mut struct_and_function_linking = true;
let mut struct_layout = true;
if old_module.address != new_module.address || old_module.name != new_module.name {
struct_and_function_linking = false;
}
for (name, old_struct) in &old_module.structs {
let new_struct = match new_module.structs.get(name) {
Some(new_struct) => new_struct,
None => {
struct_and_function_linking = false;
continue;
}
};
if !struct_abilities_compatibile(old_struct.abilities, new_struct.abilities)
|| !struct_type_parameters_compatibile(
&old_struct.type_parameters,
&new_struct.type_parameters,
)
{
struct_and_function_linking = false;
}
if new_struct.fields != old_struct.fields {
struct_layout = false
}
}
for (name, old_func) in &old_module.exposed_functions {
let new_func = match new_module.exposed_functions.get(name) {
Some(new_func) => new_func,
None => {
struct_and_function_linking = false;
continue;
}
};
let is_vis_compatible = match (old_func.visibility, new_func.visibility) {
(Visibility::Public, Visibility::Public) => true,
(Visibility::Public, _) => false,
(Visibility::Friend, Visibility::Public)
| (Visibility::Friend, Visibility::Friend) => true,
(Visibility::Friend, _) => false,
(Visibility::Private, _) => true,
};
let is_entry_compatible = if old_module.file_format_version < VERSION_5
&& new_module.file_format_version < VERSION_5
{
old_func.is_entry == new_func.is_entry
} else {
!old_func.is_entry || new_func.is_entry
};
if !is_vis_compatible
|| !is_entry_compatible
|| old_func.parameters != new_func.parameters
|| old_func.return_ != new_func.return_
|| !fun_type_parameters_compatibile(
&old_func.type_parameters,
&new_func.type_parameters,
)
{
struct_and_function_linking = false;
}
}
let old_friend_module_ids: BTreeSet<_> = old_module.friends.iter().cloned().collect();
let new_friend_module_ids: BTreeSet<_> = new_module.friends.iter().cloned().collect();
if !old_friend_module_ids.is_subset(&new_friend_module_ids) {
struct_and_function_linking = false;
}
Compatibility {
struct_and_function_linking,
struct_layout,
}
}
}
fn struct_abilities_compatibile(old_abilities: AbilitySet, new_abilities: AbilitySet) -> bool {
old_abilities.is_subset(new_abilities)
}
fn fun_type_parameters_compatibile(
old_type_parameters: &[AbilitySet],
new_type_parameters: &[AbilitySet],
) -> bool {
old_type_parameters.len() == new_type_parameters.len()
&& old_type_parameters.iter().zip(new_type_parameters).all(
|(old_type_parameter_constraint, new_type_parameter_constraint)| {
type_parameter_constraints_compatibile(
*old_type_parameter_constraint,
*new_type_parameter_constraint,
)
},
)
}
fn struct_type_parameters_compatibile(
old_type_parameters: &[StructTypeParameter],
new_type_parameters: &[StructTypeParameter],
) -> bool {
old_type_parameters.len() == new_type_parameters.len()
&& old_type_parameters.iter().zip(new_type_parameters).all(
|(old_type_parameter, new_type_parameter)| {
type_parameter_phantom_decl_compatibile(old_type_parameter, new_type_parameter)
&& type_parameter_constraints_compatibile(
old_type_parameter.constraints,
new_type_parameter.constraints,
)
},
)
}
fn type_parameter_constraints_compatibile(
old_type_constraints: AbilitySet,
new_type_constraints: AbilitySet,
) -> bool {
new_type_constraints.is_subset(old_type_constraints)
}
fn type_parameter_phantom_decl_compatibile(
old_type_parameter: &StructTypeParameter,
new_type_parameter: &StructTypeParameter,
) -> bool {
!old_type_parameter.is_phantom || new_type_parameter.is_phantom
}