]> git.proxmox.com Git - rustc.git/blob - src/tools/tidy/src/features.rs
New upstream version 1.34.2+dfsg1
[rustc.git] / src / tools / tidy / src / features.rs
1 //! Tidy check to ensure that unstable features are all in order.
2 //!
3 //! This check will ensure properties like:
4 //!
5 //! * All stability attributes look reasonably well formed.
6 //! * The set of library features is disjoint from the set of language features.
7 //! * Library features have at most one stability level.
8 //! * Library features have at most one `since` value.
9 //! * All unstable lang features have tests to ensure they are actually unstable.
10
11 use std::collections::HashMap;
12 use std::fmt;
13 use std::fs::{self, File};
14 use std::io::prelude::*;
15 use std::path::Path;
16
17 #[derive(Debug, PartialEq, Clone)]
18 pub enum Status {
19 Stable,
20 Removed,
21 Unstable,
22 }
23
24 impl fmt::Display for Status {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26 let as_str = match *self {
27 Status::Stable => "stable",
28 Status::Unstable => "unstable",
29 Status::Removed => "removed",
30 };
31 fmt::Display::fmt(as_str, f)
32 }
33 }
34
35 #[derive(Debug, Clone)]
36 pub struct Feature {
37 pub level: Status,
38 pub since: String,
39 pub has_gate_test: bool,
40 pub tracking_issue: Option<u32>,
41 }
42
43 pub type Features = HashMap<String, Feature>;
44
45 pub fn check(path: &Path, bad: &mut bool, quiet: bool) {
46 let mut features = collect_lang_features(path, bad);
47 assert!(!features.is_empty());
48
49 let lib_features = get_and_check_lib_features(path, bad, &features);
50 assert!(!lib_features.is_empty());
51
52 let mut contents = String::new();
53
54 super::walk_many(&[&path.join("test/ui"),
55 &path.join("test/ui-fulldeps"),
56 &path.join("test/compile-fail")],
57 &mut |path| super::filter_dirs(path),
58 &mut |file| {
59 let filename = file.file_name().unwrap().to_string_lossy();
60 if !filename.ends_with(".rs") || filename == "features.rs" ||
61 filename == "diagnostic_list.rs" {
62 return;
63 }
64
65 let filen_underscore = filename.replace('-',"_").replace(".rs","");
66 let filename_is_gate_test = test_filen_gate(&filen_underscore, &mut features);
67
68 contents.truncate(0);
69 t!(t!(File::open(&file), &file).read_to_string(&mut contents));
70
71 for (i, line) in contents.lines().enumerate() {
72 let mut err = |msg: &str| {
73 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
74 };
75
76 let gate_test_str = "gate-test-";
77
78 let feature_name = match line.find(gate_test_str) {
79 Some(i) => {
80 line[i+gate_test_str.len()..].splitn(2, ' ').next().unwrap()
81 },
82 None => continue,
83 };
84 match features.get_mut(feature_name) {
85 Some(f) => {
86 if filename_is_gate_test {
87 err(&format!("The file is already marked as gate test \
88 through its name, no need for a \
89 'gate-test-{}' comment",
90 feature_name));
91 }
92 f.has_gate_test = true;
93 }
94 None => {
95 err(&format!("gate-test test found referencing a nonexistent feature '{}'",
96 feature_name));
97 }
98 }
99 }
100 });
101
102 // Only check the number of lang features.
103 // Obligatory testing for library features is dumb.
104 let gate_untested = features.iter()
105 .filter(|&(_, f)| f.level == Status::Unstable)
106 .filter(|&(_, f)| !f.has_gate_test)
107 .collect::<Vec<_>>();
108
109 for &(name, _) in gate_untested.iter() {
110 println!("Expected a gate test for the feature '{}'.", name);
111 println!("Hint: create a failing test file named 'feature-gate-{}.rs'\
112 \n in the 'ui' test suite, with its failures due to\
113 \n missing usage of #![feature({})].", name, name);
114 println!("Hint: If you already have such a test and don't want to rename it,\
115 \n you can also add a // gate-test-{} line to the test file.",
116 name);
117 }
118
119 if !gate_untested.is_empty() {
120 tidy_error!(bad, "Found {} features without a gate test.", gate_untested.len());
121 }
122
123 if *bad {
124 return;
125 }
126 if quiet {
127 println!("* {} features", features.len());
128 return;
129 }
130
131 let mut lines = Vec::new();
132 for (name, feature) in features.iter() {
133 lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
134 name,
135 "lang",
136 feature.level,
137 feature.since));
138 }
139 for (name, feature) in lib_features {
140 lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
141 name,
142 "lib",
143 feature.level,
144 feature.since));
145 }
146
147 lines.sort();
148 for line in lines {
149 println!("* {}", line);
150 }
151 }
152
153 fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
154 line.find(attr)
155 .and_then(|i| line[i..].find('"').map(|j| i + j + 1))
156 .and_then(|i| line[i..].find('"').map(|j| (i, i + j)))
157 .map(|(i, j)| &line[i..j])
158 }
159
160 fn test_filen_gate(filen_underscore: &str, features: &mut Features) -> bool {
161 if filen_underscore.starts_with("feature_gate") {
162 for (n, f) in features.iter_mut() {
163 if filen_underscore == format!("feature_gate_{}", n) {
164 f.has_gate_test = true;
165 return true;
166 }
167 }
168 }
169 return false;
170 }
171
172 pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features {
173 let contents = t!(fs::read_to_string(base_src_path.join("libsyntax/feature_gate.rs")));
174
175 // We allow rustc-internal features to omit a tracking issue.
176 // These features must be marked with a `// rustc internal` in its own group.
177 let mut next_feature_is_rustc_internal = false;
178
179 contents.lines().zip(1..)
180 .filter_map(|(line, line_number)| {
181 let line = line.trim();
182 if line.starts_with("// rustc internal") {
183 next_feature_is_rustc_internal = true;
184 return None;
185 } else if line.is_empty() {
186 next_feature_is_rustc_internal = false;
187 return None;
188 }
189
190 let mut parts = line.split(',');
191 let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) {
192 Some("active") => Status::Unstable,
193 Some("removed") => Status::Removed,
194 Some("accepted") => Status::Stable,
195 _ => return None,
196 };
197 let name = parts.next().unwrap().trim();
198 let since = parts.next().unwrap().trim().trim_matches('"');
199 let issue_str = parts.next().unwrap().trim();
200 let tracking_issue = if issue_str.starts_with("None") {
201 if level == Status::Unstable && !next_feature_is_rustc_internal {
202 *bad = true;
203 tidy_error!(
204 bad,
205 "libsyntax/feature_gate.rs:{}: no tracking issue for feature {}",
206 line_number,
207 name,
208 );
209 }
210 None
211 } else {
212 next_feature_is_rustc_internal = false;
213 let s = issue_str.split('(').nth(1).unwrap().split(')').nth(0).unwrap();
214 Some(s.parse().unwrap())
215 };
216 Some((name.to_owned(),
217 Feature {
218 level,
219 since: since.to_owned(),
220 has_gate_test: false,
221 tracking_issue,
222 }))
223 })
224 .collect()
225 }
226
227 pub fn collect_lib_features(base_src_path: &Path) -> Features {
228 let mut lib_features = Features::new();
229
230 // This library feature is defined in the `compiler_builtins` crate, which
231 // has been moved out-of-tree. Now it can no longer be auto-discovered by
232 // `tidy`, because we need to filter out its (submodule) directory. Manually
233 // add it to the set of known library features so we can still generate docs.
234 lib_features.insert("compiler_builtins_lib".to_owned(), Feature {
235 level: Status::Unstable,
236 since: String::new(),
237 has_gate_test: false,
238 tracking_issue: None,
239 });
240
241 map_lib_features(base_src_path,
242 &mut |res, _, _| {
243 if let Ok((name, feature)) = res {
244 if lib_features.contains_key(name) {
245 return;
246 }
247 lib_features.insert(name.to_owned(), feature);
248 }
249 });
250 lib_features
251 }
252
253 fn get_and_check_lib_features(base_src_path: &Path,
254 bad: &mut bool,
255 lang_features: &Features) -> Features {
256 let mut lib_features = Features::new();
257 map_lib_features(base_src_path,
258 &mut |res, file, line| {
259 match res {
260 Ok((name, f)) => {
261 let mut check_features = |f: &Feature, list: &Features, display: &str| {
262 if let Some(ref s) = list.get(name) {
263 if f.tracking_issue != s.tracking_issue {
264 tidy_error!(bad,
265 "{}:{}: mismatches the `issue` in {}",
266 file.display(),
267 line,
268 display);
269 }
270 }
271 };
272 check_features(&f, &lang_features, "corresponding lang feature");
273 check_features(&f, &lib_features, "previous");
274 lib_features.insert(name.to_owned(), f);
275 },
276 Err(msg) => {
277 tidy_error!(bad, "{}:{}: {}", file.display(), line, msg);
278 },
279 }
280
281 });
282 lib_features
283 }
284
285 fn map_lib_features(base_src_path: &Path,
286 mf: &mut dyn FnMut(Result<(&str, Feature), &str>, &Path, usize)) {
287 let mut contents = String::new();
288 super::walk(base_src_path,
289 &mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
290 &mut |file| {
291 let filename = file.file_name().unwrap().to_string_lossy();
292 if !filename.ends_with(".rs") || filename == "features.rs" ||
293 filename == "diagnostic_list.rs" {
294 return;
295 }
296
297 contents.truncate(0);
298 t!(t!(File::open(&file), &file).read_to_string(&mut contents));
299
300 let mut becoming_feature: Option<(String, Feature)> = None;
301 for (i, line) in contents.lines().enumerate() {
302 macro_rules! err {
303 ($msg:expr) => {{
304 mf(Err($msg), file, i + 1);
305 continue;
306 }};
307 };
308 if let Some((ref name, ref mut f)) = becoming_feature {
309 if f.tracking_issue.is_none() {
310 f.tracking_issue = find_attr_val(line, "issue")
311 .map(|s| s.parse().unwrap());
312 }
313 if line.ends_with(']') {
314 mf(Ok((name, f.clone())), file, i + 1);
315 } else if !line.ends_with(',') && !line.ends_with('\\') {
316 // We need to bail here because we might have missed the
317 // end of a stability attribute above because the ']'
318 // might not have been at the end of the line.
319 // We could then get into the very unfortunate situation that
320 // we continue parsing the file assuming the current stability
321 // attribute has not ended, and ignoring possible feature
322 // attributes in the process.
323 err!("malformed stability attribute");
324 } else {
325 continue;
326 }
327 }
328 becoming_feature = None;
329 if line.contains("rustc_const_unstable(") {
330 // `const fn` features are handled specially.
331 let feature_name = match find_attr_val(line, "feature") {
332 Some(name) => name,
333 None => err!("malformed stability attribute"),
334 };
335 let feature = Feature {
336 level: Status::Unstable,
337 since: "None".to_owned(),
338 has_gate_test: false,
339 // FIXME(#57563): #57563 is now used as a common tracking issue,
340 // although we would like to have specific tracking issues for each
341 // `rustc_const_unstable` in the future.
342 tracking_issue: Some(57563),
343 };
344 mf(Ok((feature_name, feature)), file, i + 1);
345 continue;
346 }
347 let level = if line.contains("[unstable(") {
348 Status::Unstable
349 } else if line.contains("[stable(") {
350 Status::Stable
351 } else {
352 continue;
353 };
354 let feature_name = match find_attr_val(line, "feature") {
355 Some(name) => name,
356 None => err!("malformed stability attribute"),
357 };
358 let since = match find_attr_val(line, "since") {
359 Some(name) => name,
360 None if level == Status::Stable => {
361 err!("malformed stability attribute");
362 }
363 None => "None",
364 };
365 let tracking_issue = find_attr_val(line, "issue").map(|s| s.parse().unwrap());
366
367 let feature = Feature {
368 level,
369 since: since.to_owned(),
370 has_gate_test: false,
371 tracking_issue,
372 };
373 if line.contains(']') {
374 mf(Ok((feature_name, feature)), file, i + 1);
375 } else {
376 becoming_feature = Some((feature_name.to_owned(), feature));
377 }
378 }
379 });
380 }