1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct SupportedToolchain {
20 pub versions: &'static [&'static str],
23 pub header_digest: &'static str,
25 pub missing_symbols: &'static [&'static str],
28}
29
30impl SupportedToolchain {
31 #[must_use]
34 pub fn includes(&self, version: &str) -> bool {
35 self.versions.contains(&version)
36 }
37}
38
39pub const SUPPORTED_TOOLCHAINS: &[SupportedToolchain] = &[
52 SupportedToolchain {
53 versions: &["4.26.0"],
54 header_digest: "e0ea3efaccceb5b75c7e9e1ab92952c8aa85c3faee28ee949dfeb8ab428ad218",
55 missing_symbols: &[],
56 },
57 SupportedToolchain {
58 versions: &["4.27.0"],
59 header_digest: "42255d180910bb063d97c87cfb2a61550009ca9ceb6f495069c56bfaa6c92e13",
60 missing_symbols: &[],
61 },
62 SupportedToolchain {
63 versions: &["4.28.0"],
64 header_digest: "624726e5f1f10fd77cd95b8fe8f30389312e57c8fc98e6c2f1989289bdb5fb0e",
65 missing_symbols: &[],
66 },
67 SupportedToolchain {
68 versions: &["4.28.1"],
69 header_digest: "648ecfb615ef0222cd63b5f1bbbc379a06749bc0f5f4c2eb16ffca26fd18fe81",
70 missing_symbols: &[],
71 },
72 SupportedToolchain {
73 versions: &["4.29.0"],
74 header_digest: "671683950ef412474bede2c6a2b50aecf4f99bc29e1ddaf2222ee54ad4ffb91c",
75 missing_symbols: &[],
76 },
77 SupportedToolchain {
78 versions: &["4.29.1"],
79 header_digest: "2e481a0dac7215eb16123eaef97298ae5a6d0bd0c28c534c2818e2d2f2a28efc",
80 missing_symbols: &[],
81 },
82];
83
84#[must_use]
86pub fn supported_for(version: &str) -> Option<&'static SupportedToolchain> {
87 SUPPORTED_TOOLCHAINS.iter().find(|t| t.includes(version))
88}
89
90#[must_use]
93pub fn supported_by_digest(digest: &str) -> Option<&'static SupportedToolchain> {
94 SUPPORTED_TOOLCHAINS.iter().find(|t| t.header_digest == digest)
95}
96
97#[must_use]
101pub fn symbol_present_in_window(symbol: &str) -> bool {
102 SUPPORTED_TOOLCHAINS
103 .iter()
104 .all(|t| !t.missing_symbols.contains(&symbol))
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn window_is_non_empty_and_ordered_by_first_version() {
113 assert!(!SUPPORTED_TOOLCHAINS.is_empty());
114 for w in SUPPORTED_TOOLCHAINS.windows(2) {
115 let (Some(prev), Some(next)) = (w.first(), w.get(1)) else {
116 continue;
117 };
118 let (Some(a), Some(b)) = (prev.versions.first(), next.versions.first()) else {
119 continue;
120 };
121 assert!(
122 a < b,
123 "SUPPORTED_TOOLCHAINS must be sorted ascending by first version: {a} >= {b}",
124 );
125 }
126 }
127
128 #[test]
129 fn every_entry_lists_at_least_one_version() {
130 for t in SUPPORTED_TOOLCHAINS {
131 assert!(
132 !t.versions.is_empty(),
133 "entry with digest {} has no versions",
134 t.header_digest
135 );
136 }
137 }
138
139 #[test]
140 fn digests_are_distinct() {
141 for (i, a) in SUPPORTED_TOOLCHAINS.iter().enumerate() {
142 let Some(rest) = SUPPORTED_TOOLCHAINS.get(i + 1..) else {
143 continue;
144 };
145 for b in rest {
146 assert_ne!(
147 a.header_digest, b.header_digest,
148 "{:?} and {:?} share a header digest \u{2014} merge their `versions` arrays",
149 a.versions, b.versions,
150 );
151 }
152 }
153 }
154
155 #[test]
156 fn versions_are_distinct_across_entries() {
157 let mut seen: Vec<&str> = Vec::new();
158 for t in SUPPORTED_TOOLCHAINS {
159 for &v in t.versions {
160 assert!(
161 !seen.contains(&v),
162 "version {v} appears in more than one SupportedToolchain entry",
163 );
164 seen.push(v);
165 }
166 }
167 }
168
169 #[test]
170 fn digests_are_64_lowercase_hex() {
171 for t in SUPPORTED_TOOLCHAINS {
172 assert_eq!(
173 t.header_digest.len(),
174 64,
175 "entry for {:?}: digest is not 64 chars",
176 t.versions,
177 );
178 assert!(
179 t.header_digest
180 .bytes()
181 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)),
182 "entry for {:?}: digest is not lowercase hex",
183 t.versions,
184 );
185 }
186 }
187
188 #[test]
189 fn lookups_round_trip() {
190 for t in SUPPORTED_TOOLCHAINS {
191 for &v in t.versions {
192 assert_eq!(supported_for(v), Some(t));
193 }
194 assert_eq!(supported_by_digest(t.header_digest), Some(t));
195 }
196 assert!(supported_for("0.0.0").is_none());
197 assert!(supported_by_digest("0").is_none());
198 }
199
200 #[test]
201 fn fully_present_symbols_pass_window_check() {
202 for &s in crate::REQUIRED_SYMBOLS {
203 assert!(symbol_present_in_window(s), "{s} should be in all supported toolchains");
204 }
205 }
206
207 #[test]
208 fn unknown_symbol_passes_window_check() {
209 assert!(symbol_present_in_window("lean_does_not_exist_zzz"));
213 }
214}