use crate::language_standard::{
CStandard, CxxStandard, InterfaceRequirement, LanguageStandardSettings,
ResolvedLanguageStandards, effective_c, effective_cxx,
};
use crate::{SourceLanguage, Target, classify_source};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Requirement<S> {
Unconstrained,
Min(S),
Forbidden,
}
impl<S: Copy + Ord> Requirement<S> {
#[must_use]
pub fn join(self, other: Self) -> Self {
self.max(other)
}
#[must_use]
pub fn join_all(requirements: impl IntoIterator<Item = Self>) -> Self {
requirements
.into_iter()
.fold(Self::Unconstrained, Self::join)
}
#[must_use]
pub fn satisfied_by(self, level: S) -> bool {
match self {
Self::Unconstrained => true,
Self::Min(min) => level >= min,
Self::Forbidden => false,
}
}
#[must_use]
pub fn sat(self, levels: &[S]) -> Vec<S> {
levels
.iter()
.copied()
.filter(|&level| self.satisfied_by(level))
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DependencyKind {
Compiled,
HeaderOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DependencyAttributes {
pub kind: DependencyKind,
pub impl_c: Option<CStandard>,
pub impl_cxx: Option<CxxStandard>,
pub decl_c: Option<InterfaceRequirement<CStandard>>,
pub decl_cxx: Option<InterfaceRequirement<CxxStandard>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReqOfSource {
DeclaredNone,
Declared,
HeaderOnlyInference,
CompiledNoDeclaration,
CrossLanguageDefault,
}
#[must_use]
pub fn req_of_c(dependency: &DependencyAttributes) -> Requirement<CStandard> {
req_of_c_with_source(dependency).0
}
#[must_use]
pub fn req_of_c_with_source(
dependency: &DependencyAttributes,
) -> (Requirement<CStandard>, ReqOfSource) {
req_of(
dependency.kind,
dependency.decl_c,
dependency.impl_c,
Requirement::Forbidden,
)
}
#[must_use]
pub fn req_of_cxx(dependency: &DependencyAttributes) -> Requirement<CxxStandard> {
req_of_cxx_with_source(dependency).0
}
#[must_use]
pub fn req_of_cxx_with_source(
dependency: &DependencyAttributes,
) -> (Requirement<CxxStandard>, ReqOfSource) {
req_of(
dependency.kind,
dependency.decl_cxx,
dependency.impl_cxx,
Requirement::Unconstrained,
)
}
fn req_of<S: Copy + Ord>(
kind: DependencyKind,
decl: Option<InterfaceRequirement<S>>,
implementation: Option<S>,
absent_default: Requirement<S>,
) -> (Requirement<S>, ReqOfSource) {
match (decl, implementation) {
(Some(InterfaceRequirement::None), _) => {
(Requirement::Forbidden, ReqOfSource::DeclaredNone)
}
(Some(InterfaceRequirement::Requirement(requirement)), _) => {
(Requirement::Min(requirement.min), ReqOfSource::Declared)
}
(None, Some(min)) => match kind {
DependencyKind::HeaderOnly => (Requirement::Min(min), ReqOfSource::HeaderOnlyInference),
DependencyKind::Compiled => (
Requirement::Unconstrained,
ReqOfSource::CompiledNoDeclaration,
),
},
(None, None) => (absent_default, ReqOfSource::CrossLanguageDefault),
}
}
#[must_use]
pub fn dependency_attributes(
target: &Target,
package_standards: &ResolvedLanguageStandards,
package_settings: &LanguageStandardSettings,
) -> DependencyAttributes {
let header_only = target.kind.is_header_only();
let kind = if header_only {
DependencyKind::HeaderOnly
} else {
DependencyKind::Compiled
};
let has_sources_of = |language: SourceLanguage| {
target
.sources
.iter()
.any(|source| classify_source(source) == Some(language))
};
let impl_c = if header_only {
target.language.c_standard_value()
} else if has_sources_of(SourceLanguage::C) {
effective_c(package_standards, target).map(|resolved| resolved.standard)
} else {
None
};
let impl_cxx = if header_only {
target.language.cxx_standard_value()
} else if has_sources_of(SourceLanguage::Cxx) {
effective_cxx(package_standards, target).map(|resolved| resolved.standard)
} else {
None
};
let library_like = target.kind.is_library_like();
let decl_c = target.language.interface_c_standard_value().or_else(|| {
library_like
.then(|| package_settings.interface_c_standard_value())
.flatten()
});
let decl_cxx = target.language.interface_cxx_standard_value().or_else(|| {
library_like
.then(|| package_settings.interface_cxx_standard_value())
.flatten()
});
DependencyAttributes {
kind,
impl_c,
impl_cxx,
decl_c,
decl_cxx,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConsumerStandards {
pub c: Option<CStandard>,
pub cxx: Option<CxxStandard>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum IncompatibleStandards {
Allow,
#[default]
Fallback,
}
impl IncompatibleStandards {
pub const ALL: [Self; 2] = [Self::Allow, Self::Fallback];
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Allow => "allow",
Self::Fallback => "fallback",
}
}
pub fn parse(value: &str) -> Result<Self, UnknownIncompatibleStandards> {
match value {
"allow" => Ok(Self::Allow),
"fallback" => Ok(Self::Fallback),
other => Err(UnknownIncompatibleStandards {
value: other.to_owned(),
}),
}
}
}
impl std::fmt::Display for IncompatibleStandards {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnknownIncompatibleStandards {
pub value: String,
}
impl std::fmt::Display for UnknownIncompatibleStandards {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"invalid incompatible-standards value {:?}; expected one of: allow, fallback",
self.value
)
}
}
impl std::error::Error for UnknownIncompatibleStandards {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EffectiveRequirements {
pub c: Requirement<CStandard>,
pub cxx: Requirement<CxxStandard>,
}
#[must_use]
pub fn edge_compatible(consumer: ConsumerStandards, dependency: EffectiveRequirements) -> bool {
consumer
.c
.is_none_or(|level| dependency.c.satisfied_by(level))
&& consumer
.cxx
.is_none_or(|level| dependency.cxx.satisfied_by(level))
}
#[must_use]
pub fn version_viable(
edges: impl IntoIterator<Item = (ConsumerStandards, EffectiveRequirements)>,
) -> bool {
edges
.into_iter()
.all(|(consumer, dependency)| edge_compatible(consumer, dependency))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::language_standard::StandardRequirement;
const KINDS: [DependencyKind; 2] = [DependencyKind::Compiled, DependencyKind::HeaderOnly];
fn all_requirements<S: Copy>(levels: &[S]) -> Vec<Requirement<S>> {
let mut requirements = vec![Requirement::Unconstrained];
requirements.extend(levels.iter().copied().map(Requirement::Min));
requirements.push(Requirement::Forbidden);
requirements
}
fn spec_le<S: Copy + Ord>(r: Requirement<S>, s: Requirement<S>) -> bool {
match (r, s) {
(Requirement::Unconstrained, _) | (_, Requirement::Forbidden) => true,
(Requirement::Min(a), Requirement::Min(b)) => a <= b,
_ => r == s,
}
}
fn interface_min<S>(min: S) -> InterfaceRequirement<S> {
InterfaceRequirement::Requirement(StandardRequirement { min, max: None })
}
fn optional_levels<S: Copy>(levels: &[S]) -> Vec<Option<S>> {
let mut options = vec![None];
options.extend(levels.iter().copied().map(Some));
options
}
fn check_l1_finite_chain<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
let requirements = all_requirements(levels);
for &r in &requirements {
assert!(spec_le(r, r));
assert!(spec_le(Requirement::Unconstrained, r));
assert!(spec_le(r, Requirement::Forbidden));
for &s in &requirements {
assert_eq!(r <= s, spec_le(r, s), "derived Ord vs D3 at {r:?} ⊑ {s:?}");
assert!(spec_le(r, s) || spec_le(s, r), "totality at {r:?}, {s:?}");
if spec_le(r, s) && spec_le(s, r) {
assert_eq!(r, s, "antisymmetry at {r:?}, {s:?}");
}
for &t in &requirements {
if spec_le(r, s) && spec_le(s, t) {
assert!(spec_le(r, t), "transitivity at {r:?}, {s:?}, {t:?}");
}
}
}
}
}
#[test]
fn l1_requirement_domain_is_a_finite_chain() {
check_l1_finite_chain(&CStandard::ALL);
check_l1_finite_chain(&CxxStandard::ALL);
}
fn check_l2_bounded_semilattice<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
let requirements = all_requirements(levels);
for &r in &requirements {
assert_eq!(r.join(r), r);
assert_eq!(Requirement::Unconstrained.join(r), r);
assert_eq!(Requirement::Forbidden.join(r), Requirement::Forbidden);
for &s in &requirements {
let join = r.join(s);
assert_eq!(join, s.join(r));
assert!(
spec_le(r, join) && spec_le(s, join),
"upper bound at {r:?}, {s:?}"
);
for &upper in &requirements {
if spec_le(r, upper) && spec_le(s, upper) {
assert!(spec_le(join, upper), "leastness at {r:?}, {s:?}, {upper:?}");
}
}
assert_eq!(Requirement::join_all([r, s]), join);
assert_eq!(Requirement::join_all([s, r]), join);
assert_eq!(Requirement::join_all([r, r, s]), join);
for &t in &requirements {
assert_eq!(r.join(s).join(t), r.join(s.join(t)));
assert_eq!(Requirement::join_all([r, s, t]), r.join(s).join(t));
}
}
}
assert_eq!(
Requirement::join_all(std::iter::empty::<Requirement<S>>()),
Requirement::Unconstrained
);
for union_of in 0u32..(1 << requirements.len()) {
for other in 0u32..(1 << requirements.len()) {
assert_eq!(
mask_join(&requirements, union_of | other),
mask_join(&requirements, union_of).join(mask_join(&requirements, other))
);
}
}
}
fn mask_join<S: Copy + Ord>(requirements: &[Requirement<S>], mask: u32) -> Requirement<S> {
Requirement::join_all(
requirements
.iter()
.enumerate()
.filter(|&(index, _)| mask & (1 << index) != 0)
.map(|(_, &requirement)| requirement),
)
}
#[test]
fn l2_join_is_a_bounded_semilattice() {
check_l2_bounded_semilattice(&CStandard::ALL);
check_l2_bounded_semilattice(&CxxStandard::ALL);
}
fn check_l3_sat_characterization<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
let bottom = levels[0];
let degenerate = (Requirement::Min(bottom), Requirement::Unconstrained);
for &r1 in &all_requirements(levels) {
for &r2 in &all_requirements(levels) {
let sat1 = r1.sat(levels);
let sat2 = r2.sat(levels);
let included = sat2.iter().all(|level| sat1.contains(level));
if spec_le(r1, r2) {
assert!(included, "L3(1) at {r1:?}, {r2:?}");
}
if included && (r1, r2) != degenerate {
assert!(spec_le(r1, r2), "L3(2) at {r1:?}, {r2:?}");
}
let equivalent = r1 == r2 || (r1, r2) == degenerate || (r2, r1) == degenerate;
assert_eq!(sat1 == sat2, equivalent, "L3(3) at {r1:?}, {r2:?}");
}
}
assert_eq!(
Requirement::Min(bottom).sat(levels),
Requirement::<S>::Unconstrained.sat(levels)
);
assert!(!spec_le(
Requirement::Min(bottom),
Requirement::Unconstrained
));
}
#[test]
fn l3_sat_inclusion_characterizes_strictness() {
check_l3_sat_characterization(&CStandard::ALL);
check_l3_sat_characterization(&CxxStandard::ALL);
}
fn check_l4_join_is_intersection<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
let requirements = all_requirements(levels);
let intersect = |a: &[S], b: &[S]| -> Vec<S> {
a.iter()
.copied()
.filter(|level| b.contains(level))
.collect()
};
for &r1 in &requirements {
for &r2 in &requirements {
let expected = intersect(&r1.sat(levels), &r2.sat(levels));
assert_eq!(r1.join(r2).sat(levels), expected, "L4 at {r1:?}, {r2:?}");
for &r3 in &requirements {
assert_eq!(
Requirement::join_all([r1, r2, r3]).sat(levels),
intersect(&expected, &r3.sat(levels))
);
}
}
}
assert_eq!(
Requirement::join_all(std::iter::empty::<Requirement<S>>()).sat(levels),
levels
);
}
#[test]
fn l4_sat_of_join_is_intersection() {
check_l4_join_is_intersection(&CStandard::ALL);
check_l4_join_is_intersection(&CxxStandard::ALL);
}
fn check_l5_antitonicity<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
for &r1 in &all_requirements(levels) {
for &r2 in &all_requirements(levels) {
if !spec_le(r1, r2) {
continue;
}
for &level in levels {
if r2.satisfied_by(level) {
assert!(r1.satisfied_by(level), "L5 at {r1:?} ⊑ {r2:?}, {level:?}");
}
}
}
}
}
#[test]
fn l5_satisfies_is_antitone() {
check_l5_antitonicity(&CStandard::ALL);
check_l5_antitonicity(&CxxStandard::ALL);
}
fn check_l6_upward_closure<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
for &requirement in &all_requirements(levels) {
for &level in levels {
if !requirement.satisfied_by(level) {
continue;
}
for &higher in levels {
if higher >= level {
assert!(
requirement.satisfied_by(higher),
"L6 at {requirement:?}, {level:?} ≤ {higher:?}"
);
}
}
}
}
}
#[test]
fn l6_satisfaction_sets_are_upward_closed() {
check_l6_upward_closure(&CStandard::ALL);
check_l6_upward_closure(&CxxStandard::ALL);
}
fn check_l7_monotone_joins<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
let requirements = all_requirements(levels);
for superset in 0u32..(1 << requirements.len()) {
let mut subset = superset;
loop {
assert!(spec_le(
mask_join(&requirements, subset),
mask_join(&requirements, superset)
));
if subset == 0 {
break;
}
subset = (subset - 1) & superset;
}
}
for &r1 in &requirements {
for &s1 in &requirements {
if !spec_le(r1, s1) {
continue;
}
for &r2 in &requirements {
for &s2 in &requirements {
if spec_le(r2, s2) {
assert!(spec_le(r1.join(r2), s1.join(s2)));
}
}
}
}
}
}
#[test]
fn l7_set_joins_are_monotone() {
check_l7_monotone_joins(&CStandard::ALL);
check_l7_monotone_joins(&CxxStandard::ALL);
}
fn c_attrs(
kind: DependencyKind,
decl: Option<InterfaceRequirement<CStandard>>,
implementation: Option<CStandard>,
) -> DependencyAttributes {
DependencyAttributes {
kind,
impl_c: implementation,
impl_cxx: None,
decl_c: decl,
decl_cxx: None,
}
}
fn cxx_attrs(
kind: DependencyKind,
decl: Option<InterfaceRequirement<CxxStandard>>,
implementation: Option<CxxStandard>,
) -> DependencyAttributes {
DependencyAttributes {
kind,
impl_c: None,
impl_cxx: implementation,
decl_c: None,
decl_cxx: decl,
}
}
#[test]
fn d9_row_1_declared_none_is_forbidden() {
for kind in KINDS {
for implementation in optional_levels(&CStandard::ALL) {
assert_eq!(
req_of_c_with_source(&c_attrs(
kind,
Some(InterfaceRequirement::None),
implementation
)),
(Requirement::Forbidden, ReqOfSource::DeclaredNone)
);
}
for implementation in optional_levels(&CxxStandard::ALL) {
assert_eq!(
req_of_cxx_with_source(&cxx_attrs(
kind,
Some(InterfaceRequirement::None),
implementation
)),
(Requirement::Forbidden, ReqOfSource::DeclaredNone)
);
}
}
}
#[test]
fn d9_row_2_explicit_declaration_wins() {
for kind in KINDS {
for min in CStandard::ALL {
for implementation in optional_levels(&CStandard::ALL) {
assert_eq!(
req_of_c_with_source(&c_attrs(
kind,
Some(interface_min(min)),
implementation
)),
(Requirement::Min(min), ReqOfSource::Declared)
);
}
}
for min in CxxStandard::ALL {
for implementation in optional_levels(&CxxStandard::ALL) {
assert_eq!(
req_of_cxx_with_source(&cxx_attrs(
kind,
Some(interface_min(min)),
implementation
)),
(Requirement::Min(min), ReqOfSource::Declared)
);
}
}
}
}
#[test]
fn d9_row_3_header_only_inference() {
for min in CStandard::ALL {
assert_eq!(
req_of_c_with_source(&c_attrs(DependencyKind::HeaderOnly, None, Some(min))),
(Requirement::Min(min), ReqOfSource::HeaderOnlyInference)
);
}
for min in CxxStandard::ALL {
assert_eq!(
req_of_cxx_with_source(&cxx_attrs(DependencyKind::HeaderOnly, None, Some(min))),
(Requirement::Min(min), ReqOfSource::HeaderOnlyInference)
);
}
}
#[test]
fn d9_row_4_compiled_absence_is_unconstrained() {
for implementation in CStandard::ALL {
assert_eq!(
req_of_c_with_source(&c_attrs(
DependencyKind::Compiled,
None,
Some(implementation)
)),
(
Requirement::Unconstrained,
ReqOfSource::CompiledNoDeclaration
)
);
}
for implementation in CxxStandard::ALL {
assert_eq!(
req_of_cxx_with_source(&cxx_attrs(
DependencyKind::Compiled,
None,
Some(implementation)
)),
(
Requirement::Unconstrained,
ReqOfSource::CompiledNoDeclaration
)
);
}
}
#[test]
fn d9_row_5_permissive_c_to_cxx_default() {
for kind in KINDS {
assert_eq!(
req_of_cxx_with_source(&cxx_attrs(kind, None, None)),
(
Requirement::Unconstrained,
ReqOfSource::CrossLanguageDefault
)
);
}
}
#[test]
fn d9_row_6_strict_cxx_to_c_default() {
for kind in KINDS {
assert_eq!(
req_of_c_with_source(&c_attrs(kind, None, None)),
(Requirement::Forbidden, ReqOfSource::CrossLanguageDefault)
);
}
}
#[test]
fn d13_header_only_consumer_is_vacuously_compatible() {
let header_only = ConsumerStandards { c: None, cxx: None };
for &c in &all_requirements(&CStandard::ALL) {
for &cxx in &all_requirements(&CxxStandard::ALL) {
assert!(edge_compatible(
header_only,
EffectiveRequirements { c, cxx }
));
}
}
}
#[test]
fn d14_viability_is_a_conjunction_over_edges() {
assert!(version_viable(std::iter::empty()));
let compatible = (
ConsumerStandards {
c: None,
cxx: Some(CxxStandard::Cxx20),
},
EffectiveRequirements {
c: Requirement::Forbidden,
cxx: Requirement::Min(CxxStandard::Cxx17),
},
);
let incompatible = (
ConsumerStandards {
c: None,
cxx: Some(CxxStandard::Cxx11),
},
compatible.1,
);
assert!(version_viable([compatible]));
assert!(!version_viable([compatible, incompatible]));
}
#[test]
fn incompatible_standards_parses_cargo_vocabulary() {
assert_eq!(
IncompatibleStandards::default(),
IncompatibleStandards::Fallback
);
for value in IncompatibleStandards::ALL {
assert_eq!(IncompatibleStandards::parse(value.as_str()), Ok(value));
}
let err = IncompatibleStandards::parse("warn").unwrap_err();
assert_eq!(err.value, "warn");
assert!(err.to_string().contains("allow, fallback"));
}
#[test]
fn appendix_reference_table_satisfies_over_cxx_levels() {
let table: [(Requirement<CxxStandard>, [bool; 7]); 4] = [
(Requirement::Unconstrained, [true; 7]),
(
Requirement::Min(CxxStandard::Cxx17),
[false, false, false, true, true, true, true],
),
(
Requirement::Min(CxxStandard::Cxx20),
[false, false, false, false, true, true, true],
),
(Requirement::Forbidden, [false; 7]),
];
for (requirement, cells) in table {
for (level, expected) in CxxStandard::ALL.into_iter().zip(cells) {
assert_eq!(
requirement.satisfied_by(level),
expected,
"{requirement:?} at {level}"
);
}
}
}
#[test]
fn appendix_example_1_declared_interface_on_compiled_target() {
let z = DependencyAttributes {
kind: DependencyKind::Compiled,
impl_c: None,
impl_cxx: Some(CxxStandard::Cxx23),
decl_c: None,
decl_cxx: Some(interface_min(CxxStandard::Cxx17)),
};
assert_eq!(req_of_cxx(&z), Requirement::Min(CxxStandard::Cxx17));
let z_undeclared = DependencyAttributes {
decl_cxx: None,
..z
};
assert_eq!(req_of_cxx(&z_undeclared), Requirement::Unconstrained);
let r_z = req_of_cxx(&z).join(Requirement::join_all([]));
assert_eq!(r_z, Requirement::Min(CxxStandard::Cxx17));
let x = ConsumerStandards {
c: None,
cxx: Some(CxxStandard::Cxx17),
};
let z_requirements = EffectiveRequirements {
c: req_of_c(&z),
cxx: r_z,
};
assert_eq!(z_requirements.c, Requirement::Forbidden);
assert!(edge_compatible(x, z_requirements));
assert!(version_viable([(x, z_requirements)]));
}
#[test]
fn appendix_example_2_diamond_shared_version() {
let z = cxx_attrs(
DependencyKind::Compiled,
Some(interface_min(CxxStandard::Cxx20)),
None,
);
let z_requirements = EffectiveRequirements {
c: req_of_c(&z),
cxx: req_of_cxx(&z).join(Requirement::join_all([])),
};
assert_eq!(z_requirements.cxx, Requirement::Min(CxxStandard::Cxx20));
let y = ConsumerStandards {
c: None,
cxx: Some(CxxStandard::Cxx23),
};
let x = ConsumerStandards {
c: None,
cxx: Some(CxxStandard::Cxx17),
};
assert!(edge_compatible(y, z_requirements));
assert!(!edge_compatible(x, z_requirements));
assert!(!version_viable([(y, z_requirements), (x, z_requirements)]));
}
#[test]
fn appendix_example_3_none_poisons_the_public_chain() {
let b = cxx_attrs(
DependencyKind::Compiled,
Some(InterfaceRequirement::None),
Some(CxxStandard::Cxx17),
);
assert_eq!(req_of_cxx(&b), Requirement::Forbidden);
let a = cxx_attrs(DependencyKind::Compiled, None, Some(CxxStandard::Cxx17));
assert_eq!(req_of_cxx(&a), Requirement::Unconstrained);
let r_a = req_of_cxx(&a).join(req_of_cxx(&b));
assert_eq!(r_a, Requirement::Forbidden);
let root = ConsumerStandards {
c: None,
cxx: Some(CxxStandard::Cxx26),
};
let a_requirements = EffectiveRequirements {
c: req_of_c(&a).join(req_of_c(&b)),
cxx: r_a,
};
assert!(!edge_compatible(root, a_requirements));
assert!(!version_viable([(root, a_requirements)]));
let a_private = EffectiveRequirements {
c: req_of_c(&a),
cxx: req_of_cxx(&a),
};
assert!(edge_compatible(root, a_private));
}
#[test]
fn appendix_example_4_mixed_language_consumer() {
let w = DependencyAttributes {
kind: DependencyKind::Compiled,
impl_c: Some(CStandard::C17),
impl_cxx: None,
decl_c: Some(interface_min(CStandard::C17)),
decl_cxx: None,
};
let w_requirements = EffectiveRequirements {
c: req_of_c(&w),
cxx: req_of_cxx(&w),
};
assert_eq!(w_requirements.c, Requirement::Min(CStandard::C17));
assert_eq!(w_requirements.cxx, Requirement::Unconstrained);
let m = ConsumerStandards {
c: Some(CStandard::C11),
cxx: Some(CxxStandard::Cxx20),
};
assert!(!edge_compatible(m, w_requirements));
for c in [CStandard::C17, CStandard::C23] {
assert!(edge_compatible(
ConsumerStandards { c: Some(c), ..m },
w_requirements
));
}
let cxx_only = ConsumerStandards {
c: None,
cxx: Some(CxxStandard::Cxx20),
};
assert!(edge_compatible(cxx_only, w_requirements));
let v = cxx_attrs(DependencyKind::Compiled, None, Some(CxxStandard::Cxx20));
let v_requirements = EffectiveRequirements {
c: req_of_c(&v),
cxx: req_of_cxx(&v),
};
assert_eq!(v_requirements.c, Requirement::Forbidden);
for c in CStandard::ALL {
let consumer = ConsumerStandards {
c: Some(c),
cxx: Some(CxxStandard::Cxx20),
};
assert!(!edge_compatible(consumer, v_requirements));
}
}
#[test]
fn appendix_example_5_header_only_inference_then_relaxation() {
let h = cxx_attrs(DependencyKind::HeaderOnly, None, Some(CxxStandard::Cxx20));
assert_eq!(req_of_cxx(&h), Requirement::Min(CxxStandard::Cxx20));
let x = ConsumerStandards {
c: None,
cxx: Some(CxxStandard::Cxx17),
};
let h_requirements = EffectiveRequirements {
c: req_of_c(&h),
cxx: req_of_cxx(&h),
};
assert!(!edge_compatible(x, h_requirements));
let h_declared = DependencyAttributes {
decl_cxx: Some(interface_min(CxxStandard::Cxx17)),
..h
};
assert_eq!(
req_of_cxx(&h_declared),
Requirement::Min(CxxStandard::Cxx17)
);
let declared_requirements = EffectiveRequirements {
c: req_of_c(&h_declared),
cxx: req_of_cxx(&h_declared),
};
assert!(edge_compatible(x, declared_requirements));
assert!(req_of_cxx(&h_declared) <= req_of_cxx(&h));
}
}