1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Tidy check to ensure that unstable features are all in order
13 //! This check will ensure properties like:
15 //! * All stability attributes look reasonably well formed
16 //! * The set of library features is disjoint from the set of language features
17 //! * Library features have at most one stability level
18 //! * Library features have at most one `since` value
19 //! * All unstable lang features have tests to ensure they are actually unstable
21 use std::collections::HashMap;
24 use std::io::prelude::*;
27 #[derive(Debug, PartialEq, Clone)]
34 impl fmt::Display for Status {
35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36 let as_str = match *self {
37 Status::Stable => "stable",
38 Status::Unstable => "unstable",
39 Status::Removed => "removed",
41 fmt::Display::fmt(as_str, f)
45 #[derive(Debug, Clone)]
49 pub has_gate_test: bool,
50 pub tracking_issue: Option<u32>,
54 fn check_match(&self, other: &Feature)-> Result<(), Vec<&'static str>> {
55 let mut mismatches = Vec::new();
56 if self.level != other.level {
57 mismatches.push("stability level");
59 if self.level == Status::Stable || other.level == Status::Stable {
60 // As long as a feature is unstable, the since field tracks
61 // when the given part of the feature has been implemented.
62 // Mismatches are tolerable as features evolve and functionality
64 // Once a feature is stable, the since field tracks the first version
65 // it was part of the stable distribution, and mismatches are disallowed.
66 if self.since != other.since {
67 mismatches.push("since");
70 if self.tracking_issue != other.tracking_issue {
71 mismatches.push("tracking issue");
73 if mismatches.is_empty() {
81 pub type Features = HashMap<String, Feature>;
83 pub fn check(path: &Path, bad: &mut bool, quiet: bool) {
84 let mut features = collect_lang_features(path, bad);
85 assert!(!features.is_empty());
87 let lib_features = get_and_check_lib_features(path, bad, &features);
88 assert!(!lib_features.is_empty());
90 let mut contents = String::new();
92 super::walk_many(&[&path.join("test/ui-fulldeps"),
93 &path.join("test/ui"),
94 &path.join("test/compile-fail"),
95 &path.join("test/compile-fail-fulldeps"),
96 &path.join("test/parse-fail"),
97 &path.join("test/ui"),],
98 &mut |path| super::filter_dirs(path),
100 let filename = file.file_name().unwrap().to_string_lossy();
101 if !filename.ends_with(".rs") || filename == "features.rs" ||
102 filename == "diagnostic_list.rs" {
106 let filen_underscore = filename.replace("-","_").replace(".rs","");
107 let filename_is_gate_test = test_filen_gate(&filen_underscore, &mut features);
109 contents.truncate(0);
110 t!(t!(File::open(&file), &file).read_to_string(&mut contents));
112 for (i, line) in contents.lines().enumerate() {
113 let mut err = |msg: &str| {
114 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
117 let gate_test_str = "gate-test-";
119 if !line.contains(gate_test_str) {
123 let feature_name = match line.find(gate_test_str) {
125 &line[i+gate_test_str.len()..line[i+1..].find(' ').unwrap_or(line.len())]
129 match features.get_mut(feature_name) {
131 if filename_is_gate_test {
132 err(&format!("The file is already marked as gate test \
133 through its name, no need for a \
134 'gate-test-{}' comment",
137 f.has_gate_test = true;
140 err(&format!("gate-test test found referencing a nonexistent feature '{}'",
147 // Only check the number of lang features.
148 // Obligatory testing for library features is dumb.
149 let gate_untested = features.iter()
150 .filter(|&(_, f)| f.level == Status::Unstable)
151 .filter(|&(_, f)| !f.has_gate_test)
152 .collect::<Vec<_>>();
154 for &(name, _) in gate_untested.iter() {
155 println!("Expected a gate test for the feature '{}'.", name);
156 println!("Hint: create a failing test file named 'feature-gate-{}.rs'\
157 \n in the 'ui' test suite, with its failures due to\
158 \n missing usage of #![feature({})].", name, name);
159 println!("Hint: If you already have such a test and don't want to rename it,\
160 \n you can also add a // gate-test-{} line to the test file.",
164 if gate_untested.len() > 0 {
165 tidy_error!(bad, "Found {} features without a gate test.", gate_untested.len());
172 println!("* {} features", features.len());
176 let mut lines = Vec::new();
177 for (name, feature) in features.iter() {
178 lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
184 for (name, feature) in lib_features {
185 lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
194 println!("* {}", line);
198 fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
200 .and_then(|i| line[i..].find('"').map(|j| i + j + 1))
201 .and_then(|i| line[i..].find('"').map(|j| (i, i + j)))
202 .map(|(i, j)| &line[i..j])
205 fn test_filen_gate(filen_underscore: &str, features: &mut Features) -> bool {
206 if filen_underscore.starts_with("feature_gate") {
207 for (n, f) in features.iter_mut() {
208 if filen_underscore == format!("feature_gate_{}", n) {
209 f.has_gate_test = true;
217 pub fn collect_lang_features(base_src_path: &Path, bad: &mut bool) -> Features {
218 let mut contents = String::new();
219 let path = base_src_path.join("libsyntax/feature_gate.rs");
220 t!(t!(File::open(path)).read_to_string(&mut contents));
222 // we allow rustc-internal features to omit a tracking issue.
223 // these features must be marked with `// rustc internal` in its own group.
224 let mut next_feature_is_rustc_internal = false;
226 contents.lines().zip(1..)
227 .filter_map(|(line, line_number)| {
228 let line = line.trim();
229 if line.starts_with("// rustc internal") {
230 next_feature_is_rustc_internal = true;
232 } else if line.is_empty() {
233 next_feature_is_rustc_internal = false;
237 let mut parts = line.split(',');
238 let level = match parts.next().map(|l| l.trim().trim_left_matches('(')) {
239 Some("active") => Status::Unstable,
240 Some("removed") => Status::Removed,
241 Some("accepted") => Status::Stable,
244 let name = parts.next().unwrap().trim();
245 let since = parts.next().unwrap().trim().trim_matches('"');
246 let issue_str = parts.next().unwrap().trim();
247 let tracking_issue = if issue_str.starts_with("None") {
248 if level == Status::Unstable && !next_feature_is_rustc_internal {
252 "libsyntax/feature_gate.rs:{}: no tracking issue for feature {}",
259 next_feature_is_rustc_internal = false;
260 let s = issue_str.split('(').nth(1).unwrap().split(')').nth(0).unwrap();
261 Some(s.parse().unwrap())
263 Some((name.to_owned(),
266 since: since.to_owned(),
267 has_gate_test: false,
274 pub fn collect_lib_features(base_src_path: &Path) -> Features {
275 let mut lib_features = Features::new();
277 // This library feature is defined in the `compiler_builtins` crate, which
278 // has been moved out-of-tree. Now it can no longer be auto-discovered by
279 // `tidy`, because we need to filter out its (submodule) directory. Manually
280 // add it to the set of known library features so we can still generate docs.
281 lib_features.insert("compiler_builtins_lib".to_owned(), Feature {
282 level: Status::Unstable,
283 since: "".to_owned(),
284 has_gate_test: false,
285 tracking_issue: None,
288 map_lib_features(base_src_path,
291 Ok((name, feature)) => {
292 if lib_features.get(name).is_some() {
295 lib_features.insert(name.to_owned(), feature);
303 fn get_and_check_lib_features(base_src_path: &Path,
305 lang_features: &Features) -> Features {
306 let mut lib_features = Features::new();
307 map_lib_features(base_src_path,
308 &mut |res, file, line| {
311 let mut check_features = |f: &Feature, list: &Features, display: &str| {
312 if let Some(ref s) = list.get(name) {
313 if let Err(m) = (&f).check_match(s) {
315 "{}:{}: mismatches to {} in: {:?}",
323 check_features(&f, &lang_features, "corresponding lang feature");
324 check_features(&f, &lib_features, "previous");
325 lib_features.insert(name.to_owned(), f);
328 tidy_error!(bad, "{}:{}: {}", file.display(), line, msg);
336 fn map_lib_features(base_src_path: &Path,
337 mf: &mut dyn FnMut(Result<(&str, Feature), &str>, &Path, usize)) {
338 let mut contents = String::new();
339 super::walk(base_src_path,
340 &mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
342 let filename = file.file_name().unwrap().to_string_lossy();
343 if !filename.ends_with(".rs") || filename == "features.rs" ||
344 filename == "diagnostic_list.rs" {
348 contents.truncate(0);
349 t!(t!(File::open(&file), &file).read_to_string(&mut contents));
351 let mut becoming_feature: Option<(String, Feature)> = None;
352 for (i, line) in contents.lines().enumerate() {
355 mf(Err($msg), file, i + 1);
359 if let Some((ref name, ref mut f)) = becoming_feature {
360 if f.tracking_issue.is_none() {
361 f.tracking_issue = find_attr_val(line, "issue")
362 .map(|s| s.parse().unwrap());
364 if line.ends_with("]") {
365 mf(Ok((name, f.clone())), file, i + 1);
366 } else if !line.ends_with(",") && !line.ends_with("\\") {
367 // We need to bail here because we might have missed the
368 // end of a stability attribute above because the "]"
369 // might not have been at the end of the line.
370 // We could then get into the very unfortunate situation that
371 // we continue parsing the file assuming the current stability
372 // attribute has not ended, and ignoring possible feature
373 // attributes in the process.
374 err!("malformed stability attribute");
379 becoming_feature = None;
380 if line.contains("rustc_const_unstable(") {
381 // const fn features are handled specially
382 let feature_name = match find_attr_val(line, "feature") {
384 None => err!("malformed stability attribute"),
386 let feature = Feature {
387 level: Status::Unstable,
388 since: "None".to_owned(),
389 has_gate_test: false,
390 // Whether there is a common tracking issue
391 // for these feature gates remains an open question
392 // https://github.com/rust-lang/rust/issues/24111#issuecomment-340283184
393 // But we take 24111 otherwise they will be shown as
394 // "internal to the compiler" which they are not.
395 tracking_issue: Some(24111),
397 mf(Ok((feature_name, feature)), file, i + 1);
400 let level = if line.contains("[unstable(") {
402 } else if line.contains("[stable(") {
407 let feature_name = match find_attr_val(line, "feature") {
409 None => err!("malformed stability attribute"),
411 let since = match find_attr_val(line, "since") {
413 None if level == Status::Stable => {
414 err!("malformed stability attribute");
418 let tracking_issue = find_attr_val(line, "issue").map(|s| s.parse().unwrap());
420 let feature = Feature {
422 since: since.to_owned(),
423 has_gate_test: false,
426 if line.contains("]") {
427 mf(Ok((feature_name, feature)), file, i + 1);
429 becoming_feature = Some((feature_name.to_owned(), feature));