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 line = line.replace("(", "").replace("),", "").replace(")", "")
51 parts = line.split(",")
53 print("error: unexpected number of components in line: " + original_line)
55 feature_name = parts[0].strip().replace('"', "")
56 since = parts[1].strip().replace('"', "")
57 status = parts[2].strip()
58 assert len(feature_name) > 0
60 assert len(status) > 0
62 language_feature_names += [feature_name]
63 language_features += [(feature_name, since, status)]
65 assert len(language_features) > 0
70 lib_features_and_level = { }
71 for (dirpath, dirnames, filenames) in os.walk(src_dir):
72 # Don't look for feature names in tests
73 if "src/test" in dirpath:
76 # Takes a long time to traverse LLVM
77 if "src/llvm" in dirpath:
80 for filename in filenames:
81 if not filename.endswith(".rs"):
84 path = os.path.join(dirpath, filename)
85 with codecs.open(filename=path, mode='r', encoding="utf-8") as f:
90 if "[unstable(" in line:
92 elif "[stable(" in line:
97 # This is a stability attribute. For the purposes of this
98 # script we expect both the 'feature' and 'since' attributes on
100 # `#[unstable(feature = "foo", since = "1.0.0")]`
102 p = re.compile('(unstable|stable).*feature *= *"(\w*)"')
105 feature_name = m.group(2)
107 if re.compile("\[ *stable").search(line) is not None:
108 pp = re.compile('since *= *"([\w\.]*)"')
113 print("error: misformed stability attribute")
114 print("line %d of %:" % (line_num, path))
118 lib_features[feature_name] = feature_name
119 if lib_features_and_level.get((feature_name, level)) is None:
120 # Add it to the observed features
121 lib_features_and_level[(feature_name, level)] = \
122 (since, path, line_num, line)
124 # Verify that for this combination of feature_name and level the 'since'
126 (expected_since, source_path, source_line_num, source_line) = \
127 lib_features_and_level.get((feature_name, level))
128 if since != expected_since:
129 print("error: mismatch in %s feature '%s'" % (level, feature_name))
130 print("line %d of %s:" % (source_line_num, source_path))
132 print("line %d of %s:" % (line_num, path))
136 # Verify that this lib feature doesn't duplicate a lang feature
137 if feature_name in language_feature_names:
138 print("error: lib feature '%s' duplicates a lang feature" % (feature_name))
139 print("line %d of %s:" % (line_num, path))
144 print("error: misformed stability attribute")
145 print("line %d of %s:" % (line_num, path))
149 # Merge data about both lists
150 # name, lang, lib, status, stable since
152 language_feature_stats = {}
154 for f in language_features:
161 if f[2] == "Accepted":
163 if status == "stable":
166 language_feature_stats[name] = (name, lang, lib, status, stable_since)
168 lib_feature_stats = {}
170 for f in lib_features:
177 is_stable = lib_features_and_level.get((name, "stable")) is not None
178 is_unstable = lib_features_and_level.get((name, "unstable")) is not None
180 if is_stable and is_unstable:
181 print("error: feature '%s' is both stable and unstable" % (name))
186 stable_since = lib_features_and_level[(name, "stable")][0]
190 lib_feature_stats[name] = (name, lang, lib, status, stable_since)
192 # Check for overlap in two sets
195 for name in lib_feature_stats:
196 if language_feature_stats.get(name) is not None:
197 if not name in joint_features:
198 print("error: feature '%s' is both a lang and lib feature but not whitelisted" % (name))
200 lang_status = language_feature_stats[name][3]
201 lib_status = lib_feature_stats[name][3]
202 lang_stable_since = language_feature_stats[name][4]
203 lib_stable_since = lib_feature_stats[name][4]
205 if lang_status != lib_status and lib_status != "deprecated":
206 print("error: feature '%s' has lang status %s " +
207 "but lib status %s" % (name, lang_status, lib_status))
210 if lang_stable_since != lib_stable_since:
211 print("error: feature '%s' has lang stable since %s " +
212 "but lib stable since %s" % (name, lang_stable_since, lib_stable_since))
215 merged_stats[name] = (name, True, True, lang_status, lang_stable_since)
217 del language_feature_stats[name]
218 del lib_feature_stats[name]
223 # Finally, display the stats
225 stats.update(language_feature_stats)
226 stats.update(lib_feature_stats)
227 stats.update(merged_stats)
236 line = "{: <32}".format(s[0]) + \
237 "{: <8}".format(type_) + \
238 "{: <12}".format(s[3]) + \
239 "{: <8}".format(str(s[4]))