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;