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 # This script does a tree-wide sanity checks against stability
12 # attributes, currently:
13 # * For all feature_name/level pairs the 'since' field is the same
14 # * That no features are both stable and unstable.
15 # * That lib features don't have the same name as lang features
16 # unless they are on the 'joint_features' whitelist
17 # * That features that exist in both lang and lib and are stable
18 # since the same version
19 # * Prints information about features
27 print("usage: featureck.py <src-dir>")
32 # Features that are allowed to exist in both the language and the library
35 # Grab the list of language features from the compiler
36 language_gate_statuses = [ "Active", "Deprecated", "Removed", "Accepted" ]
37 feature_gate_source = os.path.join(src_dir, "libsyntax", "feature_gate.rs")
38 language_features = []
39 language_feature_names = []
40 with open(feature_gate_source, 'r') as f:
44 is_feature_line = False
45 for status in language_gate_statuses:
46 if status in line and line.startswith("("):
47 is_feature_line = True
50 # turn ` ("foo", "1.0.0", Some(10), Active)` into
51 # `"foo", "1.0.0", Some(10), Active`
52 line = line.strip(' ,()')
53 parts = line.split(",")
55 print("error: unexpected number of components in line: " + original_line)
57 feature_name = parts[0].strip().replace('"', "")
58 since = parts[1].strip().replace('"', "")
59 issue = parts[2].strip()
60 status = parts[3].strip()
61 assert len(feature_name) > 0
64 assert len(status) > 0
66 language_feature_names += [feature_name]
67 language_features += [(feature_name, since, issue, status)]
69 assert len(language_features) > 0
74 lib_features_and_level = { }
75 for (dirpath, dirnames, filenames) in os.walk(src_dir):
76 # Don't look for feature names in tests
77 if "src/test" in dirpath:
80 # Takes a long time to traverse LLVM
81 if "src/llvm" in dirpath:
84 for filename in filenames:
85 if not filename.endswith(".rs"):
88 path = os.path.join(dirpath, filename)
89 with codecs.open(filename=path, mode='r', encoding="utf-8") as f:
94 if "[unstable(" in line:
96 elif "[stable(" in line:
101 # This is a stability attribute. For the purposes of this
102 # script we expect both the 'feature' and 'since' attributes on
103 # the same line, e.g.
104 # `#[unstable(feature = "foo", since = "1.0.0")]`
106 p = re.compile('(unstable|stable).*feature *= *"(\w*)"')
109 feature_name = m.group(2)
111 if re.compile("\[ *stable").search(line) is not None:
112 pp = re.compile('since *= *"([\w\.]*)"')
117 print("error: misformed stability attribute")
118 print("line %d of %:" % (line_num, path))
122 lib_features[feature_name] = feature_name
123 if lib_features_and_level.get((feature_name, level)) is None:
124 # Add it to the observed features
125 lib_features_and_level[(feature_name, level)] = \
126 (since, path, line_num, line)
128 # Verify that for this combination of feature_name and level the 'since'
130 (expected_since, source_path, source_line_num, source_line) = \
131 lib_features_and_level.get((feature_name, level))
132 if since != expected_since:
133 print("error: mismatch in %s feature '%s'" % (level, feature_name))
134 print("line %d of %s:" % (source_line_num, source_path))
136 print("line %d of %s:" % (line_num, path))
140 # Verify that this lib feature doesn't duplicate a lang feature
141 if feature_name in language_feature_names:
142 print("error: lib feature '%s' duplicates a lang feature" % (feature_name))
143 print("line %d of %s:" % (line_num, path))
148 print("error: misformed stability attribute")
149 print("line %d of %s:" % (line_num, path))
153 # Merge data about both lists
154 # name, lang, lib, status, stable since
156 language_feature_stats = {}
158 for f in language_features:
165 if f[3] == "Accepted":
167 if status == "stable":
170 language_feature_stats[name] = (name, lang, lib, status, stable_since)
172 lib_feature_stats = {}
174 for f in lib_features:
181 is_stable = lib_features_and_level.get((name, "stable")) is not None
182 is_unstable = lib_features_and_level.get((name, "unstable")) is not None
184 if is_stable and is_unstable:
185 print("error: feature '%s' is both stable and unstable" % (name))
190 stable_since = lib_features_and_level[(name, "stable")][0]
194 lib_feature_stats[name] = (name, lang, lib, status, stable_since)
196 # Check for overlap in two sets
199 for name in lib_feature_stats:
200 if language_feature_stats.get(name) is not None:
201 if not name in joint_features:
202 print("error: feature '%s' is both a lang and lib feature but not whitelisted" % (name))
204 lang_status = language_feature_stats[name][3]
205 lib_status = lib_feature_stats[name][3]
206 lang_stable_since = language_feature_stats[name][4]
207 lib_stable_since = lib_feature_stats[name][4]
209 if lang_status != lib_status and lib_status != "deprecated":
210 print("error: feature '%s' has lang status %s " +
211 "but lib status %s" % (name, lang_status, lib_status))
214 if lang_stable_since != lib_stable_since:
215 print("error: feature '%s' has lang stable since %s " +
216 "but lib stable since %s" % (name, lang_stable_since, lib_stable_since))
219 merged_stats[name] = (name, True, True, lang_status, lang_stable_since)
221 del language_feature_stats[name]
222 del lib_feature_stats[name]
227 # Finally, display the stats
229 stats.update(language_feature_stats)
230 stats.update(lib_feature_stats)
231 stats.update(merged_stats)
240 line = "{: <32}".format(s[0]) + \
241 "{: <8}".format(type_) + \
242 "{: <12}".format(s[3]) + \
243 "{: <8}".format(str(s[4]))