cabin_build/standard_compat.rs
1//! Post-resolution language-standard compatibility checking.
2//!
3//! Evaluates the edge-compatibility model of
4//! `docs/design/standard-compatibility/spec.md` over the planner's
5//! resolved target graph: per-target `ReqOf` attributes are mapped
6//! per the spec's D6 population contract, `cabin_workspace::standards`
7//! composes the effective requirement `R_L` along public edges (D10),
8//! and every resolved dependency edge is checked per D13 for every
9//! language the consumer compiles. Violated edges become
10//! [`StandardCompatViolation`] records on the [`crate::BuildGraph`];
11//! the CLI renders them as errors that fail the command. An edge
12//! whose consuming package declares `ignore-interface-standard =
13//! true` on the matching `[dependencies]` entry is still evaluated,
14//! but its violations are marked [`StandardCompatViolation::ignored`]
15//! so the CLI downgrades them to unchecked-edge notes.
16//!
17//! The pass runs strictly after resolution (fresh or
18//! lockfile-seeded - both produce the same loaded graph) and never
19//! feeds back into version selection. Its defaults deliberately
20//! differ from the build-time interface enforcement in
21//! `planner::enforce_interface_standards`: a compiled dependency
22//! with no interface declaration imposes nothing here (spec D9 row
23//! 4 - no implementation-standard fallback), while an explicit
24//! `"none"` is unsatisfiable (row 1).
25
26use std::collections::HashMap;
27use std::path::PathBuf;
28
29use cabin_core::standard_compatibility::{DependencyAttributes, ReqOfSource, Requirement};
30use cabin_core::{
31 LanguageStandardSettings, LanguageStandardSource, ResolvedLanguageStandards, SourceLanguage,
32 StandardDeclaration, Target, classify_source, effective_c, effective_cxx,
33};
34use cabin_workspace::PackageKind;
35use cabin_workspace::standards::{
36 DeclarationSite, DeclarationSites, Provenance, TargetEdge, TargetNode, effective_requirements,
37 provenance_c, provenance_cxx,
38};
39
40use crate::error::BuildError;
41use crate::planner::{PlanRequest, TargetDepEdge, TargetId, format_target_id, lookup_target};
42
43/// Which manifest table a cited declaration lives in. Together
44/// with [`DeclSite::field`] this is enough for a renderer to
45/// re-locate the declaration's span in the manifest text.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum DeclScope {
48 /// The `[package]` table.
49 Package,
50 /// The `[target.<name>]` table of the carried target.
51 Target(String),
52 /// The `[workspace]` table of the workspace root manifest
53 /// (the value was inherited via `{ workspace = true }`).
54 Workspace,
55}
56
57/// A manifest declaration a violation cites: file, table, and field.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct DeclSite {
60 /// Manifest that carries the declaration. The workspace root
61 /// manifest for [`DeclScope::Workspace`], the declaring
62 /// package's manifest otherwise.
63 pub manifest_path: PathBuf,
64 pub scope: DeclScope,
65 /// Manifest field name (`c-standard`, `interface-cxx-standard`, ...).
66 pub field: &'static str,
67}
68
69/// Why a dependency's effective requirement is what it is - the D9
70/// row that originates the join, with the declaration to cite.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum RequirementOrigin {
73 /// Row 2: an explicit `interface-*-standard` minimum.
74 Declared { site: DeclSite },
75 /// Row 1: `interface-*-standard = "none"` - consumption from
76 /// this language was disabled by the origin target's author.
77 DeclaredNone { site: DeclSite },
78 /// Row 3: a header-only target's minimum inferred from its
79 /// implementation standard.
80 HeaderOnlyInference { site: DeclSite },
81 /// Row 6: the strict C++-to-C default - the origin target
82 /// implements no C and declares no C interface, so there is no
83 /// declaration to cite.
84 CrossLanguageDefault,
85}
86
87/// The violated effective requirement `R_L(d)` of spec D13.
88/// `Unconstrained` cannot be violated, so it has no variant here.
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum EdgeRequirement {
91 /// A minimum consumer level (`[m]` of spec D3).
92 Min(&'static str),
93 /// Unsatisfiable at every consumer level.
94 Forbidden,
95}
96
97/// One resolved dependency edge that violates spec D13 for one
98/// consumer language. A mixed-language consumer failing both
99/// languages on the same edge yields two records.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct StandardCompatViolation {
102 /// `package:target` of the consuming side of the edge.
103 pub consumer: String,
104 /// The consumer language whose conjunct of D13 failed.
105 pub language: SourceLanguage,
106 /// The consumer's effective compile level for `language`.
107 pub consumer_standard: &'static str,
108 /// Where the consumer's standard is declared.
109 pub consumer_site: DeclSite,
110 /// `package:target` of the direct dependency the edge points at.
111 pub dependency: String,
112 /// The dependency's package name, for the pin remedy.
113 pub dependency_package: String,
114 /// The dependency's package version, for the pin remedy.
115 pub dependency_version: String,
116 /// Whether the dependency came from a registry - the pin
117 /// remedy only applies to versioned dependencies.
118 pub dependency_is_registry: bool,
119 /// The violated effective requirement `R_L(dependency)`.
120 pub requirement: EdgeRequirement,
121 /// `package:target` whose own `ReqOf` originates the
122 /// requirement (spec T1's closed form); equals `dependency`
123 /// when the dependency itself declares it.
124 pub origin_target: String,
125 /// The D9 row and declaration behind `origin_target`'s `ReqOf`.
126 pub origin: RequirementOrigin,
127 /// Public-edge provenance chain from `dependency` down to
128 /// `origin_target`, `package:target` names inclusive on both
129 /// ends; a single entry when the dependency's own declaration
130 /// attains the join.
131 pub chain: Vec<String>,
132 /// Manifest of the consuming target's own package - where a
133 /// per-edge `ignore-interface-standard = true` override would
134 /// be declared. Distinct from
135 /// [`StandardCompatViolation::consumer_site`], which points at
136 /// the workspace root manifest for inherited standards.
137 pub consumer_manifest_path: PathBuf,
138 /// Whether the consuming package opted this edge out of the
139 /// check with `ignore-interface-standard = true` on the
140 /// selected dependency entry. The CLI renders ignored
141 /// violations as unchecked-edge notes instead of errors.
142 pub ignored: bool,
143 /// Manifest table of the package-dependency entry that
144 /// resolved this edge - where a per-edge override lives or
145 /// would live: `[dependencies]`, `[dev-dependencies]`, or the
146 /// condition-qualified `[target.'cfg(...)'.<kind>]` form the
147 /// entry was declared under. `None` for intra-package edges,
148 /// which have no entry to carry one.
149 pub override_section: Option<String>,
150}
151
152/// Per-node declaration-site bookkeeping, parallel to the
153/// [`TargetNode`] slice: the [`DeclSite`] behind each populated D6
154/// attribute, for [`RequirementOrigin`] construction.
155struct NodeSites {
156 decl_c: Option<DeclSite>,
157 decl_cxx: Option<DeclSite>,
158 impl_c: Option<DeclSite>,
159 impl_cxx: Option<DeclSite>,
160}
161
162/// Evaluate spec D13 over every resolved dependency edge and return
163/// the violated (edge, language) pairs, sorted by consumer,
164/// dependency, then language for deterministic output.
165///
166/// # Errors
167/// Returns [`BuildError::UnknownTargetInPackage`] when an edge
168/// references a target its package does not declare - unreachable
169/// after the planner's own resolution walk, kept as an error for
170/// symmetry with the surrounding planner code.
171pub(crate) fn edge_violations(
172 topo: &[TargetId],
173 resolved_deps: &HashMap<TargetId, Vec<TargetDepEdge>>,
174 req: &PlanRequest<'_>,
175) -> Result<Vec<StandardCompatViolation>, BuildError> {
176 let index_of: HashMap<&TargetId, usize> = topo
177 .iter()
178 .enumerate()
179 .map(|(index, tid)| (tid, index))
180 .collect();
181
182 let mut nodes: Vec<TargetNode> = Vec::with_capacity(topo.len());
183 let mut sites: Vec<NodeSites> = Vec::with_capacity(topo.len());
184 for tid in topo {
185 let target = lookup_target(tid, req.graph)?;
186 let pkg = &req.graph.packages[tid.0];
187 let pkg_standards = req
188 .language_standards
189 .get(&tid.0)
190 .copied()
191 .unwrap_or_default();
192 let (attributes, node_sites) =
193 dependency_attributes(tid, target, pkg_standards, &pkg.package.language, req);
194 let deps = resolved_deps
195 .get(tid)
196 .map(|edges| {
197 edges
198 .iter()
199 .map(|edge| TargetEdge {
200 to: index_of[&edge.to],
201 public: edge.public,
202 })
203 .collect()
204 })
205 .unwrap_or_default();
206 nodes.push(TargetNode {
207 name: format_target_id(tid, req.graph),
208 manifest_path: pkg.manifest_path.clone(),
209 attributes,
210 sites: declaration_sites(&node_sites),
211 deps,
212 });
213 sites.push(node_sites);
214 }
215
216 let effective = effective_requirements(&nodes);
217
218 let mut violations = Vec::new();
219 for (consumer_index, tid) in topo.iter().enumerate() {
220 let consumer_target = lookup_target(tid, req.graph)?;
221 // A header-only consumer compiles no language: every edge
222 // out of it is compatible vacuously (spec D13); its
223 // requirements still propagated through `effective`.
224 if consumer_target.kind.is_header_only() {
225 continue;
226 }
227 let pkg_standards = req
228 .language_standards
229 .get(&tid.0)
230 .copied()
231 .unwrap_or_default();
232 let Some(edges) = resolved_deps.get(tid) else {
233 continue;
234 };
235 let compiles_c = has_sources_of(consumer_target, SourceLanguage::C);
236 let compiles_cxx = has_sources_of(consumer_target, SourceLanguage::Cxx);
237 // Which package-dependency edge resolved this target's
238 // deps - the planner's own lookup rule
239 // (`resolve_target_dep`): Normal-kind edges win, and
240 // Dev-kind edges participate only for dev-only target
241 // kinds (`test` / `example`) and only when no Normal edge
242 // to the package exists. The opt-out must sit on the
243 // entry resolution actually selected: a flag on an
244 // unselected (or invisible) edge suppresses nothing.
245 let dev_edges_visible = consumer_target.kind.is_dev_only();
246 let selected_pkg_edge = |dep_pkg: usize| -> Option<&cabin_workspace::DependencyEdge> {
247 let pkg_edges = &req.graph.packages[tid.0].deps;
248 pkg_edges
249 .iter()
250 .find(|pkg_edge| {
251 pkg_edge.kind == cabin_core::DependencyKind::Normal && pkg_edge.index == dep_pkg
252 })
253 .or_else(|| {
254 dev_edges_visible
255 .then(|| {
256 pkg_edges.iter().find(|pkg_edge| {
257 pkg_edge.kind == cabin_core::DependencyKind::Dev
258 && pkg_edge.index == dep_pkg
259 })
260 })
261 .flatten()
262 })
263 };
264 for edge in edges {
265 let dep_index = index_of[&edge.to];
266 // Per-edge opt-out: the consuming package's own
267 // dependency entry for the dependency's package
268 // carries `ignore-interface-standard = true`. The
269 // check still runs so the CLI can report the edge as
270 // unchecked; only cross-package edges are overridable
271 // (an intra-package edge has no `[dependencies]`
272 // entry to carry the flag). The selected entry's
273 // manifest section travels on the violation so the
274 // remedies name the table resolution actually read.
275 let selected = if tid.0 == edge.to.0 {
276 None
277 } else {
278 selected_pkg_edge(edge.to.0)
279 };
280 let edge_ignored = selected.is_some_and(|pkg_edge| pkg_edge.ignore_interface_standard);
281 let override_section = selected.map(section_of);
282 // D13's conjunction ranges over the languages the
283 // consumer compiles; an absent effective standard for a
284 // compiled language is a manifest error surfaced at the
285 // compile site, not here.
286 if compiles_c
287 && let Some(consumer) = effective_c(&pkg_standards, consumer_target)
288 && !effective[dep_index]
289 .c
290 .requirement
291 .satisfied_by(consumer.standard)
292 {
293 let provenance = provenance_c(&effective, dep_index);
294 if !interface_less_default_origin(&provenance, topo, req)? {
295 violations.push(violation(
296 SourceLanguage::C,
297 consumer.standard.as_str(),
298 consumer_site(consumer.source, "c-standard", consumer_index, &nodes, req),
299 requirement_of(effective[dep_index].c.requirement),
300 &provenance,
301 tid,
302 edge,
303 edge_ignored,
304 override_section.clone(),
305 &nodes,
306 &sites,
307 req,
308 ));
309 }
310 }
311 if compiles_cxx
312 && let Some(consumer) = effective_cxx(&pkg_standards, consumer_target)
313 && !effective[dep_index]
314 .cxx
315 .requirement
316 .satisfied_by(consumer.standard)
317 {
318 let provenance = provenance_cxx(&effective, dep_index);
319 if !interface_less_default_origin(&provenance, topo, req)? {
320 violations.push(violation(
321 SourceLanguage::Cxx,
322 consumer.standard.as_str(),
323 consumer_site(consumer.source, "cxx-standard", consumer_index, &nodes, req),
324 requirement_of(effective[dep_index].cxx.requirement),
325 &provenance,
326 tid,
327 edge,
328 edge_ignored,
329 override_section.clone(),
330 &nodes,
331 &sites,
332 req,
333 ));
334 }
335 }
336 }
337 }
338
339 // Topo iteration is already deterministic for a fixed graph;
340 // the explicit sort pins the reading order to something a user
341 // can predict regardless of the topo tie-breaks.
342 violations.sort_by(|a, b| {
343 (&a.consumer, &a.dependency, a.language.human_label()).cmp(&(
344 &b.consumer,
345 &b.dependency,
346 b.language.human_label(),
347 ))
348 });
349 Ok(violations)
350}
351
352/// The manifest table a package-dependency edge was declared
353/// under, exactly as a user would spell it: the plain kind table
354/// for unconditional entries, the condition-qualified
355/// `[target.'cfg(...)'.<kind>]` form otherwise. Remedies must
356/// name the declaring table - `Package::validate_dependencies`
357/// rejects a second same-kind entry for the same package, so
358/// pointing a conditional edge's user at the top-level table
359/// would send them into a rejected manifest.
360fn section_of(pkg_edge: &cabin_workspace::DependencyEdge) -> String {
361 let table = match pkg_edge.kind {
362 cabin_core::DependencyKind::Normal => "dependencies",
363 cabin_core::DependencyKind::Dev => "dev-dependencies",
364 };
365 match &pkg_edge.condition {
366 Some(condition) => format!("[target.'cfg({condition})'.{table}]"),
367 None => format!("[{table}]"),
368 }
369}
370
371/// Whether a violated requirement originates at an interface-less
372/// target's cross-language default and should be suppressed.
373///
374/// An executable-like target has no consumable interface: the
375/// manifest rejects `interface-*` fields on those kinds, and the
376/// planner's include-dir walk never takes headers from them. The
377/// strict C++-to-C default (spec D9 row 6) presumes dependency
378/// headers a C consumer could fail to compile, so a requirement the
379/// default originates *at* such a target warns about headers that
380/// do not exist. Requirements merely passing through it from a
381/// library behind it keep warning (their origin is the library),
382/// and the always-on build-time enforcement independently checks
383/// the transitive closure, so nothing real is lost.
384fn interface_less_default_origin(
385 provenance: &Provenance<'_>,
386 topo: &[TargetId],
387 req: &PlanRequest<'_>,
388) -> Result<bool, BuildError> {
389 // Rows 1-3 cannot originate at an executable-like target (the
390 // parser rejects its interface fields, the package-level
391 // default is gated to library-like kinds, and it is not
392 // header-only), so the cross-language default is the only
393 // origin to inspect.
394 if provenance.origin.source != ReqOfSource::CrossLanguageDefault {
395 return Ok(false);
396 }
397 let origin_tid = &topo[*provenance
398 .path
399 .last()
400 .expect("a provenance chain is never empty")];
401 let origin = lookup_target(origin_tid, req.graph)?;
402 Ok(!origin.kind.is_library_like())
403}
404
405/// Spec D6 attribute mapping for one target, with the declaration
406/// sites behind each populated attribute.
407///
408/// Population contract (D6): `impl_L` is `Some` exactly when the
409/// target itself implements `L` - a compiled target implements `L`
410/// when it has sources of `L` (level via target-over-package
411/// precedence), a header-only target only via a target-level
412/// implementation declaration. `decl_L` is the explicit interface
413/// declaration only (target over package tier, workspace-inherited
414/// counts as declared) - never the build-time implementation-
415/// standard fallback.
416fn dependency_attributes(
417 tid: &TargetId,
418 target: &Target,
419 pkg_standards: ResolvedLanguageStandards,
420 pkg_settings: &LanguageStandardSettings,
421 req: &PlanRequest<'_>,
422) -> (DependencyAttributes, NodeSites) {
423 // The D6 attribute mapping (population contract included) is shared
424 // with the published-index derivation so the two cannot drift; only
425 // the declaration-site bookkeeping for diagnostics is local here.
426 let attributes = cabin_core::standard_compatibility::dependency_attributes(
427 target,
428 &pkg_standards,
429 pkg_settings,
430 );
431 let header_only = target.kind.is_header_only();
432
433 let pkg = &req.graph.packages[tid.0];
434 let target_name = target.name.as_str();
435
436 let node_sites = NodeSites {
437 decl_c: attributes.decl_c.is_some().then(|| {
438 interface_decl_site(
439 "interface-c-standard",
440 target.language.interface_c_standard.is_some(),
441 matches!(
442 pkg_settings.interface_c_standard,
443 Some(StandardDeclaration::Inherited(_))
444 ),
445 target_name,
446 pkg,
447 req,
448 )
449 }),
450 decl_cxx: attributes.decl_cxx.is_some().then(|| {
451 interface_decl_site(
452 "interface-cxx-standard",
453 target.language.interface_cxx_standard.is_some(),
454 matches!(
455 pkg_settings.interface_cxx_standard,
456 Some(StandardDeclaration::Inherited(_))
457 ),
458 target_name,
459 pkg,
460 req,
461 )
462 }),
463 // Header-only inference (D9 row 3) cites the target-level
464 // implementation declaration the inference read; a compiled
465 // target's implementation standard is never cited (row 4
466 // imposes nothing).
467 impl_c: (header_only && attributes.impl_c.is_some()).then(|| DeclSite {
468 manifest_path: pkg.manifest_path.clone(),
469 scope: DeclScope::Target(target_name.to_owned()),
470 field: "c-standard",
471 }),
472 impl_cxx: (header_only && attributes.impl_cxx.is_some()).then(|| DeclSite {
473 manifest_path: pkg.manifest_path.clone(),
474 scope: DeclScope::Target(target_name.to_owned()),
475 field: "cxx-standard",
476 }),
477 };
478
479 (attributes, node_sites)
480}
481
482/// The [`DeclSite`] of a populated interface declaration: the
483/// target-level field when present, otherwise the package-level
484/// field (which points at the workspace root when inherited).
485fn interface_decl_site(
486 field: &'static str,
487 target_declares: bool,
488 pkg_inherited: bool,
489 target_name: &str,
490 pkg: &cabin_workspace::WorkspacePackage,
491 req: &PlanRequest<'_>,
492) -> DeclSite {
493 if target_declares {
494 DeclSite {
495 manifest_path: pkg.manifest_path.clone(),
496 scope: DeclScope::Target(target_name.to_owned()),
497 field,
498 }
499 } else if pkg_inherited {
500 DeclSite {
501 manifest_path: req.graph.root_manifest_path.clone(),
502 scope: DeclScope::Workspace,
503 field,
504 }
505 } else {
506 DeclSite {
507 manifest_path: pkg.manifest_path.clone(),
508 scope: DeclScope::Package,
509 field,
510 }
511 }
512}
513
514/// Project [`NodeSites`] into the spanless [`DeclarationSites`] the
515/// effective-requirement composition records provenance with.
516fn declaration_sites(sites: &NodeSites) -> DeclarationSites {
517 let site = |decl: &Option<DeclSite>| {
518 decl.as_ref().map(|s| DeclarationSite {
519 manifest_path: s.manifest_path.clone(),
520 span: None,
521 })
522 };
523 DeclarationSites {
524 decl_c: site(&sites.decl_c),
525 decl_cxx: site(&sites.decl_cxx),
526 impl_c: site(&sites.impl_c),
527 impl_cxx: site(&sites.impl_cxx),
528 }
529}
530
531/// The consumer-side [`DeclSite`] for the violated language's
532/// effective compile standard.
533fn consumer_site(
534 source: LanguageStandardSource,
535 field: &'static str,
536 consumer_index: usize,
537 nodes: &[TargetNode],
538 req: &PlanRequest<'_>,
539) -> DeclSite {
540 let manifest_path = nodes[consumer_index].manifest_path.clone();
541 match source {
542 LanguageStandardSource::Target => DeclSite {
543 manifest_path,
544 scope: DeclScope::Target(target_name_of(&nodes[consumer_index].name)),
545 field,
546 },
547 LanguageStandardSource::Package => DeclSite {
548 manifest_path,
549 scope: DeclScope::Package,
550 field,
551 },
552 LanguageStandardSource::Workspace => DeclSite {
553 manifest_path: req.graph.root_manifest_path.clone(),
554 scope: DeclScope::Workspace,
555 field,
556 },
557 }
558}
559
560/// The `target` half of a `package:target` display name.
561fn target_name_of(display: &str) -> String {
562 display
563 .split_once(':')
564 .map_or(display, |(_, target)| target)
565 .to_owned()
566}
567
568fn requirement_of<S: Copy + Ord + AsStandardStr>(requirement: Requirement<S>) -> EdgeRequirement {
569 match requirement {
570 Requirement::Min(min) => EdgeRequirement::Min(min.standard_str()),
571 Requirement::Forbidden => EdgeRequirement::Forbidden,
572 // A satisfied requirement never reaches violation
573 // construction: `unconstrained` is satisfied at every level
574 // (spec D11).
575 Requirement::Unconstrained => {
576 unreachable!("an unconstrained requirement cannot be violated (spec D11)")
577 }
578 }
579}
580
581/// `as_str` unification for the two level chains, local to this
582/// module so `requirement_of` can stay generic like the spec's `L`.
583trait AsStandardStr {
584 fn standard_str(self) -> &'static str;
585}
586impl AsStandardStr for cabin_core::CStandard {
587 fn standard_str(self) -> &'static str {
588 self.as_str()
589 }
590}
591impl AsStandardStr for cabin_core::CxxStandard {
592 fn standard_str(self) -> &'static str {
593 self.as_str()
594 }
595}
596
597#[allow(clippy::too_many_arguments)]
598fn violation(
599 language: SourceLanguage,
600 consumer_standard: &'static str,
601 consumer_site: DeclSite,
602 requirement: EdgeRequirement,
603 provenance: &Provenance<'_>,
604 consumer_tid: &TargetId,
605 edge: &TargetDepEdge,
606 ignored: bool,
607 override_section: Option<String>,
608 nodes: &[TargetNode],
609 sites: &[NodeSites],
610 req: &PlanRequest<'_>,
611) -> StandardCompatViolation {
612 let origin_index = *provenance
613 .path
614 .last()
615 .expect("a provenance chain is never empty");
616 let origin_sites = &sites[origin_index];
617 let origin = match provenance.origin.source {
618 ReqOfSource::Declared => RequirementOrigin::Declared {
619 site: decl_site_for(origin_sites, language),
620 },
621 ReqOfSource::DeclaredNone => RequirementOrigin::DeclaredNone {
622 site: decl_site_for(origin_sites, language),
623 },
624 ReqOfSource::HeaderOnlyInference => RequirementOrigin::HeaderOnlyInference {
625 site: impl_site_for(origin_sites, language),
626 },
627 ReqOfSource::CrossLanguageDefault => RequirementOrigin::CrossLanguageDefault,
628 // Row 4 yields `unconstrained`, which satisfies every
629 // consumer (spec D11): it can never originate a violated
630 // join.
631 ReqOfSource::CompiledNoDeclaration => unreachable!(
632 "a compiled target without a declaration imposes no constraint (spec D9 row 4)"
633 ),
634 };
635 let dep_pkg = &req.graph.packages[edge.to.0];
636 StandardCompatViolation {
637 consumer: format_target_id(consumer_tid, req.graph),
638 language,
639 consumer_standard,
640 consumer_site,
641 dependency: nodes[*provenance
642 .path
643 .first()
644 .expect("a provenance chain is never empty")]
645 .name
646 .clone(),
647 dependency_package: dep_pkg.package.name.as_str().to_owned(),
648 dependency_version: dep_pkg.package.version.to_string(),
649 dependency_is_registry: matches!(dep_pkg.kind, PackageKind::Registry),
650 requirement,
651 origin_target: nodes[origin_index].name.clone(),
652 origin,
653 chain: provenance
654 .path
655 .iter()
656 .map(|&index| nodes[index].name.clone())
657 .collect(),
658 consumer_manifest_path: req.graph.packages[consumer_tid.0].manifest_path.clone(),
659 ignored,
660 override_section,
661 }
662}
663
664/// The interface-declaration site of the origin target for
665/// `language`. Present whenever the origin's `ReqOf` came from D9
666/// rows 1-2, which is exactly when this is called.
667fn decl_site_for(sites: &NodeSites, language: SourceLanguage) -> DeclSite {
668 match language {
669 SourceLanguage::C => sites.decl_c.clone(),
670 SourceLanguage::Cxx => sites.decl_cxx.clone(),
671 }
672 .expect("a declared requirement records its declaration site")
673}
674
675/// The implementation-declaration site anchoring header-only
676/// inference (D9 row 3).
677fn impl_site_for(sites: &NodeSites, language: SourceLanguage) -> DeclSite {
678 match language {
679 SourceLanguage::C => sites.impl_c.clone(),
680 SourceLanguage::Cxx => sites.impl_cxx.clone(),
681 }
682 .expect("header-only inference records its implementation site")
683}
684
685fn has_sources_of(target: &Target, language: SourceLanguage) -> bool {
686 target
687 .sources
688 .iter()
689 .any(|source| classify_source(source) == Some(language))
690}