Skip to main content

cabin_core/
standard_compatibility.rs

1//! Resolver-level standard-compatibility core.
2//!
3//! One-to-one implementation of the normative specification in
4//! `docs/design/standard-compatibility/spec.md`; every public item
5//! cites the definition (D1, D2, ...) or lemma (L1, ...) it
6//! implements, and the tests cite the lemma they verify.  Pure data
7//! and logic only: no I/O, no logging, no globals.
8//!
9//! The effective-requirement recursion `R_L` (spec D10/T1 -
10//! transitive composition over the dependency graph) is deliberately
11//! not implemented here: it is a graph algorithm, and graph
12//! algorithms live in `cabin-workspace` (`cabin_workspace::standards`
13//! is the implementation).  This module operates on already-composed
14//! requirements: callers fold [`req_of_c`] / [`req_of_cxx`] values
15//! with [`Requirement::join`] along public edges and hand the result
16//! to [`edge_compatible`] as [`EffectiveRequirements`].
17//!
18//! Invariant I1 (spec D8): `gnu-extensions` never participates in
19//! compatibility.  Nothing in this module takes it as an input, and
20//! future changes must keep it that way.
21//!
22//! The languages themselves (spec D1) and their level chains (spec
23//! D2) are the existing [`crate::SourceLanguage`], [`CStandard`],
24//! and [`CxxStandard`] types; the two languages appear here as the
25//! paired `c` / `cxx` fields of the structs below.
26
27use crate::language_standard::{
28    CStandard, CxxStandard, InterfaceRequirement, LanguageStandardSettings,
29    ResolvedLanguageStandards, effective_c, effective_cxx,
30};
31use crate::{SourceLanguage, Target, classify_source};
32
33/// Spec D3: the per-language requirement domain `Req_L`, a finite
34/// chain under the strictness order `⊑` (spec L1).  The derived
35/// `Ord` follows declaration order, which is exactly that chain:
36/// `unconstrained ⊑ [⊥_L] ⊑ ... ⊑ [max Level_L] ⊑ forbidden` (the
37/// tests verify the derived order against D3's case-by-case
38/// definition by exhaustive enumeration).  Populating the reserved
39/// interface `max` later is a domain swap to the interval domain of
40/// D4's remark, not a signature change.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
42pub enum Requirement<S> {
43    /// Spec D3: imposes nothing on consumers.
44    Unconstrained,
45    /// Spec D3: `[m]` - requires a consumer level of at least the
46    /// carried minimum.
47    Min(S),
48    /// Spec D3: unsatisfiable.
49    Forbidden,
50}
51
52impl<S: Copy + Ord> Requirement<S> {
53    /// Spec D4: the join `r1 ⊔ r2` is the `⊑`-maximum of the two
54    /// requirements (well-defined because the chain is total, spec
55    /// L1; a bounded join-semilattice by spec L2).
56    #[must_use]
57    pub fn join(self, other: Self) -> Self {
58        self.max(other)
59    }
60
61    /// Spec D4: the set join `⨆ S` over a finite (multi)set, with
62    /// `⨆ ∅ = unconstrained`.
63    #[must_use]
64    pub fn join_all(requirements: impl IntoIterator<Item = Self>) -> Self {
65        requirements
66            .into_iter()
67            .fold(Self::Unconstrained, Self::join)
68    }
69
70    /// Spec D11: `satisfies(c, L, r)` for a consumer whose effective
71    /// compile level in `L` is `level`.
72    #[must_use]
73    pub fn satisfied_by(self, level: S) -> bool {
74        match self {
75            Self::Unconstrained => true,
76            Self::Min(min) => level >= min,
77            Self::Forbidden => false,
78        }
79    }
80
81    /// Spec D12: the satisfaction set `Sat_L(r)`, as the sub-slice
82    /// of `levels` a consumer may compile at.  `levels` must be the
83    /// full chain `Level_L` of spec D2 ([`CStandard::ALL`] /
84    /// [`CxxStandard::ALL`]).
85    #[must_use]
86    pub fn sat(self, levels: &[S]) -> Vec<S> {
87        levels
88            .iter()
89            .copied()
90            .filter(|&level| self.satisfied_by(level))
91            .collect()
92    }
93}
94
95/// Spec D6: `kind(t)` - whether a dependency target has translation
96/// units of its own.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum DependencyKind {
99    /// The target compiles translation units.
100    Compiled,
101    /// The target has no translation units; its headers are the
102    /// implementation.
103    HeaderOnly,
104}
105
106/// Spec D6: the resolved attributes of a dependency target, as
107/// produced by the manifest layer (precedence and inheritance
108/// already applied).  Per D6's population contract, `impl_*` is
109/// `Some` exactly when the target itself implements the language - a
110/// package-level implementation default alone never populates it.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct DependencyAttributes {
113    /// Spec D6: `kind(t)`.
114    pub kind: DependencyKind,
115    /// Spec D6: `impl_C(t)`; `None` is `⊥` (the target does not
116    /// implement C).
117    pub impl_c: Option<CStandard>,
118    /// Spec D6: `impl_C++(t)`; `None` is `⊥`.
119    pub impl_cxx: Option<CxxStandard>,
120    /// Spec D6: `decl_C(t)`; `None` is `⊥` (no explicit interface
121    /// declaration), `Some(InterfaceRequirement::None)` the declared
122    /// `"none"`, and `Some(InterfaceRequirement::Requirement(..))` a
123    /// declared minimum.
124    pub decl_c: Option<InterfaceRequirement<CStandard>>,
125    /// Spec D6: `decl_C++(t)`.
126    pub decl_cxx: Option<InterfaceRequirement<CxxStandard>>,
127}
128
129/// Spec D9: which row of the declaration-to-requirement table
130/// produced a `ReqOf` value.  Provenance-tracking callers (the
131/// effective-requirement composition of D10) record this alongside
132/// the requirement so a diagnostic can say *why* a target imposes
133/// what it imposes; the rows are the routing of D9, so this enum is
134/// derived here and nowhere else.
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum ReqOfSource {
137    /// Row 1: the explicit interface declaration `"none"`.
138    DeclaredNone,
139    /// Row 2: an explicit declared interface minimum.
140    Declared,
141    /// Row 3: header-only inference from the implementation
142    /// standard.
143    HeaderOnlyInference,
144    /// Row 4: a compiled target without an interface declaration
145    /// imposes no constraint.
146    CompiledNoDeclaration,
147    /// Rows 5-6: the cross-language default - permissive
148    /// (`unconstrained`) toward C++, strict (`forbidden`) toward C.
149    CrossLanguageDefault,
150}
151
152/// Spec D9: `ReqOf(d, C)`.  Shares rows 1-4 with the C++ side and
153/// lands on row 6 - the strict C++-to-C default `forbidden` - when
154/// the target neither declares a C interface nor implements C.
155#[must_use]
156pub fn req_of_c(dependency: &DependencyAttributes) -> Requirement<CStandard> {
157    req_of_c_with_source(dependency).0
158}
159
160/// Spec D9: `ReqOf(d, C)` together with the row that produced it.
161#[must_use]
162pub fn req_of_c_with_source(
163    dependency: &DependencyAttributes,
164) -> (Requirement<CStandard>, ReqOfSource) {
165    req_of(
166        dependency.kind,
167        dependency.decl_c,
168        dependency.impl_c,
169        Requirement::Forbidden,
170    )
171}
172
173/// Spec D9: `ReqOf(d, C++)`.  Shares rows 1-4 with the C side and
174/// lands on row 5 - the permissive C-to-C++ default `unconstrained` -
175/// when the target neither declares a C++ interface nor implements
176/// C++.
177#[must_use]
178pub fn req_of_cxx(dependency: &DependencyAttributes) -> Requirement<CxxStandard> {
179    req_of_cxx_with_source(dependency).0
180}
181
182/// Spec D9: `ReqOf(d, C++)` together with the row that produced it.
183#[must_use]
184pub fn req_of_cxx_with_source(
185    dependency: &DependencyAttributes,
186) -> (Requirement<CxxStandard>, ReqOfSource) {
187    req_of(
188        dependency.kind,
189        dependency.decl_cxx,
190        dependency.impl_cxx,
191        Requirement::Unconstrained,
192    )
193}
194
195/// Spec D9 rows 1-4, shared by both languages; `absent_default` is
196/// the language-specific row 5/6 outcome for `decl = ⊥`, `impl = ⊥`.
197fn req_of<S: Copy + Ord>(
198    kind: DependencyKind,
199    decl: Option<InterfaceRequirement<S>>,
200    implementation: Option<S>,
201    absent_default: Requirement<S>,
202) -> (Requirement<S>, ReqOfSource) {
203    match (decl, implementation) {
204        // Row 1: the declared `"none"` is forbidden.
205        (Some(InterfaceRequirement::None), _) => {
206            (Requirement::Forbidden, ReqOfSource::DeclaredNone)
207        }
208        // Row 2: an explicit declaration always wins, over inference
209        // and over both cross-language defaults.  `max` is reserved
210        // and always absent in v1 (spec D4 remark).
211        (Some(InterfaceRequirement::Requirement(requirement)), _) => {
212            (Requirement::Min(requirement.min), ReqOfSource::Declared)
213        }
214        // Row 3: header-only inference from the implementation
215        // standard.  Row 4: a compiled target without an interface
216        // declaration imposes no constraint.
217        (None, Some(min)) => match kind {
218            DependencyKind::HeaderOnly => (Requirement::Min(min), ReqOfSource::HeaderOnlyInference),
219            DependencyKind::Compiled => (
220                Requirement::Unconstrained,
221                ReqOfSource::CompiledNoDeclaration,
222            ),
223        },
224        // Rows 5-6: the cross-language defaults.
225        (None, None) => (absent_default, ReqOfSource::CrossLanguageDefault),
226    }
227}
228
229/// Spec D6 attribute mapping for one target, from resolved manifest
230/// attributes (target-over-package precedence and workspace
231/// inheritance already applied).
232///
233/// Population contract (D6): `impl_L` is `Some` exactly when the
234/// target itself implements `L` - a compiled target implements `L`
235/// when it has sources of `L` (level via target-over-package
236/// precedence), a header-only target only via a target-level
237/// implementation declaration.  `decl_L` is the explicit interface
238/// declaration only (target over package tier, workspace-inherited
239/// counting as declared) - never the build-time implementation-
240/// standard fallback.  This is the single source of truth for the
241/// mapping, shared by the resolver-graph pass (`cabin-build`) and the
242/// published-index derivation (`crate::index_standards`), so the two
243/// cannot drift.
244#[must_use]
245pub fn dependency_attributes(
246    target: &Target,
247    package_standards: &ResolvedLanguageStandards,
248    package_settings: &LanguageStandardSettings,
249) -> DependencyAttributes {
250    let header_only = target.kind.is_header_only();
251    let kind = if header_only {
252        DependencyKind::HeaderOnly
253    } else {
254        DependencyKind::Compiled
255    };
256
257    let has_sources_of = |language: SourceLanguage| {
258        target
259            .sources
260            .iter()
261            .any(|source| classify_source(source) == Some(language))
262    };
263
264    let impl_c = if header_only {
265        target.language.c_standard_value()
266    } else if has_sources_of(SourceLanguage::C) {
267        effective_c(package_standards, target).map(|resolved| resolved.standard)
268    } else {
269        None
270    };
271    let impl_cxx = if header_only {
272        target.language.cxx_standard_value()
273    } else if has_sources_of(SourceLanguage::Cxx) {
274        effective_cxx(package_standards, target).map(|resolved| resolved.standard)
275    } else {
276        None
277    };
278
279    // Package-level interface fields default a library's public
280    // interface (`docs/language-standards.md`); they never apply to
281    // executable-like targets.  Target-level interface fields only
282    // exist on library-like kinds (the manifest parser rejects them
283    // elsewhere).
284    let library_like = target.kind.is_library_like();
285    let decl_c = target.language.interface_c_standard_value().or_else(|| {
286        library_like
287            .then(|| package_settings.interface_c_standard_value())
288            .flatten()
289    });
290    let decl_cxx = target.language.interface_cxx_standard_value().or_else(|| {
291        library_like
292            .then(|| package_settings.interface_cxx_standard_value())
293            .flatten()
294    });
295
296    DependencyAttributes {
297        kind,
298        impl_c,
299        impl_cxx,
300        decl_c,
301        decl_cxx,
302    }
303}
304
305/// Spec D7: a consumer's compiled languages and effective compile
306/// levels.  `langs(c)` is the set of `Some` fields and `lvl(c, L)`
307/// the carried level, so `lvl` is total on `langs(c)` by
308/// construction.  A header-only consumer compiles no language: both
309/// fields `None`.
310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub struct ConsumerStandards {
312    /// Spec D7: `lvl(c, C)` when `C ∈ langs(c)`.
313    pub c: Option<CStandard>,
314    /// Spec D7: `lvl(c, C++)` when `C++ ∈ langs(c)`.
315    pub cxx: Option<CxxStandard>,
316}
317
318/// The `[resolver] incompatible-standards` preference knob.
319///
320/// The value vocabulary is deliberately identical to Cargo's
321/// `resolver.incompatible-rust-versions` (`allow` / `fallback`), and
322/// the semantics mirror it: standards are a *version-selection
323/// preference*, never a hard constraint on solvability.  See
324/// `docs/design/standard-compatibility/preference-mode.md`.
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
326pub enum IncompatibleStandards {
327    /// Standards have no influence on version selection - selection is
328    /// a pure function of semver constraints, and lockfiles never move
329    /// when standards change.  Incompatibilities surface only through
330    /// the post-resolution validation.  The strict/deterministic mode.
331    Allow,
332    /// Prefer standard-compatible versions, falling back to
333    /// newest-first when no compatible candidate exists (never a
334    /// resolution failure `Allow` would not also produce).  The
335    /// default.
336    #[default]
337    Fallback,
338}
339
340impl IncompatibleStandards {
341    /// Both values, for enumeration in tests and diagnostics.
342    pub const ALL: [Self; 2] = [Self::Allow, Self::Fallback];
343
344    /// The canonical spelling used in config, env vars, and messages.
345    #[must_use]
346    pub fn as_str(self) -> &'static str {
347        match self {
348            Self::Allow => "allow",
349            Self::Fallback => "fallback",
350        }
351    }
352
353    /// Parse a config / env-var value.  The accepted spellings are
354    /// Cargo's verbatim.
355    ///
356    /// # Errors
357    /// Returns [`UnknownIncompatibleStandards`] for any other value.
358    pub fn parse(value: &str) -> Result<Self, UnknownIncompatibleStandards> {
359        match value {
360            "allow" => Ok(Self::Allow),
361            "fallback" => Ok(Self::Fallback),
362            other => Err(UnknownIncompatibleStandards {
363                value: other.to_owned(),
364            }),
365        }
366    }
367}
368
369impl std::fmt::Display for IncompatibleStandards {
370    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371        f.write_str(self.as_str())
372    }
373}
374
375/// An `incompatible-standards` value that is neither `allow` nor
376/// `fallback`.
377#[derive(Debug, Clone, PartialEq, Eq)]
378pub struct UnknownIncompatibleStandards {
379    /// The rejected value, for the diagnostic.
380    pub value: String,
381}
382
383impl std::fmt::Display for UnknownIncompatibleStandards {
384    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
385        write!(
386            f,
387            "invalid incompatible-standards value {:?}; expected one of: allow, fallback",
388            self.value
389        )
390    }
391}
392
393impl std::error::Error for UnknownIncompatibleStandards {}
394
395/// Spec D10: a dependency target's effective requirement `R_L(d)`
396/// for each language, already composed by the caller (the `R_L`
397/// recursion itself is outside this module; see the module docs).
398#[derive(Debug, Clone, Copy, PartialEq, Eq)]
399pub struct EffectiveRequirements {
400    /// Spec D10: `R_C(d)`.
401    pub c: Requirement<CStandard>,
402    /// Spec D10: `R_C++(d)`.
403    pub cxx: Requirement<CxxStandard>,
404}
405
406/// Spec D13: edge compatibility - the conjunction, over every
407/// language the consumer compiles, of `satisfies(c, L, R_L(d))`.
408/// Languages the consumer does not compile impose nothing, so a
409/// header-only consumer (empty `langs(c)`) is compatible vacuously;
410/// the edge's own public/private classification does not appear in
411/// the condition.
412#[must_use]
413pub fn edge_compatible(consumer: ConsumerStandards, dependency: EffectiveRequirements) -> bool {
414    consumer
415        .c
416        .is_none_or(|level| dependency.c.satisfied_by(level))
417        && consumer
418            .cxx
419            .is_none_or(|level| dependency.cxx.satisfied_by(level))
420}
421
422/// Spec D14: package-version viability - the conjunction of edge
423/// compatibility (spec D13) over every dependency edge resolving to
424/// the candidate version.  One incompatible edge excludes the
425/// version.
426#[must_use]
427pub fn version_viable(
428    edges: impl IntoIterator<Item = (ConsumerStandards, EffectiveRequirements)>,
429) -> bool {
430    edges
431        .into_iter()
432        .all(|(consumer, dependency)| edge_compatible(consumer, dependency))
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438    use crate::language_standard::StandardRequirement;
439
440    const KINDS: [DependencyKind; 2] = [DependencyKind::Compiled, DependencyKind::HeaderOnly];
441
442    /// The full `Req_L` chain (spec D3) for the given `Level_L`.
443    fn all_requirements<S: Copy>(levels: &[S]) -> Vec<Requirement<S>> {
444        let mut requirements = vec![Requirement::Unconstrained];
445        requirements.extend(levels.iter().copied().map(Requirement::Min));
446        requirements.push(Requirement::Forbidden);
447        requirements
448    }
449
450    /// Spec D3's order definition transcribed case by case: the
451    /// oracle every lemma test uses, and the definition the derived
452    /// `Ord` must match (verified exhaustively in the L1 test).
453    fn spec_le<S: Copy + Ord>(r: Requirement<S>, s: Requirement<S>) -> bool {
454        match (r, s) {
455            (Requirement::Unconstrained, _) | (_, Requirement::Forbidden) => true,
456            (Requirement::Min(a), Requirement::Min(b)) => a <= b,
457            // No other pairs are related; `⊑` is reflexive.
458            _ => r == s,
459        }
460    }
461
462    fn interface_min<S>(min: S) -> InterfaceRequirement<S> {
463        InterfaceRequirement::Requirement(StandardRequirement { min, max: None })
464    }
465
466    /// `⊥` plus every level: the domain of `impl_L(t)` (spec D6).
467    fn optional_levels<S: Copy>(levels: &[S]) -> Vec<Option<S>> {
468        let mut options = vec![None];
469        options.extend(levels.iter().copied().map(Some));
470        options
471    }
472
473    fn check_l1_finite_chain<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
474        let requirements = all_requirements(levels);
475        for &r in &requirements {
476            // Reflexivity, and the chain's bounds: unconstrained is
477            // least, forbidden greatest.
478            assert!(spec_le(r, r));
479            assert!(spec_le(Requirement::Unconstrained, r));
480            assert!(spec_le(r, Requirement::Forbidden));
481            for &s in &requirements {
482                // The derived order is exactly D3's definition.
483                assert_eq!(r <= s, spec_le(r, s), "derived Ord vs D3 at {r:?} ⊑ {s:?}");
484                // Totality and antisymmetry.
485                assert!(spec_le(r, s) || spec_le(s, r), "totality at {r:?}, {s:?}");
486                if spec_le(r, s) && spec_le(s, r) {
487                    assert_eq!(r, s, "antisymmetry at {r:?}, {s:?}");
488                }
489                // Transitivity.
490                for &t in &requirements {
491                    if spec_le(r, s) && spec_le(s, t) {
492                        assert!(spec_le(r, t), "transitivity at {r:?}, {s:?}, {t:?}");
493                    }
494                }
495            }
496        }
497    }
498
499    /// L1: `(Req_L, ⊑)` is a finite total order with least element
500    /// `unconstrained` and greatest element `forbidden`, and the
501    /// derived `Ord` coincides with D3's case-by-case definition.
502    #[test]
503    fn l1_requirement_domain_is_a_finite_chain() {
504        check_l1_finite_chain(&CStandard::ALL);
505        check_l1_finite_chain(&CxxStandard::ALL);
506    }
507
508    fn check_l2_bounded_semilattice<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
509        let requirements = all_requirements(levels);
510        for &r in &requirements {
511            // Idempotence, identity, absorption.
512            assert_eq!(r.join(r), r);
513            assert_eq!(Requirement::Unconstrained.join(r), r);
514            assert_eq!(Requirement::Forbidden.join(r), Requirement::Forbidden);
515            for &s in &requirements {
516                let join = r.join(s);
517                // Commutativity.
518                assert_eq!(join, s.join(r));
519                // `⊔` is the least upper bound.
520                assert!(
521                    spec_le(r, join) && spec_le(s, join),
522                    "upper bound at {r:?}, {s:?}"
523                );
524                for &upper in &requirements {
525                    if spec_le(r, upper) && spec_le(s, upper) {
526                        assert!(spec_le(join, upper), "leastness at {r:?}, {s:?}, {upper:?}");
527                    }
528                }
529                // `⨆` is order- and multiplicity-independent.
530                assert_eq!(Requirement::join_all([r, s]), join);
531                assert_eq!(Requirement::join_all([s, r]), join);
532                assert_eq!(Requirement::join_all([r, r, s]), join);
533                // Associativity.
534                for &t in &requirements {
535                    assert_eq!(r.join(s).join(t), r.join(s.join(t)));
536                    assert_eq!(Requirement::join_all([r, s, t]), r.join(s).join(t));
537                }
538            }
539        }
540        // The empty-join convention and the flattening law
541        // `⨆(S ∪ S') = ⨆S ⊔ ⨆S'`, over every pair of subsets.
542        assert_eq!(
543            Requirement::join_all(std::iter::empty::<Requirement<S>>()),
544            Requirement::Unconstrained
545        );
546        for union_of in 0u32..(1 << requirements.len()) {
547            for other in 0u32..(1 << requirements.len()) {
548                assert_eq!(
549                    mask_join(&requirements, union_of | other),
550                    mask_join(&requirements, union_of).join(mask_join(&requirements, other))
551                );
552            }
553        }
554    }
555
556    /// The join of the sub-multiset of `requirements` selected by
557    /// `mask`.
558    fn mask_join<S: Copy + Ord>(requirements: &[Requirement<S>], mask: u32) -> Requirement<S> {
559        Requirement::join_all(
560            requirements
561                .iter()
562                .enumerate()
563                .filter(|&(index, _)| mask & (1 << index) != 0)
564                .map(|(_, &requirement)| requirement),
565        )
566    }
567
568    /// L2: `(Req_L, ⊑, ⊔)` is a bounded join-semilattice - `⊔` is the
569    /// least upper bound; associative, commutative, idempotent, with
570    /// `unconstrained` as identity and `forbidden` absorbing - and
571    /// `⨆` is well-defined on finite multisets with `⨆∅ =
572    /// unconstrained` and `⨆(S ∪ S') = ⨆S ⊔ ⨆S'`.
573    #[test]
574    fn l2_join_is_a_bounded_semilattice() {
575        check_l2_bounded_semilattice(&CStandard::ALL);
576        check_l2_bounded_semilattice(&CxxStandard::ALL);
577    }
578
579    fn check_l3_sat_characterization<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
580        let bottom = levels[0];
581        let degenerate = (Requirement::Min(bottom), Requirement::Unconstrained);
582        for &r1 in &all_requirements(levels) {
583            for &r2 in &all_requirements(levels) {
584                let sat1 = r1.sat(levels);
585                let sat2 = r2.sat(levels);
586                let included = sat2.iter().all(|level| sat1.contains(level));
587                // (1) Soundness.
588                if spec_le(r1, r2) {
589                    assert!(included, "L3(1) at {r1:?}, {r2:?}");
590                }
591                // (2) Completeness, except the one degenerate pair.
592                if included && (r1, r2) != degenerate {
593                    assert!(spec_le(r1, r2), "L3(2) at {r1:?}, {r2:?}");
594                }
595                // (3) Induced equivalence.  `sat` filters the same
596                // ordered slice, so `Vec` equality is set equality.
597                let equivalent = r1 == r2 || (r1, r2) == degenerate || (r2, r1) == degenerate;
598                assert_eq!(sat1 == sat2, equivalent, "L3(3) at {r1:?}, {r2:?}");
599            }
600        }
601        // The exception is genuine: `Sat` agrees on the pair while
602        // the order does not relate it.
603        assert_eq!(
604            Requirement::Min(bottom).sat(levels),
605            Requirement::<S>::Unconstrained.sat(levels)
606        );
607        assert!(!spec_le(
608            Requirement::Min(bottom),
609            Requirement::Unconstrained
610        ));
611    }
612
613    /// L3: `⊑` coincides with reverse `Sat`-inclusion - soundness,
614    /// completeness up to the single degenerate pair
615    /// `([⊥_L], unconstrained)`, and the induced equivalence.
616    #[test]
617    fn l3_sat_inclusion_characterizes_strictness() {
618        check_l3_sat_characterization(&CStandard::ALL);
619        check_l3_sat_characterization(&CxxStandard::ALL);
620    }
621
622    fn check_l4_join_is_intersection<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
623        let requirements = all_requirements(levels);
624        let intersect = |a: &[S], b: &[S]| -> Vec<S> {
625            a.iter()
626                .copied()
627                .filter(|level| b.contains(level))
628                .collect()
629        };
630        for &r1 in &requirements {
631            for &r2 in &requirements {
632                let expected = intersect(&r1.sat(levels), &r2.sat(levels));
633                assert_eq!(r1.join(r2).sat(levels), expected, "L4 at {r1:?}, {r2:?}");
634                // The finite generalization, on triples.
635                for &r3 in &requirements {
636                    assert_eq!(
637                        Requirement::join_all([r1, r2, r3]).sat(levels),
638                        intersect(&expected, &r3.sat(levels))
639                    );
640                }
641            }
642        }
643        // The empty intersection convention is `Level_L` itself.
644        assert_eq!(
645            Requirement::join_all(std::iter::empty::<Requirement<S>>()).sat(levels),
646            levels
647        );
648    }
649
650    /// L4: `Sat(r1 ⊔ r2) = Sat(r1) ∩ Sat(r2)`, and the finite
651    /// generalization with the empty join denoting all of `Level_L`.
652    #[test]
653    fn l4_sat_of_join_is_intersection() {
654        check_l4_join_is_intersection(&CStandard::ALL);
655        check_l4_join_is_intersection(&CxxStandard::ALL);
656    }
657
658    fn check_l5_antitonicity<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
659        for &r1 in &all_requirements(levels) {
660            for &r2 in &all_requirements(levels) {
661                if !spec_le(r1, r2) {
662                    continue;
663                }
664                for &level in levels {
665                    if r2.satisfied_by(level) {
666                        assert!(r1.satisfied_by(level), "L5 at {r1:?} ⊑ {r2:?}, {level:?}");
667                    }
668                }
669            }
670        }
671    }
672
673    /// L5: `satisfies` is antitone in the requirement - satisfying a
674    /// stricter requirement satisfies every laxer one.
675    #[test]
676    fn l5_satisfies_is_antitone() {
677        check_l5_antitonicity(&CStandard::ALL);
678        check_l5_antitonicity(&CxxStandard::ALL);
679    }
680
681    fn check_l6_upward_closure<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
682        for &requirement in &all_requirements(levels) {
683            for &level in levels {
684                if !requirement.satisfied_by(level) {
685                    continue;
686                }
687                for &higher in levels {
688                    if higher >= level {
689                        assert!(
690                            requirement.satisfied_by(higher),
691                            "L6 at {requirement:?}, {level:?} ≤ {higher:?}"
692                        );
693                    }
694                }
695            }
696        }
697    }
698
699    /// L6: every satisfaction set is upward closed - raising a
700    /// consumer's effective level never breaks satisfaction.
701    #[test]
702    fn l6_satisfaction_sets_are_upward_closed() {
703        check_l6_upward_closure(&CStandard::ALL);
704        check_l6_upward_closure(&CxxStandard::ALL);
705    }
706
707    fn check_l7_monotone_joins<S: Copy + Ord + std::fmt::Debug>(levels: &[S]) {
708        let requirements = all_requirements(levels);
709        // Subset claim: `S ⊆ S' ⟹ ⨆S ⊑ ⨆S'`, over every subset pair.
710        for superset in 0u32..(1 << requirements.len()) {
711            let mut subset = superset;
712            loop {
713                assert!(spec_le(
714                    mask_join(&requirements, subset),
715                    mask_join(&requirements, superset)
716                ));
717                if subset == 0 {
718                    break;
719                }
720                subset = (subset - 1) & superset;
721            }
722        }
723        // Pointwise claim, exhaustively for k = 2.
724        for &r1 in &requirements {
725            for &s1 in &requirements {
726                if !spec_le(r1, s1) {
727                    continue;
728                }
729                for &r2 in &requirements {
730                    for &s2 in &requirements {
731                        if spec_le(r2, s2) {
732                            assert!(spec_le(r1.join(r2), s1.join(s2)));
733                        }
734                    }
735                }
736            }
737        }
738    }
739
740    /// L7: set joins are monotone, both in the subset sense and
741    /// pointwise.
742    #[test]
743    fn l7_set_joins_are_monotone() {
744        check_l7_monotone_joins(&CStandard::ALL);
745        check_l7_monotone_joins(&CxxStandard::ALL);
746    }
747
748    fn c_attrs(
749        kind: DependencyKind,
750        decl: Option<InterfaceRequirement<CStandard>>,
751        implementation: Option<CStandard>,
752    ) -> DependencyAttributes {
753        DependencyAttributes {
754            kind,
755            impl_c: implementation,
756            impl_cxx: None,
757            decl_c: decl,
758            decl_cxx: None,
759        }
760    }
761
762    fn cxx_attrs(
763        kind: DependencyKind,
764        decl: Option<InterfaceRequirement<CxxStandard>>,
765        implementation: Option<CxxStandard>,
766    ) -> DependencyAttributes {
767        DependencyAttributes {
768            kind,
769            impl_c: None,
770            impl_cxx: implementation,
771            decl_c: None,
772            decl_cxx: decl,
773        }
774    }
775
776    /// D9 row 1: the declared `"none"` is forbidden, whatever the
777    /// kind and implementation say.
778    #[test]
779    fn d9_row_1_declared_none_is_forbidden() {
780        for kind in KINDS {
781            for implementation in optional_levels(&CStandard::ALL) {
782                assert_eq!(
783                    req_of_c_with_source(&c_attrs(
784                        kind,
785                        Some(InterfaceRequirement::None),
786                        implementation
787                    )),
788                    (Requirement::Forbidden, ReqOfSource::DeclaredNone)
789                );
790            }
791            for implementation in optional_levels(&CxxStandard::ALL) {
792                assert_eq!(
793                    req_of_cxx_with_source(&cxx_attrs(
794                        kind,
795                        Some(InterfaceRequirement::None),
796                        implementation
797                    )),
798                    (Requirement::Forbidden, ReqOfSource::DeclaredNone)
799                );
800            }
801        }
802    }
803
804    /// D9 row 2: an explicit declared minimum always wins - over
805    /// header-only inference and over both cross-language defaults.
806    #[test]
807    fn d9_row_2_explicit_declaration_wins() {
808        for kind in KINDS {
809            for min in CStandard::ALL {
810                for implementation in optional_levels(&CStandard::ALL) {
811                    assert_eq!(
812                        req_of_c_with_source(&c_attrs(
813                            kind,
814                            Some(interface_min(min)),
815                            implementation
816                        )),
817                        (Requirement::Min(min), ReqOfSource::Declared)
818                    );
819                }
820            }
821            for min in CxxStandard::ALL {
822                for implementation in optional_levels(&CxxStandard::ALL) {
823                    assert_eq!(
824                        req_of_cxx_with_source(&cxx_attrs(
825                            kind,
826                            Some(interface_min(min)),
827                            implementation
828                        )),
829                        (Requirement::Min(min), ReqOfSource::Declared)
830                    );
831                }
832            }
833        }
834    }
835
836    /// D9 row 3: a header-only target without a declaration infers
837    /// its interface minimum from its implementation standard.
838    #[test]
839    fn d9_row_3_header_only_inference() {
840        for min in CStandard::ALL {
841            assert_eq!(
842                req_of_c_with_source(&c_attrs(DependencyKind::HeaderOnly, None, Some(min))),
843                (Requirement::Min(min), ReqOfSource::HeaderOnlyInference)
844            );
845        }
846        for min in CxxStandard::ALL {
847            assert_eq!(
848                req_of_cxx_with_source(&cxx_attrs(DependencyKind::HeaderOnly, None, Some(min))),
849                (Requirement::Min(min), ReqOfSource::HeaderOnlyInference)
850            );
851        }
852    }
853
854    /// D9 row 4: a compiled target without a declaration imposes no
855    /// constraint.
856    #[test]
857    fn d9_row_4_compiled_absence_is_unconstrained() {
858        for implementation in CStandard::ALL {
859            assert_eq!(
860                req_of_c_with_source(&c_attrs(
861                    DependencyKind::Compiled,
862                    None,
863                    Some(implementation)
864                )),
865                (
866                    Requirement::Unconstrained,
867                    ReqOfSource::CompiledNoDeclaration
868                )
869            );
870        }
871        for implementation in CxxStandard::ALL {
872            assert_eq!(
873                req_of_cxx_with_source(&cxx_attrs(
874                    DependencyKind::Compiled,
875                    None,
876                    Some(implementation)
877                )),
878                (
879                    Requirement::Unconstrained,
880                    ReqOfSource::CompiledNoDeclaration
881                )
882            );
883        }
884    }
885
886    /// D9 row 5: the permissive C-to-C++ default - no C++
887    /// implementation and no declaration is consumable from any C++
888    /// level.
889    #[test]
890    fn d9_row_5_permissive_c_to_cxx_default() {
891        for kind in KINDS {
892            assert_eq!(
893                req_of_cxx_with_source(&cxx_attrs(kind, None, None)),
894                (
895                    Requirement::Unconstrained,
896                    ReqOfSource::CrossLanguageDefault
897                )
898            );
899        }
900    }
901
902    /// D9 row 6: the strict C++-to-C default - no C implementation
903    /// and no declaration is not consumable from C.
904    #[test]
905    fn d9_row_6_strict_cxx_to_c_default() {
906        for kind in KINDS {
907            assert_eq!(
908                req_of_c_with_source(&c_attrs(kind, None, None)),
909                (Requirement::Forbidden, ReqOfSource::CrossLanguageDefault)
910            );
911        }
912    }
913
914    /// D13: a header-only consumer compiles no language, so every
915    /// edge out of it is compatible vacuously - even against
916    /// `forbidden` on both sides.
917    #[test]
918    fn d13_header_only_consumer_is_vacuously_compatible() {
919        let header_only = ConsumerStandards { c: None, cxx: None };
920        for &c in &all_requirements(&CStandard::ALL) {
921            for &cxx in &all_requirements(&CxxStandard::ALL) {
922                assert!(edge_compatible(
923                    header_only,
924                    EffectiveRequirements { c, cxx }
925                ));
926            }
927        }
928    }
929
930    /// D14: viability is a conjunction over the version's incoming
931    /// edges - vacuously true with no edges, and one incompatible
932    /// edge excludes the version.
933    #[test]
934    fn d14_viability_is_a_conjunction_over_edges() {
935        assert!(version_viable(std::iter::empty()));
936        let compatible = (
937            ConsumerStandards {
938                c: None,
939                cxx: Some(CxxStandard::Cxx20),
940            },
941            EffectiveRequirements {
942                c: Requirement::Forbidden,
943                cxx: Requirement::Min(CxxStandard::Cxx17),
944            },
945        );
946        let incompatible = (
947            ConsumerStandards {
948                c: None,
949                cxx: Some(CxxStandard::Cxx11),
950            },
951            compatible.1,
952        );
953        assert!(version_viable([compatible]));
954        assert!(!version_viable([compatible, incompatible]));
955    }
956
957    /// `IncompatibleStandards` round-trips Cargo's verbatim vocabulary
958    /// and rejects anything else; `fallback` is the default.
959    #[test]
960    fn incompatible_standards_parses_cargo_vocabulary() {
961        assert_eq!(
962            IncompatibleStandards::default(),
963            IncompatibleStandards::Fallback
964        );
965        for value in IncompatibleStandards::ALL {
966            assert_eq!(IncompatibleStandards::parse(value.as_str()), Ok(value));
967        }
968        let err = IncompatibleStandards::parse("warn").unwrap_err();
969        assert_eq!(err.value, "warn");
970        assert!(err.to_string().contains("allow, fallback"));
971    }
972
973    /// Appendix reference table: `satisfies` over all of `CxxLevel`
974    /// for the four requirements the worked examples use.
975    #[test]
976    fn appendix_reference_table_satisfies_over_cxx_levels() {
977        let table: [(Requirement<CxxStandard>, [bool; 7]); 4] = [
978            (Requirement::Unconstrained, [true; 7]),
979            (
980                Requirement::Min(CxxStandard::Cxx17),
981                [false, false, false, true, true, true, true],
982            ),
983            (
984                Requirement::Min(CxxStandard::Cxx20),
985                [false, false, false, false, true, true, true],
986            ),
987            (Requirement::Forbidden, [false; 7]),
988        ];
989        for (requirement, cells) in table {
990            for (level, expected) in CxxStandard::ALL.into_iter().zip(cells) {
991                assert_eq!(
992                    requirement.satisfied_by(level),
993                    expected,
994                    "{requirement:?} at {level}"
995                );
996            }
997        }
998    }
999
1000    /// Appendix example 1: a compiled C++23 implementation with a
1001    /// declared `c++17` interface, consumed from `c++17`.
1002    #[test]
1003    fn appendix_example_1_declared_interface_on_compiled_target() {
1004        let z = DependencyAttributes {
1005            kind: DependencyKind::Compiled,
1006            impl_c: None,
1007            impl_cxx: Some(CxxStandard::Cxx23),
1008            decl_c: None,
1009            decl_cxx: Some(interface_min(CxxStandard::Cxx17)),
1010        };
1011        // D9 row 2: the explicit declaration wins; the
1012        // implementation standard never enters.
1013        assert_eq!(req_of_cxx(&z), Requirement::Min(CxxStandard::Cxx17));
1014        // Contrast: absent declaration on this compiled target
1015        // would give unconstrained by row 4.
1016        let z_undeclared = DependencyAttributes {
1017            decl_cxx: None,
1018            ..z
1019        };
1020        assert_eq!(req_of_cxx(&z_undeclared), Requirement::Unconstrained);
1021        // R(Z) = ReqOf(Z) ⊔ ⨆∅ = [c++17] (D10's arithmetic on a
1022        // dependency with no public deps, composed here by hand).
1023        let r_z = req_of_cxx(&z).join(Requirement::join_all([]));
1024        assert_eq!(r_z, Requirement::Min(CxxStandard::Cxx17));
1025        // Edge (X, Z) at c++17 is compatible; Z's C-side forbidden
1026        // (row 6) is inert for a language X does not compile.
1027        let x = ConsumerStandards {
1028            c: None,
1029            cxx: Some(CxxStandard::Cxx17),
1030        };
1031        let z_requirements = EffectiveRequirements {
1032            c: req_of_c(&z),
1033            cxx: r_z,
1034        };
1035        assert_eq!(z_requirements.c, Requirement::Forbidden);
1036        assert!(edge_compatible(x, z_requirements));
1037        // The only edge resolving to Z's version: viable (D14).
1038        assert!(version_viable([(x, z_requirements)]));
1039    }
1040
1041    /// Appendix example 2: the diamond - consumers at c++17 and
1042    /// c++23 sharing one dependency version; the incompatible edge
1043    /// poisons the version for the whole graph.
1044    #[test]
1045    fn appendix_example_2_diamond_shared_version() {
1046        let z = cxx_attrs(
1047            DependencyKind::Compiled,
1048            Some(interface_min(CxxStandard::Cxx20)),
1049            None,
1050        );
1051        let z_requirements = EffectiveRequirements {
1052            c: req_of_c(&z),
1053            cxx: req_of_cxx(&z).join(Requirement::join_all([])),
1054        };
1055        assert_eq!(z_requirements.cxx, Requirement::Min(CxxStandard::Cxx20));
1056        let y = ConsumerStandards {
1057            c: None,
1058            cxx: Some(CxxStandard::Cxx23),
1059        };
1060        let x = ConsumerStandards {
1061            c: None,
1062            cxx: Some(CxxStandard::Cxx17),
1063        };
1064        assert!(edge_compatible(y, z_requirements));
1065        assert!(!edge_compatible(x, z_requirements));
1066        // The (Y, Z) edge cannot rescue the version.
1067        assert!(!version_viable([(y, z_requirements), (x, z_requirements)]));
1068    }
1069
1070    /// Appendix example 3: `"none"` on a transitive public
1071    /// dependency poisons the root; a private edge would not
1072    /// propagate.
1073    #[test]
1074    fn appendix_example_3_none_poisons_the_public_chain() {
1075        let b = cxx_attrs(
1076            DependencyKind::Compiled,
1077            Some(InterfaceRequirement::None),
1078            Some(CxxStandard::Cxx17),
1079        );
1080        assert_eq!(req_of_cxx(&b), Requirement::Forbidden);
1081        let a = cxx_attrs(DependencyKind::Compiled, None, Some(CxxStandard::Cxx17));
1082        assert_eq!(req_of_cxx(&a), Requirement::Unconstrained);
1083        // Public edge A → B: R(A) = ReqOf(A) ⊔ R(B) = forbidden -
1084        // L2's absorbing element; nothing recovers.
1085        let r_a = req_of_cxx(&a).join(req_of_cxx(&b));
1086        assert_eq!(r_a, Requirement::Forbidden);
1087        // Incompatible at every consumer level, even c++26.
1088        let root = ConsumerStandards {
1089            c: None,
1090            cxx: Some(CxxStandard::Cxx26),
1091        };
1092        let a_requirements = EffectiveRequirements {
1093            c: req_of_c(&a).join(req_of_c(&b)),
1094            cxx: r_a,
1095        };
1096        assert!(!edge_compatible(root, a_requirements));
1097        assert!(!version_viable([(root, a_requirements)]));
1098        // Had A → B been private, D10 would not fold R(B) in and
1099        // the root would be unaffected.
1100        let a_private = EffectiveRequirements {
1101            c: req_of_c(&a),
1102            cxx: req_of_cxx(&a),
1103        };
1104        assert!(edge_compatible(root, a_private));
1105    }
1106
1107    /// Appendix example 4: a mixed-language consumer must satisfy
1108    /// every language it compiles; one failed conjunct suffices.
1109    #[test]
1110    fn appendix_example_4_mixed_language_consumer() {
1111        let w = DependencyAttributes {
1112            kind: DependencyKind::Compiled,
1113            impl_c: Some(CStandard::C17),
1114            impl_cxx: None,
1115            decl_c: Some(interface_min(CStandard::C17)),
1116            decl_cxx: None,
1117        };
1118        let w_requirements = EffectiveRequirements {
1119            c: req_of_c(&w),
1120            cxx: req_of_cxx(&w),
1121        };
1122        // D9 row 2 on the C side, row 5 (permissive default) on the
1123        // C++ side.
1124        assert_eq!(w_requirements.c, Requirement::Min(CStandard::C17));
1125        assert_eq!(w_requirements.cxx, Requirement::Unconstrained);
1126        // c11 < c17 (no equivalence special case): the C conjunct
1127        // fails even though the C++ conjunct passes.
1128        let m = ConsumerStandards {
1129            c: Some(CStandard::C11),
1130            cxx: Some(CxxStandard::Cxx20),
1131        };
1132        assert!(!edge_compatible(m, w_requirements));
1133        // Raising the C level to c17 or c23 fixes it (L6).
1134        for c in [CStandard::C17, CStandard::C23] {
1135            assert!(edge_compatible(
1136                ConsumerStandards { c: Some(c), ..m },
1137                w_requirements
1138            ));
1139        }
1140        // A C++-only consumer takes only the first conjunct.
1141        let cxx_only = ConsumerStandards {
1142            c: None,
1143            cxx: Some(CxxStandard::Cxx20),
1144        };
1145        assert!(edge_compatible(cxx_only, w_requirements));
1146        // The strict opposite direction: a compiled C++ library
1147        // without `interface-c-standard` fails at every C level.
1148        let v = cxx_attrs(DependencyKind::Compiled, None, Some(CxxStandard::Cxx20));
1149        let v_requirements = EffectiveRequirements {
1150            c: req_of_c(&v),
1151            cxx: req_of_cxx(&v),
1152        };
1153        assert_eq!(v_requirements.c, Requirement::Forbidden);
1154        for c in CStandard::ALL {
1155            let consumer = ConsumerStandards {
1156                c: Some(c),
1157                cxx: Some(CxxStandard::Cxx20),
1158            };
1159            assert!(!edge_compatible(consumer, v_requirements));
1160        }
1161    }
1162
1163    /// Appendix example 5: header-only inference, then the explicit
1164    /// declaration relaxing the requirement down the chain.
1165    #[test]
1166    fn appendix_example_5_header_only_inference_then_relaxation() {
1167        let h = cxx_attrs(DependencyKind::HeaderOnly, None, Some(CxxStandard::Cxx20));
1168        // D9 row 3: the headers are the implementation.
1169        assert_eq!(req_of_cxx(&h), Requirement::Min(CxxStandard::Cxx20));
1170        let x = ConsumerStandards {
1171            c: None,
1172            cxx: Some(CxxStandard::Cxx17),
1173        };
1174        let h_requirements = EffectiveRequirements {
1175            c: req_of_c(&h),
1176            cxx: req_of_cxx(&h),
1177        };
1178        assert!(!edge_compatible(x, h_requirements));
1179        // The author audits the headers and declares
1180        // `interface-cxx-standard = "c++17"`: row 2 preempts row 3.
1181        let h_declared = DependencyAttributes {
1182            decl_cxx: Some(interface_min(CxxStandard::Cxx17)),
1183            ..h
1184        };
1185        assert_eq!(
1186            req_of_cxx(&h_declared),
1187            Requirement::Min(CxxStandard::Cxx17)
1188        );
1189        let declared_requirements = EffectiveRequirements {
1190            c: req_of_c(&h_declared),
1191            cxx: req_of_cxx(&h_declared),
1192        };
1193        assert!(edge_compatible(x, declared_requirements));
1194        // The relaxation moved down the chain (the first deliberate
1195        // exception in the remark after C3).
1196        assert!(req_of_cxx(&h_declared) <= req_of_cxx(&h));
1197    }
1198}