1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Copyright (C) 2026 Michael S. Klishin and Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// See LICENSE-APACHE and LICENSE-MIT for details.
//! Which symbol classes the analyzer resolves, and the classifier from a
//! symbol to its class.
//!
//! A symbol-presence taxonomy across mechanisms: macro, record, local
//! call, include, and behaviour resolve on the target tree; qualified
//! call and type through the snapshot. The fold routes a break by class
//! against the producing row's coverage: a checked class is a resolver
//! bug, an unchecked one a coverage gap.
use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::model::names::vocabulary;
use crate::model::symbol::SymbolKind;
vocabulary!(
/// A class of symbol whose presence a backport can dangle on.
#[derive(Hash, PartialOrd, Ord)]
pub enum ResolverClass: "resolver class" {
Macro => "macro",
Include => "include",
Behaviour => "behaviour",
QualifiedCall => "qualified_call",
Record => "record",
LocalCall => "local_call",
Type => "type",
IndirectCall => "indirect_call",
}
);
impl ResolverClass {
/// Whether the analyzer resolves this class. Macro, include,
/// behaviour, record, local call, and indirect call resolve on the
/// target tree; qualified call and type resolve through the
/// snapshot and self-pin.
// exhaustive so a newly added class fails to compile here rather than silently return true
#[must_use]
pub fn is_covered(self) -> bool {
match self {
ResolverClass::Macro
| ResolverClass::Include
| ResolverClass::Behaviour
| ResolverClass::QualifiedCall
| ResolverClass::Record
| ResolverClass::LocalCall
| ResolverClass::Type
| ResolverClass::IndirectCall => true,
}
}
/// The class a symbol falls in. One class per kind, so the producer
/// and the consumer agree without a hand-copied table: a function is
/// a qualified call, a type its own class, a callback its behaviour.
#[must_use]
pub fn of_symbol_kind(kind: &SymbolKind) -> ResolverClass {
match kind {
SymbolKind::Macro { .. } => ResolverClass::Macro,
SymbolKind::Record { .. } => ResolverClass::Record,
SymbolKind::Behaviour { .. } | SymbolKind::Callback { .. } => ResolverClass::Behaviour,
SymbolKind::Type { .. } => ResolverClass::Type,
SymbolKind::Function { .. } | SymbolKind::FunctionAnyArity { .. } => {
ResolverClass::QualifiedCall
}
}
}
}
/// The set of classes a producer checks, stamped on a verdict envelope
/// so a corpus row self-describes its producer's coverage. Stores only
/// the checked set: `unchecked` is derived, so a class can never be in
/// both or neither.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ResolverCoverage {
checked: BTreeSet<ResolverClass>,
}
impl ResolverCoverage {
/// Coverage of the running binary.
#[must_use]
pub fn current() -> Self {
Self {
checked: ResolverClass::ALL
.iter()
.copied()
.filter(|c| c.is_covered())
.collect(),
}
}
/// Whether this producer checked `class`: the fold's bug-vs-gap test.
#[must_use]
pub fn is_checked(&self, class: ResolverClass) -> bool {
self.checked.contains(&class)
}
/// Classes this producer did not check: the calibration sampler steers
/// toward candidates touching one.
pub fn unchecked(&self) -> impl Iterator<Item = ResolverClass> + '_ {
ResolverClass::ALL
.iter()
.copied()
.filter(|c| !self.checked.contains(c))
}
}