]> git.lizzy.rs Git - rust.git/blob - src/etc/featureck.py
Auto merge of #29325 - alexcrichton:revert-trait-accessibility, r=nrc
[rust.git] / src / etc / featureck.py
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 # 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
20
21 import sys
22 import os
23 import re
24 import codecs
25
26 if len(sys.argv) < 2:
27     print("usage: featureck.py <src-dir>")
28     sys.exit(1)
29
30 src_dir = sys.argv[1]
31
32 # Features that are allowed to exist in both the language and the library
33 joint_features = [ ]
34
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:
41     for line in f:
42         original_line = line
43         line = line.strip()
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
48
49         if is_feature_line:
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(",")
54             if len(parts) != 4:
55                 print("error: unexpected number of components in line: " + original_line)
56                 sys.exit(1)
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
62             assert len(since) > 0
63             assert len(issue) > 0
64             assert len(status) > 0
65
66             language_feature_names += [feature_name]
67             language_features += [(feature_name, since, issue, status)]
68
69 assert len(language_features) > 0
70
71 errors = False
72
73 lib_features = { }
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:
78         continue
79
80     # Takes a long time to traverse LLVM
81     if "src/llvm" in dirpath:
82         continue
83
84     for filename in filenames:
85         if not filename.endswith(".rs"):
86             continue
87
88         path = os.path.join(dirpath, filename)
89         with codecs.open(filename=path, mode='r', encoding="utf-8") as f:
90             line_num = 0
91             for line in f:
92                 line_num += 1
93                 level = None
94                 if "[unstable(" in line:
95                     level = "unstable"
96                 elif "[stable(" in line:
97                     level = "stable"
98                 else:
99                     continue
100
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")]`
105
106                 p = re.compile('(unstable|stable).*feature *= *"(\w*)"')
107                 m = p.search(line)
108                 if not m is None:
109                     feature_name = m.group(2)
110                     since = None
111                     if re.compile("\[ *stable").search(line) is not None:
112                         pp = re.compile('since *= *"([\w\.]*)"')
113                         mm = pp.search(line)
114                         if not mm is None:
115                             since = mm.group(1)
116                         else:
117                             print("error: misformed stability attribute")
118                             print("line %d of %:" % (line_num, path))
119                             print(line)
120                             errors = True
121
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)
127                     else:
128                         # Verify that for this combination of feature_name and level the 'since'
129                         # attribute matches.
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))
135                             print(source_line)
136                             print("line %d of %s:" % (line_num, path))
137                             print(line)
138                             errors = True
139
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))
144                         print(line)
145                         errors = True
146
147                 else:
148                     print("error: misformed stability attribute")
149                     print("line %d of %s:" % (line_num, path))
150                     print(line)
151                     errors = True
152
153 # Merge data about both lists
154 # name, lang, lib, status, stable since
155
156 language_feature_stats = {}
157
158 for f in language_features:
159     name = f[0]
160     lang = True
161     lib = False
162     status = "unstable"
163     stable_since = None
164
165     if f[3] == "Accepted":
166         status = "stable"
167     if status == "stable":
168         stable_since = f[1]
169
170     language_feature_stats[name] = (name, lang, lib, status, stable_since)
171
172 lib_feature_stats = {}
173
174 for f in lib_features:
175     name = f
176     lang = False
177     lib = True
178     status = "unstable"
179     stable_since = None
180
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
183
184     if is_stable and is_unstable:
185         print("error: feature '%s' is both stable and unstable" % (name))
186         errors = True
187
188     if is_stable:
189         status = "stable"
190         stable_since = lib_features_and_level[(name, "stable")][0]
191     elif is_unstable:
192         status = "unstable"
193
194     lib_feature_stats[name] = (name, lang, lib, status, stable_since)
195
196 # Check for overlap in two sets
197 merged_stats = { }
198
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))
203             errors = True
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]
208
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))
212             errors = True
213
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))
217             errors = True
218
219         merged_stats[name] = (name, True, True, lang_status, lang_stable_since)
220
221         del language_feature_stats[name]
222         del lib_feature_stats[name]
223
224 if errors:
225     sys.exit(1)
226
227 # Finally, display the stats
228 stats = {}
229 stats.update(language_feature_stats)
230 stats.update(lib_feature_stats)
231 stats.update(merged_stats)
232 lines = []
233 for s in stats:
234     s = stats[s]
235     type_ = "lang"
236     if s[1] and s[2]:
237         type_ = "lang/lib"
238     elif s[2]:
239         type_ = "lib"
240     line = "{: <32}".format(s[0]) + \
241            "{: <8}".format(type_) + \
242            "{: <12}".format(s[3]) + \
243            "{: <8}".format(str(s[4]))
244     lines += [line]
245
246 lines.sort()
247
248 print
249 for line in lines:
250     print("* " + line)
251 print