]> git.proxmox.com Git - rustc.git/blame - src/tools/tidy/src/features.rs
New upstream version 1.19.0+dfsg1
[rustc.git] / src / tools / tidy / src / features.rs
CommitLineData
a7813a04
XL
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.
4//
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.
10
11//! Tidy check to ensure that unstable features are all in order
12//!
13//! This check will ensure properties like:
14//!
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
32a655c1 19//! * All unstable lang features have tests to ensure they are actually unstable
a7813a04
XL
20
21use std::collections::HashMap;
c30ab7b3 22use std::fmt;
a7813a04
XL
23use std::fs::File;
24use std::io::prelude::*;
25use std::path::Path;
26
cc61c64b
XL
27#[derive(Debug, PartialEq)]
28pub enum Status {
c30ab7b3 29 Stable,
32a655c1 30 Removed,
c30ab7b3
SL
31 Unstable,
32}
33
34impl 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",
32a655c1 39 Status::Removed => "removed",
c30ab7b3
SL
40 };
41 fmt::Display::fmt(as_str, f)
42 }
43}
44
cc61c64b
XL
45#[derive(Debug)]
46pub struct Feature {
47 pub level: Status,
48 pub since: String,
49 pub has_gate_test: bool,
a7813a04
XL
50}
51
7cac9316 52pub fn check(path: &Path, bad: &mut bool, quiet: bool) {
cc61c64b 53 let mut features = collect_lang_features(path);
c30ab7b3 54 assert!(!features.is_empty());
a7813a04 55
cc61c64b
XL
56 let lib_features = collect_lib_features(path, bad, &features);
57 assert!(!lib_features.is_empty());
a7813a04 58
cc61c64b 59 let mut contents = String::new();
a7813a04 60
32a655c1 61 super::walk_many(&[&path.join("test/compile-fail"),
8bb4bdeb
XL
62 &path.join("test/compile-fail-fulldeps"),
63 &path.join("test/parse-fail"),],
32a655c1
SL
64 &mut |path| super::filter_dirs(path),
65 &mut |file| {
66 let filename = file.file_name().unwrap().to_string_lossy();
67 if !filename.ends_with(".rs") || filename == "features.rs" ||
68 filename == "diagnostic_list.rs" {
69 return;
70 }
71
72 let filen_underscore = filename.replace("-","_").replace(".rs","");
7cac9316 73 let filename_is_gate_test = test_filen_gate(&filen_underscore, &mut features);
32a655c1
SL
74
75 contents.truncate(0);
76 t!(t!(File::open(&file), &file).read_to_string(&mut contents));
77
78 for (i, line) in contents.lines().enumerate() {
79 let mut err = |msg: &str| {
cc61c64b 80 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
32a655c1
SL
81 };
82
83 let gate_test_str = "gate-test-";
84
85 if !line.contains(gate_test_str) {
86 continue;
87 }
88
89 let feature_name = match line.find(gate_test_str) {
90 Some(i) => {
91 &line[i+gate_test_str.len()..line[i+1..].find(' ').unwrap_or(line.len())]
92 },
93 None => continue,
94 };
7cac9316
XL
95 match features.get_mut(feature_name) {
96 Some(f) => {
97 if filename_is_gate_test {
98 err(&format!("The file is already marked as gate test \
99 through its name, no need for a \
100 'gate-test-{}' comment",
101 feature_name));
102 }
103 f.has_gate_test = true;
104 }
105 None => {
106 err(&format!("gate-test test found referencing a nonexistent feature '{}'",
107 feature_name));
108 }
32a655c1
SL
109 }
110 }
111 });
112
32a655c1
SL
113 // Only check the number of lang features.
114 // Obligatory testing for library features is dumb.
115 let gate_untested = features.iter()
116 .filter(|&(_, f)| f.level == Status::Unstable)
117 .filter(|&(_, f)| !f.has_gate_test)
32a655c1
SL
118 .collect::<Vec<_>>();
119
120 for &(name, _) in gate_untested.iter() {
121 println!("Expected a gate test for the feature '{}'.", name);
122 println!("Hint: create a file named 'feature-gate-{}.rs' in the compile-fail\
123 \n test suite, with its failures due to missing usage of\
124 \n #![feature({})].", name, name);
125 println!("Hint: If you already have such a test and don't want to rename it,\
126 \n you can also add a // gate-test-{} line to the test file.",
127 name);
128 }
129
130 if gate_untested.len() > 0 {
cc61c64b 131 tidy_error!(bad, "Found {} features without a gate test.", gate_untested.len());
32a655c1
SL
132 }
133
a7813a04 134 if *bad {
c30ab7b3 135 return;
a7813a04 136 }
7cac9316
XL
137 if quiet {
138 println!("* {} features", features.len());
139 return;
140 }
a7813a04
XL
141
142 let mut lines = Vec::new();
32a655c1 143 for (name, feature) in features.iter() {
a7813a04 144 lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
32a655c1 145 name,
c30ab7b3
SL
146 "lang",
147 feature.level,
148 feature.since));
a7813a04
XL
149 }
150 for (name, feature) in lib_features {
151 lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
c30ab7b3
SL
152 name,
153 "lib",
154 feature.level,
155 feature.since));
a7813a04
XL
156 }
157
158 lines.sort();
159 for line in lines {
160 println!("* {}", line);
161 }
162}
163
164fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
c30ab7b3
SL
165 line.find(attr)
166 .and_then(|i| line[i..].find('"').map(|j| i + j + 1))
167 .and_then(|i| line[i..].find('"').map(|j| (i, i + j)))
168 .map(|(i, j)| &line[i..j])
a7813a04
XL
169}
170
32a655c1
SL
171fn test_filen_gate(filen_underscore: &str,
172 features: &mut HashMap<String, Feature>) -> bool {
173 if filen_underscore.starts_with("feature_gate") {
174 for (n, f) in features.iter_mut() {
175 if filen_underscore == format!("feature_gate_{}", n) {
176 f.has_gate_test = true;
177 return true;
178 }
179 }
180 }
181 return false;
182}
183
cc61c64b 184pub fn collect_lang_features(base_src_path: &Path) -> HashMap<String, Feature> {
a7813a04 185 let mut contents = String::new();
cc61c64b 186 let path = base_src_path.join("libsyntax/feature_gate.rs");
a7813a04
XL
187 t!(t!(File::open(path)).read_to_string(&mut contents));
188
c30ab7b3
SL
189 contents.lines()
190 .filter_map(|line| {
191 let mut parts = line.trim().split(",");
192 let level = match parts.next().map(|l| l.trim().trim_left_matches('(')) {
193 Some("active") => Status::Unstable,
32a655c1 194 Some("removed") => Status::Removed,
c30ab7b3
SL
195 Some("accepted") => Status::Stable,
196 _ => return None,
197 };
198 let name = parts.next().unwrap().trim();
199 let since = parts.next().unwrap().trim().trim_matches('"');
32a655c1
SL
200 Some((name.to_owned(),
201 Feature {
202 level: level,
203 since: since.to_owned(),
204 has_gate_test: false,
205 }))
c30ab7b3
SL
206 })
207 .collect()
a7813a04 208}
cc61c64b
XL
209
210pub fn collect_lib_features(base_src_path: &Path,
211 bad: &mut bool,
212 features: &HashMap<String, Feature>) -> HashMap<String, Feature> {
213 let mut lib_features = HashMap::<String, Feature>::new();
214 let mut contents = String::new();
215 super::walk(base_src_path,
216 &mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
217 &mut |file| {
218 let filename = file.file_name().unwrap().to_string_lossy();
219 if !filename.ends_with(".rs") || filename == "features.rs" ||
220 filename == "diagnostic_list.rs" {
221 return;
222 }
223
224 contents.truncate(0);
225 t!(t!(File::open(&file), &file).read_to_string(&mut contents));
226
227 for (i, line) in contents.lines().enumerate() {
228 let mut err = |msg: &str| {
229 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
230 };
231 let level = if line.contains("[unstable(") {
232 Status::Unstable
233 } else if line.contains("[stable(") {
234 Status::Stable
235 } else {
236 continue;
237 };
238 let feature_name = match find_attr_val(line, "feature") {
239 Some(name) => name,
240 None => {
241 err("malformed stability attribute");
242 continue;
243 }
244 };
245 let since = match find_attr_val(line, "since") {
246 Some(name) => name,
247 None if level == Status::Stable => {
248 err("malformed stability attribute");
249 continue;
250 }
251 None => "None",
252 };
253
254 if features.contains_key(feature_name) {
255 err("duplicating a lang feature");
256 }
257 if let Some(ref s) = lib_features.get(feature_name) {
258 if s.level != level {
259 err("different stability level than before");
260 }
261 if s.since != since {
262 err("different `since` than before");
263 }
264 continue;
265 }
266 lib_features.insert(feature_name.to_owned(),
267 Feature {
268 level: level,
269 since: since.to_owned(),
270 has_gate_test: false,
271 });
272 }
273 });
274 lib_features
7cac9316 275}