]> git.lizzy.rs Git - rust.git/blob - src/etc/featureck.py
Auto merge of #27856 - nikomatsakis:move-def-id-to-rustc, r=eddyb
[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             line = line.replace("(", "").replace("),", "").replace(")", "")
51             parts = line.split(",")
52             if len(parts) != 3:
53                 print("error: unexpected number of components in line: " + original_line)
54                 sys.exit(1)
55             feature_name = parts[0].strip().replace('"', "")
56             since = parts[1].strip().replace('"', "")
57             status = parts[2].strip()
58             assert len(feature_name) > 0
59             assert len(since) > 0
60             assert len(status) > 0
61
62             language_feature_names += [feature_name]
63             language_features += [(feature_name, since, status)]
64
65 assert len(language_features) > 0
66
67 errors = False
68
69 lib_features = { }
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:
74         continue
75
76     # Takes a long time to traverse LLVM
77     if "src/llvm" in dirpath:
78         continue
79
80     for filename in filenames:
81         if not filename.endswith(".rs"):
82             continue
83
84         path = os.path.join(dirpath, filename)
85         with codecs.open(filename=path, mode='r', encoding="utf-8") as f:
86             line_num = 0
87             for line in f:
88                 line_num += 1
89                 level = None
90                 if "[unstable(" in line:
91                     level = "unstable"
92                 elif "[stable(" in line:
93                     level = "stable"
94                 else:
95                     continue
96
97                 # This is a stability attribute. For the purposes of this
98                 # script we expect both the 'feature' and 'since' attributes on
99                 # the same line, e.g.
100                 # `#[unstable(feature = "foo", since = "1.0.0")]`
101
102                 p = re.compile('(unstable|stable).*feature *= *"(\w*)"')
103                 m = p.search(line)
104                 if not m is None:
105                     feature_name = m.group(2)
106                     since = None
107                     if re.compile("\[ *stable").search(line) is not None:
108                         pp = re.compile('since *= *"([\w\.]*)"')
109                         mm = pp.search(line)
110                         if not mm is None:
111                             since = mm.group(1)
112                         else:
113                             print("error: misformed stability attribute")
114                             print("line %d of %:" % (line_num, path))
115                             print(line)
116                             errors = True
117
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)
123                     else:
124                         # Verify that for this combination of feature_name and level the 'since'
125                         # attribute matches.
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))
131                             print(source_line)
132                             print("line %d of %s:" % (line_num, path))
133                             print(line)
134                             errors = True
135
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))
140                         print(line)
141                         errors = True
142
143                 else:
144                     print("error: misformed stability attribute")
145                     print("line %d of %s:" % (line_num, path))
146                     print(line)
147                     errors = True
148
149 # Merge data about both lists
150 # name, lang, lib, status, stable since
151
152 language_feature_stats = {}
153
154 for f in language_features:
155     name = f[0]
156     lang = True
157     lib = False
158     status = "unstable"
159     stable_since = None
160
161     if f[2] == "Accepted":
162         status = "stable"
163     if status == "stable":
164         stable_since = f[1]
165
166     language_feature_stats[name] = (name, lang, lib, status, stable_since)
167
168 lib_feature_stats = {}
169
170 for f in lib_features:
171     name = f
172     lang = False
173     lib = True
174     status = "unstable"
175     stable_since = None
176
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
179
180     if is_stable and is_unstable:
181         print("error: feature '%s' is both stable and unstable" % (name))
182         errors = True
183
184     if is_stable:
185         status = "stable"
186         stable_since = lib_features_and_level[(name, "stable")][0]
187     elif is_unstable:
188         status = "unstable"
189
190     lib_feature_stats[name] = (name, lang, lib, status, stable_since)
191
192 # Check for overlap in two sets
193 merged_stats = { }
194
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))
199             errors = True
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]
204
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))
208             errors = True
209
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))
213             errors = True
214
215         merged_stats[name] = (name, True, True, lang_status, lang_stable_since)
216
217         del language_feature_stats[name]
218         del lib_feature_stats[name]
219
220 if errors:
221     sys.exit(1)
222
223 # Finally, display the stats
224 stats = {}
225 stats.update(language_feature_stats)
226 stats.update(lib_feature_stats)
227 stats.update(merged_stats)
228 lines = []
229 for s in stats:
230     s = stats[s]
231     type_ = "lang"
232     if s[1] and s[2]:
233         type_ = "lang/lib"
234     elif s[2]:
235         type_ = "lib"
236     line = "{: <32}".format(s[0]) + \
237            "{: <8}".format(type_) + \
238            "{: <12}".format(s[3]) + \
239            "{: <8}".format(str(s[4]))
240     lines += [line]
241
242 lines.sort()
243
244 print
245 for line in lines:
246     print("* " + line)
247 print