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 SupportedToolchain {
83 versions: &["4.30.0-rc2"],
84 header_digest: "790b121ce52942086a360a91f6db5f0f738043bc87b669daffa3fb8bc01e6dd3",
85 missing_symbols: &[],
86 },
87];
88
89#[must_use]
91pub fn supported_for(version: &str) -> Option<&'static SupportedToolchain> {
92 SUPPORTED_TOOLCHAINS.iter().find(|t| t.includes(version))
93}
94
95#[must_use]
98pub fn supported_by_digest(digest: &str) -> Option<&'static SupportedToolchain> {
99 SUPPORTED_TOOLCHAINS.iter().find(|t| t.header_digest == digest)
100}
101
102#[must_use]
106pub fn symbol_present_in_window(symbol: &str) -> bool {
107 SUPPORTED_TOOLCHAINS
108 .iter()
109 .all(|t| !t.missing_symbols.contains(&symbol))
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn window_is_non_empty_and_ordered_by_first_version() {
118 assert!(!SUPPORTED_TOOLCHAINS.is_empty());
119 for w in SUPPORTED_TOOLCHAINS.windows(2) {
120 let (Some(prev), Some(next)) = (w.first(), w.get(1)) else {
121 continue;
122 };
123 let (Some(a), Some(b)) = (prev.versions.first(), next.versions.first()) else {
124 continue;
125 };
126 assert!(
127 a < b,
128 "SUPPORTED_TOOLCHAINS must be sorted ascending by first version: {a} >= {b}",
129 );
130 }
131 }
132
133 #[test]
134 fn every_entry_lists_at_least_one_version() {
135 for t in SUPPORTED_TOOLCHAINS {
136 assert!(
137 !t.versions.is_empty(),
138 "entry with digest {} has no versions",
139 t.header_digest
140 );
141 }
142 }
143
144 #[test]
145 fn digests_are_distinct() {
146 for (i, a) in SUPPORTED_TOOLCHAINS.iter().enumerate() {
147 let Some(rest) = SUPPORTED_TOOLCHAINS.get(i + 1..) else {
148 continue;
149 };
150 for b in rest {
151 assert_ne!(
152 a.header_digest, b.header_digest,
153 "{:?} and {:?} share a header digest \u{2014} merge their `versions` arrays",
154 a.versions, b.versions,
155 );
156 }
157 }
158 }
159
160 #[test]
161 fn versions_are_distinct_across_entries() {
162 let mut seen: Vec<&str> = Vec::new();
163 for t in SUPPORTED_TOOLCHAINS {
164 for &v in t.versions {
165 assert!(
166 !seen.contains(&v),
167 "version {v} appears in more than one SupportedToolchain entry",
168 );
169 seen.push(v);
170 }
171 }
172 }
173
174 #[test]
175 fn digests_are_64_lowercase_hex() {
176 for t in SUPPORTED_TOOLCHAINS {
177 assert_eq!(
178 t.header_digest.len(),
179 64,
180 "entry for {:?}: digest is not 64 chars",
181 t.versions,
182 );
183 assert!(
184 t.header_digest
185 .bytes()
186 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)),
187 "entry for {:?}: digest is not lowercase hex",
188 t.versions,
189 );
190 }
191 }
192
193 #[test]
194 fn lookups_round_trip() {
195 for t in SUPPORTED_TOOLCHAINS {
196 for &v in t.versions {
197 assert_eq!(supported_for(v), Some(t));
198 }
199 assert_eq!(supported_by_digest(t.header_digest), Some(t));
200 }
201 assert!(supported_for("0.0.0").is_none());
202 assert!(supported_by_digest("0").is_none());
203 }
204
205 #[test]
206 fn fully_present_symbols_pass_window_check() {
207 for &s in crate::REQUIRED_SYMBOLS {
208 assert!(symbol_present_in_window(s), "{s} should be in all supported toolchains");
209 }
210 }
211
212 #[test]
213 fn unknown_symbol_passes_window_check() {
214 assert!(symbol_present_in_window("lean_does_not_exist_zzz"));
218 }
219}