Skip to main content

cabin_core/
language_standard.rs

1//! Typed C/C++ language standards.
2//!
3//! Owns the standard enums, the manifest declaration shape shared by
4//! `[package]` and `[target.<name>]`, effective-standard resolution
5//! (target ▶ package ▶ built-in default), interface-requirement
6//! relevance and fallback, the escape-hatch conflict detector, and
7//! the per-package summary that feeds `BuildConfiguration`
8//! fingerprinting and the metadata view. Pure data and logic only;
9//! no I/O. See `docs/language-standards.md` for the user-facing
10//! contract.
11
12use std::collections::BTreeMap;
13
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16
17use crate::{ResolvedProfileFlags, SourceLanguage, Target, classify_source};
18
19/// C language standards Cabin can request, oldest to newest. The
20/// `Ord` derive follows declaration order, which the interface
21/// compatibility check relies on.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
23pub enum CStandard {
24    #[serde(rename = "c89")]
25    C89,
26    #[serde(rename = "c99")]
27    C99,
28    #[serde(rename = "c11")]
29    C11,
30    #[serde(rename = "c17")]
31    C17,
32    #[serde(rename = "c23")]
33    C23,
34}
35
36impl CStandard {
37    pub const ALL: [Self; 5] = [Self::C89, Self::C99, Self::C11, Self::C17, Self::C23];
38
39    pub const fn as_str(self) -> &'static str {
40        match self {
41            Self::C89 => "c89",
42            Self::C99 => "c99",
43            Self::C11 => "c11",
44            Self::C17 => "c17",
45            Self::C23 => "c23",
46        }
47    }
48
49    /// # Errors
50    /// Returns [`LanguageStandardParseError`] listing the valid
51    /// spellings when `value` is not a recognized C standard.
52    pub fn parse(value: &str) -> Result<Self, LanguageStandardParseError> {
53        Self::ALL
54            .into_iter()
55            .find(|s| s.as_str() == value)
56            .ok_or_else(|| LanguageStandardParseError {
57                language: SourceLanguage::C,
58                value: value.to_owned(),
59            })
60    }
61}
62
63impl std::fmt::Display for CStandard {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.write_str(self.as_str())
66    }
67}
68
69impl std::str::FromStr for CStandard {
70    type Err = LanguageStandardParseError;
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        Self::parse(s)
73    }
74}
75
76/// C++ language standards Cabin can request, oldest to newest.
77/// `c++26` is deferred until its capability thresholds are audited.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
79pub enum CxxStandard {
80    #[serde(rename = "c++98")]
81    Cxx98,
82    #[serde(rename = "c++03")]
83    Cxx03,
84    #[serde(rename = "c++11")]
85    Cxx11,
86    #[serde(rename = "c++14")]
87    Cxx14,
88    #[serde(rename = "c++17")]
89    Cxx17,
90    #[serde(rename = "c++20")]
91    Cxx20,
92    #[serde(rename = "c++23")]
93    Cxx23,
94}
95
96impl CxxStandard {
97    pub const ALL: [Self; 7] = [
98        Self::Cxx98,
99        Self::Cxx03,
100        Self::Cxx11,
101        Self::Cxx14,
102        Self::Cxx17,
103        Self::Cxx20,
104        Self::Cxx23,
105    ];
106
107    pub const fn as_str(self) -> &'static str {
108        match self {
109            Self::Cxx98 => "c++98",
110            Self::Cxx03 => "c++03",
111            Self::Cxx11 => "c++11",
112            Self::Cxx14 => "c++14",
113            Self::Cxx17 => "c++17",
114            Self::Cxx20 => "c++20",
115            Self::Cxx23 => "c++23",
116        }
117    }
118
119    /// # Errors
120    /// Returns [`LanguageStandardParseError`] listing the valid
121    /// spellings when `value` is not a recognized C++ standard.
122    pub fn parse(value: &str) -> Result<Self, LanguageStandardParseError> {
123        Self::ALL
124            .into_iter()
125            .find(|s| s.as_str() == value)
126            .ok_or_else(|| LanguageStandardParseError {
127                language: SourceLanguage::Cxx,
128                value: value.to_owned(),
129            })
130    }
131}
132
133impl std::fmt::Display for CxxStandard {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        f.write_str(self.as_str())
136    }
137}
138
139impl std::str::FromStr for CxxStandard {
140    type Err = LanguageStandardParseError;
141    fn from_str(s: &str) -> Result<Self, Self::Err> {
142        Self::parse(s)
143    }
144}
145
146/// Built-in defaults: what every package compiles with when no
147/// standard is declared anywhere. Changing either constant changes
148/// every undeclared project's compile commands.
149pub const DEFAULT_C_STANDARD: CStandard = CStandard::C11;
150pub const DEFAULT_CXX_STANDARD: CxxStandard = CxxStandard::Cxx17;
151
152/// An invalid manifest standard value, with the valid spellings for
153/// the diagnostic.
154#[derive(Debug, Error, Clone, PartialEq, Eq)]
155#[error(
156    "unknown {} standard `{value}`: expected one of {}",
157    .language.human_label(),
158    valid_standard_values(*.language)
159)]
160pub struct LanguageStandardParseError {
161    pub language: SourceLanguage,
162    pub value: String,
163}
164
165fn valid_standard_values(language: SourceLanguage) -> String {
166    match language {
167        SourceLanguage::C => CStandard::ALL.map(CStandard::as_str).join(", "),
168        SourceLanguage::Cxx => CxxStandard::ALL.map(CxxStandard::as_str).join(", "),
169    }
170}
171
172/// One per-compile standard value, carried by the build IR. Encodes
173/// the source language, so the dialect lowering derives both the
174/// rule kind and the standard flag from this single field.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
176pub enum LanguageStandard {
177    C(CStandard),
178    Cxx(CxxStandard),
179}
180
181impl LanguageStandard {
182    pub const fn language(self) -> SourceLanguage {
183        match self {
184            Self::C(_) => SourceLanguage::C,
185            Self::Cxx(_) => SourceLanguage::Cxx,
186        }
187    }
188
189    pub const fn as_str(self) -> &'static str {
190        match self {
191            Self::C(s) => s.as_str(),
192            Self::Cxx(s) => s.as_str(),
193        }
194    }
195
196    /// The `/std:` value `cl.exe` accepts for this standard, when a
197    /// stable one exists. `None` marks the MSVC-dialect gaps
198    /// (C89/C99/C23, C++98/03/11/23); the planner rejects those
199    /// before lowering on the MSVC dialect.
200    pub const fn msvc_spelling(self) -> Option<&'static str> {
201        match self {
202            Self::C(CStandard::C11) => Some("/std:c11"),
203            Self::C(CStandard::C17) => Some("/std:c17"),
204            Self::Cxx(CxxStandard::Cxx14) => Some("/std:c++14"),
205            Self::Cxx(CxxStandard::Cxx17) => Some("/std:c++17"),
206            Self::Cxx(CxxStandard::Cxx20) => Some("/std:c++20"),
207            _ => None,
208        }
209    }
210}
211
212impl std::fmt::Display for LanguageStandard {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        f.write_str(self.as_str())
215    }
216}
217
218/// One declared standard-field value as it travels from the
219/// manifest to the resolved package model. Mirrors the
220/// `DependencySource::Workspace` contract: `cabin-manifest`
221/// constructs `Declared` (literal) or `Workspace` (the
222/// `{ workspace = true }` opt-in marker), `cabin-workspace`
223/// rewrites every marker into `Inherited(value)` before any
224/// consumer sees the `Package`, and a marker that survives past
225/// the loader is a workspace invariant violation. Marker
226/// semantics deliberately split by consumer: `.is_some()`-based
227/// relevance checks (`imposes_requirement`,
228/// `find_standard_flag_conflicts`, `is_empty`) count an
229/// unresolved marker as a declaration, while the `*_value()`
230/// accessors treat it as absent — both cases are unreachable
231/// post-loader under the rewrite invariant.
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub enum StandardDeclaration<S> {
234    /// Literal value written in this manifest → source `package`
235    /// (or `target` for a target-level field).
236    Declared(S),
237    /// Unresolved `{ workspace = true }` opt-in marker.
238    Workspace,
239    /// Value resolved from the workspace root's `[workspace]`
240    /// declaration → source `workspace`.
241    Inherited(S),
242}
243
244impl<S> StandardDeclaration<S> {
245    /// The resolved standard value. `None` only for an unresolved
246    /// marker, which must not reach consumers (debug-asserted).
247    #[must_use]
248    pub fn value(self) -> Option<S> {
249        match self {
250            Self::Declared(s) | Self::Inherited(s) => Some(s),
251            Self::Workspace => {
252                debug_assert!(
253                    false,
254                    "unresolved `{{ workspace = true }}` standard marker reached a consumer"
255                );
256                None
257            }
258        }
259    }
260}
261
262// `Declared` and `Inherited` serialize as the bare standard string
263// so the canonical-metadata / index wire format is identical to a
264// literal declaration (publish bakes inherited values). An
265// unresolved marker must never reach a serialization boundary.
266impl<S: Serialize> Serialize for StandardDeclaration<S> {
267    fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
268    where
269        Ser: serde::Serializer,
270    {
271        match self {
272            Self::Declared(s) | Self::Inherited(s) => s.serialize(serializer),
273            Self::Workspace => Err(serde::ser::Error::custom(
274                "unresolved `{ workspace = true }` standard marker cannot be serialized",
275            )),
276        }
277    }
278}
279
280// A bare string deserializes as `Declared`: a consumer re-parsing
281// published metadata sees a plain declaration.
282impl<'de, S: Deserialize<'de>> Deserialize<'de> for StandardDeclaration<S> {
283    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
284    where
285        D: serde::Deserializer<'de>,
286    {
287        S::deserialize(deserializer).map(Self::Declared)
288    }
289}
290
291/// The four manifest fields, shared by `[package]` and
292/// `[target.<name>]` (`c-standard` / `cxx-standard` /
293/// `interface-c-standard` / `interface-cxx-standard`). At
294/// `[package]` level each field may also be the
295/// `{ workspace = true }` opt-in marker; target-level fields are
296/// always `Declared` (the parser rejects markers there).
297#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
298pub struct LanguageStandardSettings {
299    #[serde(default, skip_serializing_if = "Option::is_none")]
300    pub c_standard: Option<StandardDeclaration<CStandard>>,
301    #[serde(default, skip_serializing_if = "Option::is_none")]
302    pub cxx_standard: Option<StandardDeclaration<CxxStandard>>,
303    #[serde(default, skip_serializing_if = "Option::is_none")]
304    pub interface_c_standard: Option<StandardDeclaration<CStandard>>,
305    #[serde(default, skip_serializing_if = "Option::is_none")]
306    pub interface_cxx_standard: Option<StandardDeclaration<CxxStandard>>,
307}
308
309impl LanguageStandardSettings {
310    #[must_use]
311    pub fn is_empty(&self) -> bool {
312        self.c_standard.is_none()
313            && self.cxx_standard.is_none()
314            && self.interface_c_standard.is_none()
315            && self.interface_cxx_standard.is_none()
316    }
317
318    /// Resolved C implementation standard, when declared or
319    /// inherited.
320    #[must_use]
321    pub fn c_standard_value(&self) -> Option<CStandard> {
322        self.c_standard.and_then(StandardDeclaration::value)
323    }
324
325    /// Resolved C++ implementation standard, when declared or
326    /// inherited.
327    #[must_use]
328    pub fn cxx_standard_value(&self) -> Option<CxxStandard> {
329        self.cxx_standard.and_then(StandardDeclaration::value)
330    }
331
332    /// Resolved C interface standard, when declared or inherited.
333    #[must_use]
334    pub fn interface_c_standard_value(&self) -> Option<CStandard> {
335        self.interface_c_standard
336            .and_then(StandardDeclaration::value)
337    }
338
339    /// Resolved C++ interface standard, when declared or
340    /// inherited.
341    #[must_use]
342    pub fn interface_cxx_standard_value(&self) -> Option<CxxStandard> {
343        self.interface_cxx_standard
344            .and_then(StandardDeclaration::value)
345    }
346
347    /// First field carrying the unresolved `{ workspace = true }`
348    /// marker, for error reporting.
349    #[must_use]
350    pub fn workspace_marker_field(&self) -> Option<&'static str> {
351        if self.c_standard == Some(StandardDeclaration::Workspace) {
352            return Some("c-standard");
353        }
354        if self.cxx_standard == Some(StandardDeclaration::Workspace) {
355            return Some("cxx-standard");
356        }
357        if self.interface_c_standard == Some(StandardDeclaration::Workspace) {
358            return Some("interface-c-standard");
359        }
360        if self.interface_cxx_standard == Some(StandardDeclaration::Workspace) {
361            return Some("interface-cxx-standard");
362        }
363        None
364    }
365}
366
367/// Literal `[workspace]`-level standard default values that member
368/// packages opt into per field with `<field> = { workspace = true }`
369/// on `[package]`. Plain values only — the opt-in marker is not
370/// accepted on the `[workspace]` table itself.
371#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
372pub struct WorkspaceStandardDefaults {
373    #[serde(rename = "c-standard", skip_serializing_if = "Option::is_none")]
374    pub c_standard: Option<CStandard>,
375    #[serde(rename = "cxx-standard", skip_serializing_if = "Option::is_none")]
376    pub cxx_standard: Option<CxxStandard>,
377    #[serde(
378        rename = "interface-c-standard",
379        skip_serializing_if = "Option::is_none"
380    )]
381    pub interface_c_standard: Option<CStandard>,
382    #[serde(
383        rename = "interface-cxx-standard",
384        skip_serializing_if = "Option::is_none"
385    )]
386    pub interface_cxx_standard: Option<CxxStandard>,
387}
388
389impl WorkspaceStandardDefaults {
390    #[must_use]
391    pub fn is_empty(&self) -> bool {
392        self.c_standard.is_none()
393            && self.cxx_standard.is_none()
394            && self.interface_c_standard.is_none()
395            && self.interface_cxx_standard.is_none()
396    }
397}
398
399/// Provenance of an effective implementation standard.
400#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
401#[serde(rename_all = "kebab-case")]
402pub enum LanguageStandardSource {
403    BuiltinDefault,
404    Package,
405    Target,
406    Workspace,
407}
408
409impl LanguageStandardSource {
410    pub const fn as_key(self) -> &'static str {
411        match self {
412            Self::BuiltinDefault => "builtin-default",
413            Self::Package => "package",
414            Self::Target => "target",
415            Self::Workspace => "workspace",
416        }
417    }
418}
419
420/// Provenance of an effective interface standard.
421/// `CompileStandard` marks the documented default: no interface
422/// field was declared, so the requirement equals the target's
423/// effective implementation standard.
424#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
425#[serde(rename_all = "kebab-case")]
426pub enum InterfaceStandardSource {
427    Target,
428    Package,
429    CompileStandard,
430    Workspace,
431}
432
433impl InterfaceStandardSource {
434    pub const fn as_key(self) -> &'static str {
435        match self {
436            Self::Target => "target",
437            Self::Package => "package",
438            Self::CompileStandard => "compile-standard",
439            Self::Workspace => "workspace",
440        }
441    }
442}
443
444/// A resolved implementation standard plus where it came from.
445#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
446pub struct ResolvedStandard<S> {
447    pub standard: S,
448    pub source: LanguageStandardSource,
449}
450
451/// A resolved interface standard plus where it came from.
452#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
453pub struct InterfaceStandard<S> {
454    pub standard: S,
455    pub source: InterfaceStandardSource,
456}
457
458/// Package-level effective implementation standards.
459#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
460pub struct ResolvedLanguageStandards {
461    pub c: ResolvedStandard<CStandard>,
462    pub cxx: ResolvedStandard<CxxStandard>,
463}
464
465impl Default for ResolvedLanguageStandards {
466    fn default() -> Self {
467        resolve_language_standards(&LanguageStandardSettings::default())
468    }
469}
470
471/// Map a package-level declaration to its resolved standard and
472/// provenance: literal → `package`, workspace-inherited →
473/// `workspace`, absent (or an unresolved marker, debug-asserted
474/// here) → built-in default.
475fn package_resolution<S: Copy>(
476    declaration: Option<StandardDeclaration<S>>,
477    default: S,
478) -> ResolvedStandard<S> {
479    match declaration {
480        Some(StandardDeclaration::Declared(standard)) => ResolvedStandard {
481            standard,
482            source: LanguageStandardSource::Package,
483        },
484        Some(StandardDeclaration::Inherited(standard)) => ResolvedStandard {
485            standard,
486            source: LanguageStandardSource::Workspace,
487        },
488        Some(StandardDeclaration::Workspace) => {
489            debug_assert!(
490                false,
491                "unresolved `{{ workspace = true }}` standard marker reached resolution"
492            );
493            ResolvedStandard {
494                standard: default,
495                source: LanguageStandardSource::BuiltinDefault,
496            }
497        }
498        None => ResolvedStandard {
499            standard: default,
500            source: LanguageStandardSource::BuiltinDefault,
501        },
502    }
503}
504
505/// Resolve the package-level effective standards:
506/// `[package]` declaration (literal or workspace-inherited) ▶
507/// built-in default.
508#[must_use]
509pub fn resolve_language_standards(package: &LanguageStandardSettings) -> ResolvedLanguageStandards {
510    ResolvedLanguageStandards {
511        c: package_resolution(package.c_standard, DEFAULT_C_STANDARD),
512        cxx: package_resolution(package.cxx_standard, DEFAULT_CXX_STANDARD),
513    }
514}
515
516/// Effective C implementation standard for one target:
517/// target override ▶ package (literal or workspace-inherited) ▶
518/// built-in default.
519#[must_use]
520pub fn effective_c(
521    package: &ResolvedLanguageStandards,
522    target: &Target,
523) -> ResolvedStandard<CStandard> {
524    target
525        .language
526        .c_standard_value()
527        .map_or(package.c, |standard| ResolvedStandard {
528            standard,
529            source: LanguageStandardSource::Target,
530        })
531}
532
533/// Effective C++ implementation standard for one target.
534#[must_use]
535pub fn effective_cxx(
536    package: &ResolvedLanguageStandards,
537    target: &Target,
538) -> ResolvedStandard<CxxStandard> {
539    target
540        .language
541        .cxx_standard_value()
542        .map_or(package.cxx, |standard| ResolvedStandard {
543            standard,
544            source: LanguageStandardSource::Target,
545        })
546}
547
548/// Map a package-level *interface* declaration to its provenance:
549/// literal → `package`, workspace-inherited → `workspace`.
550fn interface_resolution<S: Copy>(
551    declaration: Option<StandardDeclaration<S>>,
552) -> Option<InterfaceStandard<S>> {
553    match declaration {
554        Some(StandardDeclaration::Declared(standard)) => Some(InterfaceStandard {
555            standard,
556            source: InterfaceStandardSource::Package,
557        }),
558        Some(StandardDeclaration::Inherited(standard)) => Some(InterfaceStandard {
559            standard,
560            source: InterfaceStandardSource::Workspace,
561        }),
562        Some(StandardDeclaration::Workspace) => {
563            debug_assert!(
564                false,
565                "unresolved `{{ workspace = true }}` standard marker reached resolution"
566            );
567            None
568        }
569        None => None,
570    }
571}
572
573/// Effective C interface standard for a library-like target:
574/// target interface ▶ package interface (literal or
575/// workspace-inherited) ▶ the target's effective implementation
576/// standard (literal defaulting — built-in default included).
577#[must_use]
578pub fn interface_c(
579    package: &ResolvedLanguageStandards,
580    package_settings: &LanguageStandardSettings,
581    target: &Target,
582) -> InterfaceStandard<CStandard> {
583    if let Some(standard) = target.language.interface_c_standard_value() {
584        return InterfaceStandard {
585            standard,
586            source: InterfaceStandardSource::Target,
587        };
588    }
589    if let Some(interface) = interface_resolution(package_settings.interface_c_standard) {
590        return interface;
591    }
592    InterfaceStandard {
593        standard: effective_c(package, target).standard,
594        source: InterfaceStandardSource::CompileStandard,
595    }
596}
597
598/// Effective C++ interface standard for a library-like target.
599#[must_use]
600pub fn interface_cxx(
601    package: &ResolvedLanguageStandards,
602    package_settings: &LanguageStandardSettings,
603    target: &Target,
604) -> InterfaceStandard<CxxStandard> {
605    if let Some(standard) = target.language.interface_cxx_standard_value() {
606        return InterfaceStandard {
607            standard,
608            source: InterfaceStandardSource::Target,
609        };
610    }
611    if let Some(interface) = interface_resolution(package_settings.interface_cxx_standard) {
612        return interface;
613    }
614    InterfaceStandard {
615        standard: effective_cxx(package, target).standard,
616        source: InterfaceStandardSource::CompileStandard,
617    }
618}
619
620/// Whether a dependency target imposes an interface requirement for
621/// `language` on its consumers. A language is relevant when the
622/// target has sources of that language, declares a target-level
623/// field for it (implementation or interface), or is header-only
624/// while the package declares a package-level *interface* standard
625/// for it. Package-level implementation defaults never create
626/// relevance by themselves.
627#[must_use]
628pub fn imposes_requirement(
629    target: &Target,
630    package_settings: &LanguageStandardSettings,
631    language: SourceLanguage,
632) -> bool {
633    let has_sources = target
634        .sources
635        .iter()
636        .any(|s| classify_source(s) == Some(language));
637    let target_declares = match language {
638        SourceLanguage::C => {
639            target.language.c_standard.is_some() || target.language.interface_c_standard.is_some()
640        }
641        SourceLanguage::Cxx => {
642            target.language.cxx_standard.is_some()
643                || target.language.interface_cxx_standard.is_some()
644        }
645    };
646    let header_only_package_interface = target.kind.is_header_only()
647        && match language {
648            SourceLanguage::C => package_settings.interface_c_standard.is_some(),
649            SourceLanguage::Cxx => package_settings.interface_cxx_standard.is_some(),
650        };
651    has_sources || target_declares || header_only_package_interface
652}
653
654/// Token prefixes that select a language standard inside an
655/// escape-hatch flag list.
656pub const STANDARD_FLAG_PREFIXES: [&str; 3] = ["-std=", "--std=", "/std:"];
657
658/// A first-class standard declaration conflicting with an explicit
659/// standard flag in the same package's manifest-derived flags.
660#[derive(Debug, Error, Clone, PartialEq, Eq)]
661#[error(
662    "package `{package}` declares a first-class {} standard (`{field}`) but its `{flag_list}` also select one via `{flag}`; remove the flag, or drop the `{field}` declaration and keep the raw flag",
663    .language.human_label()
664)]
665pub struct StandardFlagConflict {
666    pub package: String,
667    pub language: SourceLanguage,
668    /// The manifest field family that was declared (`c-standard` or
669    /// `cxx-standard`, at package or target level).
670    pub field: &'static str,
671    /// The flag list carrying the conflicting token (`cflags` or
672    /// `cxxflags`).
673    pub flag_list: &'static str,
674    pub flag: String,
675    /// Scope of the conflicting declaration: `Some(target)` when a
676    /// target-level field created it (the ambiguity exists only on
677    /// that target's compiles), `None` when the package-level field
678    /// did (every compile of the language is ambiguous). The build
679    /// planner uses the scope to surface a conflict only when a
680    /// matching compile is actually planned.
681    pub target: Option<String>,
682}
683
684fn first_standard_token(flags: &[String]) -> Option<String> {
685    flags
686        .iter()
687        .find(|f| STANDARD_FLAG_PREFIXES.iter().any(|p| f.starts_with(p)))
688        .cloned()
689}
690
691/// Detect the documented conflict candidates: an explicit
692/// first-class implementation standard declaration (package or
693/// target level) for a language whose manifest-derived flag list
694/// also pins a standard. Runs on resolved flags *before* env /
695/// pkg-config augmentation so `CFLAGS` / `CXXFLAGS` remain exempt.
696///
697/// These are *candidates*, scoped per declaration: the build
698/// planner surfaces one only when a compile its scope covers is
699/// actually planned, so an unbuilt sibling target's declaration
700/// never gates a command that does not compile it.
701#[must_use]
702pub fn find_standard_flag_conflicts(
703    package: &str,
704    settings: &LanguageStandardSettings,
705    targets: &[Target],
706    flags: &ResolvedProfileFlags,
707) -> Vec<StandardFlagConflict> {
708    let mut out = Vec::new();
709    let mut push = |language: SourceLanguage,
710                    field: &'static str,
711                    flag_list: &'static str,
712                    flag: String,
713                    target: Option<String>| {
714        out.push(StandardFlagConflict {
715            package: package.to_owned(),
716            language,
717            field,
718            flag_list,
719            flag,
720            target,
721        });
722    };
723    if let Some(flag) = first_standard_token(&flags.cflags) {
724        if settings.c_standard.is_some() {
725            push(
726                SourceLanguage::C,
727                "c-standard",
728                "cflags",
729                flag.clone(),
730                None,
731            );
732        } else {
733            for target in targets {
734                if target.language.c_standard.is_some() {
735                    push(
736                        SourceLanguage::C,
737                        "c-standard",
738                        "cflags",
739                        flag.clone(),
740                        Some(target.name.as_str().to_owned()),
741                    );
742                }
743            }
744        }
745    }
746    if let Some(flag) = first_standard_token(&flags.cxxflags) {
747        if settings.cxx_standard.is_some() {
748            push(
749                SourceLanguage::Cxx,
750                "cxx-standard",
751                "cxxflags",
752                flag.clone(),
753                None,
754            );
755        } else {
756            for target in targets {
757                if target.language.cxx_standard.is_some() {
758                    push(
759                        SourceLanguage::Cxx,
760                        "cxx-standard",
761                        "cxxflags",
762                        flag.clone(),
763                        Some(target.name.as_str().to_owned()),
764                    );
765                }
766            }
767        }
768    }
769    out
770}
771
772/// Per-package language-standard summary carried by
773/// `BuildConfiguration`: package-level effective standards plus the
774/// effective values for every target. Values (not provenance) feed
775/// the fingerprint; the whole struct feeds `cabin metadata` /
776/// `cabin explain build-config`.
777#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
778pub struct LanguageStandardsSummary {
779    pub c: ResolvedStandard<CStandard>,
780    pub cxx: ResolvedStandard<CxxStandard>,
781    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
782    pub targets: BTreeMap<String, TargetStandardsSummary>,
783}
784
785/// Effective standards for one target. Interface entries are
786/// present only for `library` / `header-only` kinds.
787#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
788pub struct TargetStandardsSummary {
789    pub c: ResolvedStandard<CStandard>,
790    pub cxx: ResolvedStandard<CxxStandard>,
791    #[serde(default, skip_serializing_if = "Option::is_none")]
792    pub interface_c: Option<InterfaceStandard<CStandard>>,
793    #[serde(default, skip_serializing_if = "Option::is_none")]
794    pub interface_cxx: Option<InterfaceStandard<CxxStandard>>,
795}
796
797impl Default for LanguageStandardsSummary {
798    fn default() -> Self {
799        let resolved = ResolvedLanguageStandards::default();
800        Self {
801            c: resolved.c,
802            cxx: resolved.cxx,
803            targets: BTreeMap::new(),
804        }
805    }
806}
807
808impl LanguageStandardsSummary {
809    /// Compute the summary from a package's declarations.
810    #[must_use]
811    pub fn from_package(package: &crate::Package) -> Self {
812        let resolved = resolve_language_standards(&package.language);
813        let targets = package
814            .targets
815            .iter()
816            .map(|target| {
817                let library_like = target.kind.produces_archive() || target.kind.is_header_only();
818                let summary = TargetStandardsSummary {
819                    c: effective_c(&resolved, target),
820                    cxx: effective_cxx(&resolved, target),
821                    interface_c: library_like
822                        .then(|| interface_c(&resolved, &package.language, target)),
823                    interface_cxx: library_like
824                        .then(|| interface_cxx(&resolved, &package.language, target)),
825                };
826                (target.name.as_str().to_owned(), summary)
827            })
828            .collect();
829        Self {
830            c: resolved.c,
831            cxx: resolved.cxx,
832            targets,
833        }
834    }
835
836    /// Stable line serialization for the build-configuration
837    /// fingerprint. Values only — provenance must not move the
838    /// fingerprint.
839    #[must_use]
840    pub fn fingerprint_lines(&self) -> Vec<String> {
841        let mut lines = vec![
842            format!("c={}", self.c.standard),
843            format!("cxx={}", self.cxx.standard),
844        ];
845        for (name, target) in &self.targets {
846            lines.push(format!("target={name}"));
847            lines.push(format!("c={}", target.c.standard));
848            lines.push(format!("cxx={}", target.cxx.standard));
849            if let Some(interface) = &target.interface_c {
850                lines.push(format!("interface-c={}", interface.standard));
851            }
852            if let Some(interface) = &target.interface_cxx {
853                lines.push(format!("interface-cxx={}", interface.standard));
854            }
855        }
856        lines
857    }
858}
859
860#[cfg(test)]
861mod tests {
862    use super::*;
863    use crate::{TargetKind, TargetName};
864    use camino::Utf8PathBuf;
865
866    fn target(kind: TargetKind, sources: &[&str], language: LanguageStandardSettings) -> Target {
867        Target {
868            name: TargetName::new("t").unwrap(),
869            kind,
870            sources: sources.iter().map(Utf8PathBuf::from).collect(),
871            include_dirs: Vec::new(),
872            defines: Vec::new(),
873            deps: Vec::new(),
874            language,
875        }
876    }
877
878    #[test]
879    fn standards_parse_round_trip_and_reject_unknown_values() {
880        for s in CStandard::ALL {
881            assert_eq!(CStandard::parse(s.as_str()).unwrap(), s);
882        }
883        for s in CxxStandard::ALL {
884            assert_eq!(CxxStandard::parse(s.as_str()).unwrap(), s);
885        }
886        let err = CStandard::parse("c++17").unwrap_err();
887        assert!(
888            err.to_string()
889                .contains("expected one of c89, c99, c11, c17, c23"),
890            "unexpected message: {err}"
891        );
892        let err = CxxStandard::parse("c++26").unwrap_err();
893        assert!(
894            err.to_string().contains("c++23"),
895            "unexpected message: {err}"
896        );
897        assert!(
898            err.to_string().contains("unknown C++ standard `c++26`"),
899            "unexpected message: {err}"
900        );
901    }
902
903    #[test]
904    fn standards_order_chronologically() {
905        assert!(CStandard::C99 < CStandard::C23);
906        assert!(CStandard::C11 < CStandard::C17);
907        assert!(CxxStandard::Cxx14 < CxxStandard::Cxx20);
908        assert!(CxxStandard::Cxx98 < CxxStandard::Cxx03);
909    }
910
911    #[test]
912    fn msvc_spellings_cover_exactly_the_stable_flags() {
913        assert_eq!(
914            LanguageStandard::Cxx(CxxStandard::Cxx20).msvc_spelling(),
915            Some("/std:c++20")
916        );
917        assert_eq!(
918            LanguageStandard::C(CStandard::C17).msvc_spelling(),
919            Some("/std:c17")
920        );
921        assert_eq!(LanguageStandard::C(CStandard::C99).msvc_spelling(), None);
922        assert_eq!(
923            LanguageStandard::Cxx(CxxStandard::Cxx23).msvc_spelling(),
924            None
925        );
926        assert_eq!(
927            LanguageStandard::Cxx(CxxStandard::Cxx11).msvc_spelling(),
928            None
929        );
930    }
931
932    #[test]
933    fn effective_standard_prefers_target_then_package_then_builtin() {
934        let undeclared = resolve_language_standards(&LanguageStandardSettings::default());
935        let plain = target(
936            TargetKind::Executable,
937            &["a.cc"],
938            LanguageStandardSettings::default(),
939        );
940        let effective = effective_cxx(&undeclared, &plain);
941        assert_eq!(effective.standard, CxxStandard::Cxx17);
942        assert_eq!(effective.source, LanguageStandardSource::BuiltinDefault);
943
944        let package = resolve_language_standards(&LanguageStandardSettings {
945            cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx14)),
946            ..Default::default()
947        });
948        let effective = effective_cxx(&package, &plain);
949        assert_eq!(effective.standard, CxxStandard::Cxx14);
950        assert_eq!(effective.source, LanguageStandardSource::Package);
951
952        let overridden = target(
953            TargetKind::Executable,
954            &["a.cc"],
955            LanguageStandardSettings {
956                cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
957                ..Default::default()
958            },
959        );
960        let effective = effective_cxx(&package, &overridden);
961        assert_eq!(effective.standard, CxxStandard::Cxx20);
962        assert_eq!(effective.source, LanguageStandardSource::Target);
963    }
964
965    #[test]
966    fn interface_standard_falls_back_to_effective_compile_standard() {
967        let package_settings = LanguageStandardSettings {
968            cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
969            ..Default::default()
970        };
971        let resolved = resolve_language_standards(&package_settings);
972        let lib = target(
973            TargetKind::Library,
974            &["a.cc"],
975            LanguageStandardSettings::default(),
976        );
977        let interface = interface_cxx(&resolved, &package_settings, &lib);
978        assert_eq!(interface.standard, CxxStandard::Cxx20);
979        assert_eq!(interface.source, InterfaceStandardSource::CompileStandard);
980
981        let package_interface = LanguageStandardSettings {
982            cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
983            interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
984            ..Default::default()
985        };
986        let resolved = resolve_language_standards(&package_interface);
987        let interface = interface_cxx(&resolved, &package_interface, &lib);
988        assert_eq!(interface.standard, CxxStandard::Cxx17);
989        assert_eq!(interface.source, InterfaceStandardSource::Package);
990
991        let lib_override = target(
992            TargetKind::Library,
993            &["a.cc"],
994            LanguageStandardSettings {
995                interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx14)),
996                ..Default::default()
997            },
998        );
999        let interface = interface_cxx(&resolved, &package_interface, &lib_override);
1000        assert_eq!(interface.standard, CxxStandard::Cxx14);
1001        assert_eq!(interface.source, InterfaceStandardSource::Target);
1002    }
1003
1004    #[test]
1005    fn imposes_requirement_relevance_rules() {
1006        let none = LanguageStandardSettings::default();
1007        // A pure-C library imposes no C++ requirement.
1008        let c_lib = target(
1009            TargetKind::Library,
1010            &["a.c"],
1011            LanguageStandardSettings::default(),
1012        );
1013        assert!(imposes_requirement(&c_lib, &none, SourceLanguage::C));
1014        assert!(!imposes_requirement(&c_lib, &none, SourceLanguage::Cxx));
1015
1016        // A package-level *implementation* default alone creates no
1017        // relevance for a target without that language.
1018        let package_impl = LanguageStandardSettings {
1019            cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1020            ..Default::default()
1021        };
1022        assert!(!imposes_requirement(
1023            &c_lib,
1024            &package_impl,
1025            SourceLanguage::Cxx
1026        ));
1027
1028        // A target-level field (implementation or interface) does.
1029        let declared = target(
1030            TargetKind::Library,
1031            &["a.c"],
1032            LanguageStandardSettings {
1033                interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1034                ..Default::default()
1035            },
1036        );
1037        assert!(imposes_requirement(&declared, &none, SourceLanguage::Cxx));
1038
1039        // Header-only + package-level *interface* standard does.
1040        let header_only = target(
1041            TargetKind::HeaderOnly,
1042            &[],
1043            LanguageStandardSettings::default(),
1044        );
1045        assert!(!imposes_requirement(
1046            &header_only,
1047            &none,
1048            SourceLanguage::Cxx
1049        ));
1050        let package_interface = LanguageStandardSettings {
1051            interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1052            ..Default::default()
1053        };
1054        assert!(imposes_requirement(
1055            &header_only,
1056            &package_interface,
1057            SourceLanguage::Cxx
1058        ));
1059        // ... but not via a package-level implementation default.
1060        assert!(!imposes_requirement(
1061            &header_only,
1062            &package_impl,
1063            SourceLanguage::Cxx
1064        ));
1065    }
1066
1067    #[test]
1068    fn conflict_fires_only_for_declared_language_and_matching_bucket() {
1069        let flags = ResolvedProfileFlags {
1070            cxxflags: vec!["-std=c++14".to_owned()],
1071            ..Default::default()
1072        };
1073        let declared_cxx = LanguageStandardSettings {
1074            cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1075            ..Default::default()
1076        };
1077
1078        // Nothing declared: never a conflict.
1079        assert!(
1080            find_standard_flag_conflicts("p", &LanguageStandardSettings::default(), &[], &flags)
1081                .is_empty()
1082        );
1083
1084        // Declared C++ + `-std=` in cxxflags: a package-scoped
1085        // conflict candidate.
1086        let conflicts = find_standard_flag_conflicts("p", &declared_cxx, &[], &flags);
1087        let conflict = conflicts.first().unwrap();
1088        assert_eq!(conflicts.len(), 1);
1089        assert_eq!(conflict.language, SourceLanguage::Cxx);
1090        assert_eq!(conflict.flag, "-std=c++14");
1091        assert_eq!(conflict.field, "cxx-standard");
1092        assert_eq!(conflict.target, None);
1093        assert!(conflict.to_string().contains("cxx-standard"));
1094
1095        // Declared C++ + `-std=` in cflags only: no conflict.
1096        let c_only_flags = ResolvedProfileFlags {
1097            cflags: vec!["-std=c99".to_owned()],
1098            ..Default::default()
1099        };
1100        assert!(find_standard_flag_conflicts("p", &declared_cxx, &[], &c_only_flags).is_empty());
1101
1102        // A target-level declaration counts as declared.
1103        let t = target(
1104            TargetKind::Executable,
1105            &["a.c"],
1106            LanguageStandardSettings {
1107                c_standard: Some(StandardDeclaration::Declared(CStandard::C17)),
1108                ..Default::default()
1109            },
1110        );
1111        let conflicts = find_standard_flag_conflicts(
1112            "p",
1113            &LanguageStandardSettings::default(),
1114            std::slice::from_ref(&t),
1115            &c_only_flags,
1116        );
1117        let conflict = conflicts.first().unwrap();
1118        assert_eq!(conflict.language, SourceLanguage::C);
1119        assert_eq!(conflict.flag_list, "cflags");
1120        // A target-level declaration scopes the candidate to that
1121        // target so the planner only surfaces it when the target's
1122        // compile is planned.
1123        assert_eq!(conflict.target.as_deref(), Some("t"));
1124
1125        // `/std:` and `--std=` prefixes are recognized too.
1126        let msvc_flags = ResolvedProfileFlags {
1127            cxxflags: vec!["/std:c++latest".to_owned()],
1128            ..Default::default()
1129        };
1130        assert!(!find_standard_flag_conflicts("p", &declared_cxx, &[], &msvc_flags).is_empty());
1131    }
1132
1133    #[test]
1134    fn summary_lists_every_target_with_interface_only_for_library_like() {
1135        use crate::{Package, PackageName};
1136        let package = Package::new(
1137            PackageName::new("demo").unwrap(),
1138            semver::Version::parse("0.1.0").unwrap(),
1139            vec![
1140                target(
1141                    TargetKind::Executable,
1142                    &["main.cc"],
1143                    LanguageStandardSettings::default(),
1144                ),
1145                Target {
1146                    name: TargetName::new("core").unwrap(),
1147                    kind: TargetKind::Library,
1148                    sources: vec![Utf8PathBuf::from("core.cc")],
1149                    include_dirs: Vec::new(),
1150                    defines: Vec::new(),
1151                    deps: Vec::new(),
1152                    language: LanguageStandardSettings {
1153                        cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1154                        interface_cxx_standard: Some(StandardDeclaration::Declared(
1155                            CxxStandard::Cxx17,
1156                        )),
1157                        ..Default::default()
1158                    },
1159                },
1160            ],
1161            Vec::new(),
1162        )
1163        .unwrap();
1164        let summary = LanguageStandardsSummary::from_package(&package);
1165        assert_eq!(summary.cxx.standard, CxxStandard::Cxx17);
1166        assert_eq!(summary.targets.len(), 2);
1167        let exe = &summary.targets["t"];
1168        assert!(exe.interface_c.is_none() && exe.interface_cxx.is_none());
1169        let core = &summary.targets["core"];
1170        assert_eq!(core.cxx.standard, CxxStandard::Cxx20);
1171        assert_eq!(core.interface_cxx.unwrap().standard, CxxStandard::Cxx17);
1172        assert_eq!(
1173            core.interface_c.unwrap().source,
1174            InterfaceStandardSource::CompileStandard
1175        );
1176    }
1177
1178    #[test]
1179    fn package_level_interface_fields_are_inert_without_library_like_targets() {
1180        use crate::{Package, PackageName};
1181        // docs/language-standards.md: package-level interface fields
1182        // are "allowed, and inert, in packages without any"
1183        // library-like target — the summary must not attach them to
1184        // executables.
1185        let package = Package::new(
1186            PackageName::new("demo").unwrap(),
1187            semver::Version::parse("0.1.0").unwrap(),
1188            vec![target(
1189                TargetKind::Executable,
1190                &["main.cc"],
1191                LanguageStandardSettings::default(),
1192            )],
1193            Vec::new(),
1194        )
1195        .unwrap()
1196        .with_language(LanguageStandardSettings {
1197            interface_c_standard: Some(StandardDeclaration::Declared(CStandard::C17)),
1198            interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1199            ..Default::default()
1200        });
1201        let summary = LanguageStandardsSummary::from_package(&package);
1202        let exe = &summary.targets["t"];
1203        assert!(
1204            exe.interface_c.is_none() && exe.interface_cxx.is_none(),
1205            "package-level interface fields must stay inert on executables"
1206        );
1207    }
1208
1209    #[test]
1210    fn fingerprint_lines_are_values_only_and_deterministic() {
1211        let mut summary = LanguageStandardsSummary::default();
1212        let lines = summary.fingerprint_lines();
1213        assert_eq!(lines, vec!["c=c11".to_owned(), "cxx=c++17".to_owned()]);
1214
1215        // Provenance must not appear in the lines.
1216        summary.cxx = ResolvedStandard {
1217            standard: CxxStandard::Cxx17,
1218            source: LanguageStandardSource::Package,
1219        };
1220        assert_eq!(summary.fingerprint_lines(), lines);
1221
1222        summary.targets.insert(
1223            "core".to_owned(),
1224            TargetStandardsSummary {
1225                c: summary.c,
1226                cxx: ResolvedStandard {
1227                    standard: CxxStandard::Cxx20,
1228                    source: LanguageStandardSource::Target,
1229                },
1230                interface_c: None,
1231                interface_cxx: Some(InterfaceStandard {
1232                    standard: CxxStandard::Cxx17,
1233                    source: InterfaceStandardSource::Target,
1234                }),
1235            },
1236        );
1237        assert_eq!(
1238            summary.fingerprint_lines(),
1239            vec![
1240                "c=c11".to_owned(),
1241                "cxx=c++17".to_owned(),
1242                "target=core".to_owned(),
1243                "c=c11".to_owned(),
1244                "cxx=c++20".to_owned(),
1245                "interface-cxx=c++17".to_owned(),
1246            ]
1247        );
1248    }
1249
1250    #[test]
1251    fn standard_declaration_serde_is_a_bare_string_and_rejects_markers() {
1252        let declared: StandardDeclaration<CxxStandard> =
1253            StandardDeclaration::Declared(CxxStandard::Cxx20);
1254        let inherited: StandardDeclaration<CxxStandard> =
1255            StandardDeclaration::Inherited(CxxStandard::Cxx20);
1256        assert_eq!(serde_json::to_string(&declared).unwrap(), "\"c++20\"");
1257        assert_eq!(serde_json::to_string(&inherited).unwrap(), "\"c++20\"");
1258        let marker: StandardDeclaration<CxxStandard> = StandardDeclaration::Workspace;
1259        assert!(serde_json::to_string(&marker).is_err());
1260        let parsed: StandardDeclaration<CxxStandard> = serde_json::from_str("\"c++20\"").unwrap();
1261        assert_eq!(parsed, StandardDeclaration::Declared(CxxStandard::Cxx20));
1262    }
1263
1264    #[test]
1265    fn inherited_standard_resolves_with_workspace_source() {
1266        let settings = LanguageStandardSettings {
1267            cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx20)),
1268            ..Default::default()
1269        };
1270        let resolved = resolve_language_standards(&settings);
1271        assert_eq!(resolved.cxx.standard, CxxStandard::Cxx20);
1272        assert_eq!(resolved.cxx.source, LanguageStandardSource::Workspace);
1273        assert_eq!(resolved.c.source, LanguageStandardSource::BuiltinDefault);
1274    }
1275
1276    #[test]
1277    fn inherited_interface_standard_resolves_with_workspace_source() {
1278        let settings = LanguageStandardSettings {
1279            cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1280            interface_cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx17)),
1281            ..Default::default()
1282        };
1283        let resolved = resolve_language_standards(&settings);
1284        let lib = target(
1285            TargetKind::Library,
1286            &["a.cc"],
1287            LanguageStandardSettings::default(),
1288        );
1289        let interface = interface_cxx(&resolved, &settings, &lib);
1290        assert_eq!(interface.standard, CxxStandard::Cxx17);
1291        assert_eq!(interface.source, InterfaceStandardSource::Workspace);
1292    }
1293
1294    #[test]
1295    fn inherited_values_behave_like_declarations_for_conflicts_and_relevance() {
1296        let flags = ResolvedProfileFlags {
1297            cxxflags: vec!["-std=gnu++20".to_owned()],
1298            ..Default::default()
1299        };
1300        let inherited = LanguageStandardSettings {
1301            cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx17)),
1302            ..Default::default()
1303        };
1304        assert_eq!(
1305            find_standard_flag_conflicts("p", &inherited, &[], &flags).len(),
1306            1
1307        );
1308
1309        let header_only = target(
1310            TargetKind::HeaderOnly,
1311            &[],
1312            LanguageStandardSettings::default(),
1313        );
1314        let pkg = LanguageStandardSettings {
1315            interface_cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx20)),
1316            ..Default::default()
1317        };
1318        assert!(imposes_requirement(&header_only, &pkg, SourceLanguage::Cxx));
1319    }
1320
1321    #[test]
1322    fn fingerprint_is_identical_for_declared_and_inherited_values() {
1323        use crate::{Package, PackageName};
1324        let make = |decl: StandardDeclaration<CxxStandard>| {
1325            let package = Package::new(
1326                PackageName::new("demo").unwrap(),
1327                semver::Version::parse("0.1.0").unwrap(),
1328                vec![target(
1329                    TargetKind::Executable,
1330                    &["main.cc"],
1331                    LanguageStandardSettings::default(),
1332                )],
1333                Vec::new(),
1334            )
1335            .unwrap()
1336            .with_language(LanguageStandardSettings {
1337                cxx_standard: Some(decl),
1338                ..Default::default()
1339            });
1340            LanguageStandardsSummary::from_package(&package).fingerprint_lines()
1341        };
1342        assert_eq!(
1343            make(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1344            make(StandardDeclaration::Inherited(CxxStandard::Cxx20))
1345        );
1346    }
1347
1348    #[test]
1349    fn workspace_marker_field_reports_the_first_marker() {
1350        assert_eq!(
1351            LanguageStandardSettings::default().workspace_marker_field(),
1352            None
1353        );
1354        let settings = LanguageStandardSettings {
1355            interface_c_standard: Some(StandardDeclaration::Workspace),
1356            ..Default::default()
1357        };
1358        assert_eq!(
1359            settings.workspace_marker_field(),
1360            Some("interface-c-standard")
1361        );
1362        let settings = LanguageStandardSettings {
1363            c_standard: Some(StandardDeclaration::Workspace),
1364            interface_c_standard: Some(StandardDeclaration::Workspace),
1365            ..Default::default()
1366        };
1367        assert_eq!(settings.workspace_marker_field(), Some("c-standard"));
1368    }
1369}