Skip to main content

compatdb_core/
resolve.rs

1//! Folding the rules that match a process into the settings it gets, with
2//! where each value came from.
3
4use std::collections::BTreeMap;
5
6#[cfg(doc)]
7use crate::runtime::basename_of;
8use crate::{
9    Argument, D3d9, DebugChannel, DllOverride, Dxgi, ENV_VAR, EnvEntry, OpenGl, Rule, Scalar,
10    ScalarValue, Setting, Source, Specificity, Table, VersionInfo, Wined3d, metal_hud_env, runtime,
11};
12
13/// The settings that apply to one process.
14///
15/// Every matching rule folded in the order [`Table::resolve`] describes, so a
16/// scalar holds the value of the last rule that set it and the lists
17/// accumulate. Also says where each value came from, for the log.
18#[derive(Clone, Default, Debug, PartialEq, Eq)]
19pub struct Resolution {
20    /// The state of each scalar setting a rule set or reset; one that is
21    /// missing is unset.
22    scalars: BTreeMap<Scalar, ScalarState>,
23    /// The `dll_overrides` entries of every matching rule in fold order,
24    /// without the `default` entries, which only take modules out of the
25    /// entries before them.
26    pub dll_overrides: Vec<DllOverride>,
27    /// Where each entry of [`Resolution::dll_overrides`] came from, at the
28    /// same index.
29    pub dll_overrides_from: Vec<Origin>,
30    /// Each module a `<module>=default` entry named, in fold order, with
31    /// what that did to the entries before it.
32    pub dll_overrides_reset: Vec<DllOverrideReset>,
33    /// The texts to append to the command line, in fold order, after any
34    /// `arguments_override` replaced what came before it.
35    pub arguments: Vec<Argument>,
36    /// Where each entry of [`Resolution::arguments`] came from, at the same
37    /// index.
38    pub arguments_from: Vec<Origin>,
39    /// Each `arguments_override` that dropped entries from the argument
40    /// list, in fold order.
41    pub arguments_dropped: Vec<ArgumentsDropped>,
42    /// The `debug_channels` entries of every matching rule in fold order,
43    /// which `compatdb.so` only logs (see [`Rule::debug_channels`]).
44    pub debug_channels: Vec<DebugChannel>,
45    /// Where each entry of [`Resolution::debug_channels`] came from, at the
46    /// same index.
47    pub debug_channels_from: Vec<Origin>,
48    /// The `env` entries of every matching rule in fold order, followed by
49    /// those [`Resolution::fold_derived_env`] adds.
50    /// [`Resolution::env_steps`] says which of them apply in a process.
51    pub env: Vec<EnvEntry>,
52    /// Where each entry of [`Resolution::env`] came from, at the same index.
53    pub env_from: Vec<EnvFrom>,
54    /// Whether [`Resolution::fold_derived_env`] has run.
55    derived_env: bool,
56    /// Where in [`Resolution::env`] the rule that set `metal_hud` started
57    /// its entries, which is where [`Resolution::fold_derived_env`] puts
58    /// the entries of the setting.
59    metal_hud_at: Option<usize>,
60}
61
62/// Where a scalar setting of a [`Resolution`] ended up.
63#[non_exhaustive]
64#[derive(Clone, Default, Debug, PartialEq, Eq)]
65pub struct ScalarState {
66    /// The value of the last rule folded that set it, or `None` when no rule
67    /// set it or a later rule reset it.
68    pub value: Option<ScalarValue>,
69    /// The rule of that value; `Some` exactly when `value` is.
70    pub set_by: Option<Origin>,
71    /// The rule that reset the setting to `default` when no rule folded
72    /// after it set it again; never `Some` together with `value`.
73    pub reset_by: Option<Origin>,
74}
75
76/// The state of a setting no rule set or reset.
77static UNSET: ScalarState = ScalarState {
78    value: None,
79    set_by: None,
80    reset_by: None,
81};
82
83/// What a `<module>=default` entry of `dll_overrides` did.
84#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct DllOverrideReset {
86    /// The module it names.
87    pub module: String,
88    /// The rule of the entry.
89    pub by: Origin,
90    /// Where each entry it took the module out of came from; empty when no
91    /// rule folded before it had given the module a load order.
92    pub removed: Vec<Origin>,
93}
94
95/// Where a value of a [`Resolution`] came from: the rule that set it, by its
96/// [`Source`] and, for a built-in rule that does not match every process,
97/// its matchers.
98#[derive(Clone, Debug, PartialEq, Eq)]
99pub enum Origin {
100    /// A built-in rule that matches every process.
101    BuiltIn,
102    /// A built-in rule for the processes its matchers (see
103    /// [`Rule::matchers`]) describe.
104    BuiltInFor(String),
105    /// Line `n` of [`ENV_VAR`].
106    Line(usize),
107}
108
109impl Origin {
110    /// Where the values of `rule` come from, a rule of a table with the
111    /// source `source`.
112    #[must_use]
113    pub fn of(source: Source, rule: &Rule) -> Self {
114        match source {
115            Source::Line(n) => Self::Line(n),
116            Source::BuiltIn if rule.specificity() == Specificity::Any => Self::BuiltIn,
117            Source::BuiltIn => Self::BuiltInFor(rule.matchers()),
118        }
119    }
120
121    /// Whether the `env` entries of a rule from here are defaults, which
122    /// leave a variable the process already has alone: those of a built-in
123    /// rule that matches every process (see [`Rule::env`]).
124    #[must_use]
125    pub const fn env_is_default(&self) -> bool {
126        matches!(self, Self::BuiltIn)
127    }
128
129    /// How the log names it: `built-in`, `built-in for <matchers>` or
130    /// `WINE_COMPATDB line <n>`.
131    #[must_use]
132    pub fn label(&self) -> String {
133        match self {
134            Self::BuiltIn => String::from("built-in"),
135            Self::BuiltInFor(matchers) => format!("built-in for {matchers}"),
136            Self::Line(n) => format!("{ENV_VAR} line {n}"),
137        }
138    }
139}
140
141/// An `arguments_override` that dropped the arguments the rules folded
142/// before it had accumulated.
143#[derive(Clone, Debug, PartialEq, Eq)]
144pub struct ArgumentsDropped {
145    /// The rule whose `arguments_override` it is.
146    pub by: Origin,
147    /// The arguments it dropped, each with where it came from.
148    pub entries: Vec<(Argument, Origin)>,
149}
150
151/// Where an env entry of a [`Resolution`] came from.
152#[derive(Clone, Debug, PartialEq, Eq)]
153pub struct EnvFrom {
154    /// The rule that set it.
155    pub origin: Origin,
156    /// Whether it is a default (see [`Origin::env_is_default`]), which leaves
157    /// a variable the process already has alone.
158    pub default: bool,
159}
160
161/// What one env entry of a [`Resolution`] does in a process.
162#[derive(Clone, Debug, PartialEq, Eq)]
163pub struct EnvStep {
164    /// The entry: the variable and the value it sets, or none to remove it.
165    pub entry: EnvEntry,
166    /// The rule that set it.
167    pub origin: Origin,
168    /// False for a default whose variable the process already had, which it
169    /// leaves alone.
170    pub applied: bool,
171    /// The origin of a later entry for the same variable that applies, which
172    /// replaces what this one did: the variable ends up with that entry's
173    /// value.
174    pub replaced_by: Option<Origin>,
175    /// For an entry that removes the variable, the origin of the earlier
176    /// entry for it that applied and set a value, which the removal undoes.
177    pub removes: Option<Origin>,
178}
179
180impl Resolution {
181    /// Whether there is nothing to apply or log: no value of a scalar
182    /// setting and no entry of a list.
183    #[must_use]
184    pub fn is_empty(&self) -> bool {
185        self.scalars.values().all(|state| state.value.is_none())
186            && self.dll_overrides.is_empty()
187            && self.arguments.is_empty()
188            && self.debug_channels.is_empty()
189            && self.env.is_empty()
190    }
191
192    /// Where the scalar setting `scalar` ended up: its value and the rule
193    /// that set it, or the rule that reset it.
194    #[must_use]
195    pub fn scalar(&self, scalar: Scalar) -> &ScalarState {
196        self.scalars.get(&scalar).unwrap_or(&UNSET)
197    }
198
199    /// The DXGI-family implementation (see [`Rule::dxgi`]), `None` when no
200    /// rule sets it.
201    #[must_use]
202    pub fn dxgi(&self) -> Option<Dxgi> {
203        match self.scalar(Scalar::Dxgi).value {
204            Some(ScalarValue::Dxgi(dxgi)) => Some(dxgi),
205            _ => None,
206        }
207    }
208
209    /// The D3D9 implementation (see [`Rule::d3d9`]), `None` when no rule
210    /// sets it.
211    #[must_use]
212    pub fn d3d9(&self) -> Option<D3d9> {
213        match self.scalar(Scalar::D3d9).value {
214            Some(ScalarValue::D3d9(d3d9)) => Some(d3d9),
215            _ => None,
216        }
217    }
218
219    /// The renderer of wined3d (see [`Rule::wined3d`]), `None` when no rule
220    /// sets it.
221    #[must_use]
222    pub fn wined3d(&self) -> Option<Wined3d> {
223        match self.scalar(Scalar::Wined3d).value {
224            Some(ScalarValue::Wined3d(wined3d)) => Some(wined3d),
225            _ => None,
226        }
227    }
228
229    /// Which OpenGL the Mac driver gives (see [`Rule::opengl`]), `None` when
230    /// no rule sets it.
231    #[must_use]
232    pub fn opengl(&self) -> Option<OpenGl> {
233        match self.scalar(Scalar::OpenGl).value {
234            Some(ScalarValue::OpenGl(opengl)) => Some(opengl),
235            _ => None,
236        }
237    }
238
239    /// The DPI awareness (see [`Rule::dpi_aware`]), `None` when no rule sets
240    /// it.
241    #[must_use]
242    pub fn dpi_aware(&self) -> Option<bool> {
243        self.switch(Scalar::DpiAware)
244    }
245
246    /// Whether large address awareness is forced (see
247    /// [`Rule::large_address_aware`]), `None` when no rule sets it.
248    #[must_use]
249    pub fn large_address_aware(&self) -> Option<bool> {
250        self.switch(Scalar::LargeAddressAware)
251    }
252
253    /// Whether mouse movement comes from `GCMouse` (see [`Rule::gcmouse`]),
254    /// `None` when no rule sets it.
255    #[must_use]
256    pub fn gcmouse(&self) -> Option<bool> {
257        self.switch(Scalar::Gcmouse)
258    }
259
260    /// Whether no-execute stays on (see [`Rule::force_nx`]), `None` when no
261    /// rule sets it.
262    #[must_use]
263    pub fn force_nx(&self) -> Option<bool> {
264        self.switch(Scalar::ForceNx)
265    }
266
267    /// Whether the Metal performance HUD shows (see [`Rule::metal_hud`]),
268    /// `None` when no rule sets it.
269    #[must_use]
270    pub fn metal_hud(&self) -> Option<bool> {
271        self.switch(Scalar::MetalHud)
272    }
273
274    /// The value of the true-or-false setting `scalar`.
275    fn switch(&self, scalar: Scalar) -> Option<bool> {
276        match self.scalar(scalar).value {
277            Some(ScalarValue::Bool(on)) => Some(on),
278            _ => None,
279        }
280    }
281
282    /// The value the rules' own `env` entries leave `WINE_D3D_CONFIG` at:
283    /// the last entry for it, `Some("")` when that one removes it, and `None`
284    /// when no rule sets it.
285    #[must_use]
286    pub fn d3d_config(&self) -> Option<&str> {
287        self.env
288            .iter()
289            .rev()
290            .find(|entry| is_d3d_config(entry))
291            .map(|entry| entry.value().unwrap_or_default())
292    }
293
294    /// Fold the settings that `compatdb.so` applies as environment entries
295    /// into [`Resolution::env`], each with the rule that set the setting as
296    /// its origin and none of them a default:
297    ///
298    /// - [`Resolution::wined3d`], when a rule sets it: the `WINE_D3D_CONFIG`
299    ///   that names the renderer, after the rules' own entries. It is the
300    ///   value the variable ends up with, and it keeps the other options a
301    ///   rule's `WINE_D3D_CONFIG` set (see [`runtime::wined3d_config`]).
302    ///   Unset, the process keeps whatever `WINE_D3D_CONFIG` it has, from a
303    ///   rule's `env` or inherited, and without one wined3d uses OpenGL.
304    /// - [`Resolution::metal_hud`], when a rule sets it: the entries of
305    ///   [`metal_hud_env`], which set or remove the `MTL_HUD_` variables.
306    ///   They go where the rule that set it folded, before that rule's own
307    ///   `env` entries, so an `env` entry for one of the variables from that
308    ///   rule or from any rule folded after it wins. Unset, the process keeps
309    ///   the ones it was started with.
310    ///
311    /// Call it after [`Table::resolve`], which records where each setting
312    /// came from; a setting without that record adds nothing. A second call
313    /// changes nothing.
314    pub fn fold_derived_env(&mut self) {
315        if std::mem::replace(&mut self.derived_env, true) {
316            return;
317        }
318        if let (Some(wined3d), Some(origin)) =
319            (self.wined3d(), self.scalar(Scalar::Wined3d).set_by.clone())
320        {
321            let config = self.d3d_config().filter(|value| !value.is_empty());
322            let value = runtime::wined3d_config(wined3d, config);
323            // The value is the renderer and the options of a checked one.
324            self.push_derived(EnvEntry::literal(Wined3d::CONFIG_VAR, &value), origin);
325        }
326        if let (Some(on), Some(origin)) = (
327            self.metal_hud(),
328            self.scalar(Scalar::MetalHud).set_by.clone(),
329        ) {
330            let at = self
331                .metal_hud_at
332                .map_or(self.env.len(), |at| at.min(self.env.len()));
333            let entries = metal_hud_env(on);
334            let from: Vec<EnvFrom> = entries
335                .iter()
336                .map(|_| EnvFrom {
337                    origin: origin.clone(),
338                    default: false,
339                })
340                .collect();
341            self.env.splice(at..at, entries);
342            self.env_from.splice(at..at, from);
343        }
344    }
345
346    /// Add `entry`, which a setting from `origin` stands for, after the
347    /// other env entries.
348    fn push_derived(&mut self, entry: EnvEntry, origin: Origin) {
349        self.env.push(entry);
350        self.env_from.push(EnvFrom {
351            origin,
352            default: false,
353        });
354    }
355
356    /// Where the env entry that leaves `WINE_D3D_CONFIG` at
357    /// [`Resolution::d3d_config`] came from, or `None` when no rule sets it.
358    #[must_use]
359    pub fn d3d_config_origin(&self) -> Option<&Origin> {
360        let i = self.env.iter().rposition(is_d3d_config)?;
361        self.env_from.get(i).map(|from| &from.origin)
362    }
363
364    /// Fold what a rule from `origin` says about the scalar setting
365    /// `scalar`: a value replaces the value or reset of a rule folded before,
366    /// and a reset unsets the setting from here on.
367    pub(crate) fn fold_scalar(
368        &mut self,
369        scalar: Scalar,
370        setting: Setting<ScalarValue>,
371        origin: &Origin,
372    ) {
373        let state = match setting {
374            Setting::Unset => return,
375            Setting::Set(value) => ScalarState {
376                value: Some(value),
377                set_by: Some(origin.clone()),
378                reset_by: None,
379            },
380            Setting::Reset => ScalarState {
381                value: None,
382                set_by: None,
383                reset_by: Some(origin.clone()),
384            },
385        };
386        self.scalars.insert(scalar, state);
387    }
388
389    /// Fold the `dll_overrides` entries of `rule` into the list: an entry
390    /// whose order is `default` takes its modules out of the entries before
391    /// it, dropping an entry left without one, and every other entry is
392    /// appended.
393    fn fold_dll_overrides(&mut self, rule: &Rule, origin: &Origin) {
394        for entry in &rule.dll_overrides {
395            if !entry.is_default() {
396                self.dll_overrides.push(entry.clone());
397                self.dll_overrides_from.push(origin.clone());
398                continue;
399            }
400            for module in entry.modules() {
401                let mut removed = Vec::new();
402                let mut kept = Vec::new();
403                let entries = std::mem::take(&mut self.dll_overrides)
404                    .into_iter()
405                    .zip(std::mem::take(&mut self.dll_overrides_from));
406                for (earlier, from) in entries {
407                    match without_module(&earlier, module) {
408                        Without::Absent => kept.push((earlier, from)),
409                        Without::Emptied => removed.push(from),
410                        Without::Rest(rest) => {
411                            removed.push(from.clone());
412                            kept.push((rest, from));
413                        }
414                    }
415                }
416                let (entries, from): (Vec<_>, Vec<_>) = kept.into_iter().unzip();
417                self.dll_overrides = entries;
418                self.dll_overrides_from = from;
419                self.dll_overrides_reset.push(DllOverrideReset {
420                    module: module.to_string(),
421                    by: origin.clone(),
422                    removed,
423                });
424            }
425        }
426    }
427
428    /// Fold the arguments of `rule` into the list: its `arguments_override`,
429    /// when set, replaces what the rules folded before it accumulated, and
430    /// its `arguments` append.
431    fn fold_arguments(&mut self, rule: &Rule, origin: &Origin) {
432        if let Some(over) = &rule.arguments_override {
433            let entries: Vec<(Argument, Origin)> = std::mem::take(&mut self.arguments)
434                .into_iter()
435                .zip(std::mem::take(&mut self.arguments_from))
436                .collect();
437            if !entries.is_empty() {
438                self.arguments_dropped.push(ArgumentsDropped {
439                    by: origin.clone(),
440                    entries,
441                });
442            }
443            self.arguments.extend(over.iter().cloned());
444            self.arguments_from
445                .extend(over.iter().map(|_| origin.clone()));
446        }
447        self.arguments.extend(rule.arguments.iter().cloned());
448        self.arguments_from
449            .extend(rule.arguments.iter().map(|_| origin.clone()));
450    }
451
452    /// The env entries in order, each with the rule that set it and whether
453    /// it applies in a process where `has` says which variables the process
454    /// already has. A default leaves such a variable alone, so a value the
455    /// caller exported wins over a built-in `*` rule; every other entry
456    /// applies, replacing the value or, when empty, removing the variable.
457    /// An entry that a later applied entry for the same variable replaces
458    /// says where that entry came from.
459    ///
460    /// [`Resolution::env_from`] has one entry per env entry, as
461    /// [`Table::resolve`] and [`Resolution::fold_derived_env`] keep it.
462    #[must_use]
463    pub fn env_steps(&self, has: impl Fn(&str) -> bool) -> Vec<EnvStep> {
464        let mut steps: Vec<EnvStep> = self
465            .env
466            .iter()
467            .zip(&self.env_from)
468            .map(|(entry, from)| EnvStep {
469                entry: entry.clone(),
470                origin: from.origin.clone(),
471                applied: !(from.default && has(entry.name().as_str())),
472                replaced_by: None,
473                removes: None,
474            })
475            .collect();
476        let same = |a: &EnvStep, b: &EnvStep| {
477            a.applied
478                && a.entry
479                    .name()
480                    .as_str()
481                    .eq_ignore_ascii_case(b.entry.name().as_str())
482        };
483        let links: Vec<(Option<Origin>, Option<Origin>)> = steps
484            .iter()
485            .enumerate()
486            .map(|(i, step)| {
487                let replaced_by = steps
488                    .iter()
489                    .skip(i.saturating_add(1))
490                    .rev()
491                    .find(|later| same(later, step))
492                    .map(|later| later.origin.clone());
493                let removes = steps
494                    .iter()
495                    .take(i)
496                    .rev()
497                    .find(|earlier| same(earlier, step))
498                    .filter(|earlier| {
499                        step.entry.value().is_none() && earlier.entry.value().is_some()
500                    })
501                    .map(|earlier| earlier.origin.clone());
502                (replaced_by, removes)
503            })
504            .collect();
505        for (step, (replaced_by, removes)) in steps.iter_mut().zip(links) {
506            step.replaced_by = replaced_by;
507            step.removes = removes;
508        }
509        steps
510    }
511}
512
513impl Table {
514    /// Fold every matching rule into one [`Resolution`]. `exe` is the image
515    /// basename (see [`basename_of`]); `version` is the image's version
516    /// resource, for the optional fingerprint constraints.
517    ///
518    /// The rules fold in this order, each group in table order, which is
519    /// line order for the rules of [`ENV_VAR`]:
520    ///
521    /// 1. built-in `*` rules, those without a fingerprint first;
522    /// 2. `*` rules of the variable, those without a fingerprint first;
523    /// 3. built-in rules naming an executable, those without a fingerprint
524    ///    first;
525    /// 4. rules of the variable naming an executable, those without a
526    ///    fingerprint first.
527    ///
528    /// So a scalar field ends up holding the value of the last rule that
529    /// sets it. Whether a rule names an executable decides first, then where
530    /// it comes from, then whether it pins a fingerprint (see
531    /// [`Rule::specificity`]). A launch-wide `*` rule from the variable
532    /// replaces a built-in default but not what a built-in rule for one
533    /// program sets, so that fix survives launch-wide settings, and a rule
534    /// from the variable for the program beats it, with or without a
535    /// fingerprint. The lists accumulate across every rule that matched, in
536    /// the same order. The exceptions are a
537    /// rule's `arguments_override`, which replaces the arguments the rules
538    /// folded before it accumulated (the `arguments` of the rule itself and
539    /// of the rules folded after it append to it), a scalar set to
540    /// `default` (see [`Setting::Reset`]), and a `dll_overrides` entry with
541    /// the order `default`, which takes its modules out of the entries
542    /// before it.
543    ///
544    /// The sort has to stay stable: rules of equal place in that order fold
545    /// in table order.
546    ///
547    /// A rule whose executable matches but whose fingerprint does not simply
548    /// does not apply; nothing falls back to it, and the rules that do match
549    /// still apply.
550    #[must_use]
551    pub fn resolve(&self, exe: &str, version: &VersionInfo) -> Resolution {
552        let mut matching: Vec<(Source, &Rule)> = self
553            .entries()
554            .filter(|(_, rule)| rule.matches(exe, version))
555            .collect();
556        matching.sort_by_key(|(source, rule)| {
557            let specificity = rule.specificity();
558            let names_exe = matches!(
559                specificity,
560                Specificity::Exe | Specificity::ExeWithFingerprint
561            );
562            (names_exe, *source != Source::BuiltIn, specificity)
563        });
564        let mut r = Resolution::default();
565        for (source, rule) in matching {
566            let origin = Origin::of(source, rule);
567            for &scalar in Scalar::ALL {
568                r.fold_scalar(scalar, rule.scalar(scalar), &origin);
569            }
570            r.fold_dll_overrides(rule, &origin);
571            r.fold_arguments(rule, &origin);
572            r.debug_channels.extend(rule.debug_channels.iter().cloned());
573            r.debug_channels_from
574                .extend(rule.debug_channels.iter().map(|_| origin.clone()));
575            match rule.metal_hud {
576                Setting::Set(_) => r.metal_hud_at = Some(r.env.len()),
577                Setting::Reset => r.metal_hud_at = None,
578                Setting::Unset => {}
579            }
580            let default = origin.env_is_default();
581            r.env.extend(rule.env.iter().cloned());
582            r.env_from.extend(rule.env.iter().map(|_| EnvFrom {
583                origin: origin.clone(),
584                default,
585            }));
586        }
587        r
588    }
589}
590
591/// Whether `entry` is for the variable wined3d reads its settings from,
592/// matched ASCII-case-insensitively as in a process environment.
593fn is_d3d_config(entry: &EnvEntry) -> bool {
594    entry
595        .name()
596        .as_str()
597        .eq_ignore_ascii_case(Wined3d::CONFIG_VAR)
598}
599
600/// Whether two module names of `dll_overrides` entries name the same module:
601/// compared case-insensitively, with or without `.dll`.
602fn same_module(a: &str, b: &str) -> bool {
603    let (a, b) = (a.to_ascii_lowercase(), b.to_ascii_lowercase());
604    a.strip_suffix(".dll").unwrap_or(&a) == b.strip_suffix(".dll").unwrap_or(&b)
605}
606
607/// A `dll_overrides` entry with one module taken out (see
608/// [`without_module`]).
609#[derive(Debug, PartialEq, Eq)]
610enum Without {
611    /// The entry does not name the module.
612    Absent,
613    /// The module was the entry's only one, so nothing is left.
614    Emptied,
615    /// The entry with its other modules and the same order.
616    Rest(DllOverride),
617}
618
619/// The `dll_overrides` entry `entry` without `module`.
620fn without_module(entry: &DllOverride, module: &str) -> Without {
621    let all: Vec<&str> = entry.modules().collect();
622    let rest: Vec<&str> = all
623        .iter()
624        .copied()
625        .filter(|name| !same_module(name, module))
626        .collect();
627    if rest.len() == all.len() {
628        Without::Absent
629    } else if rest.is_empty() {
630        Without::Emptied
631    } else {
632        // Made of the parts of a checked entry: its `=` is there.
633        Without::Rest(DllOverride::literal(&format!(
634            "{}={}",
635            rest.join(","),
636            entry.order()
637        )))
638    }
639}
640
641#[cfg(test)]
642mod tests;