Skip to content

Commit ef283b0

Browse files
committed
Add a lint for library features
Does a sanity check of the version numbers.
1 parent b5a452e commit ef283b0

File tree

4 files changed

+335
-67
lines changed

4 files changed

+335
-67
lines changed

mk/tests.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ tidy:
300300
| grep '^$(S)src/libbacktrace' -v \
301301
| grep '^$(S)src/rust-installer' -v \
302302
| xargs $(CFG_PYTHON) $(S)src/etc/check-binaries.py
303+
$(CFG_PYTHON) $(S)src/etc/featureck.py $(S)src/
303304

304305

305306
endif

src/etc/featureck.py

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
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, os, re
22+
23+
src_dir = sys.argv[1]
24+
25+
# Features that are allowed to exist in both the language and the library
26+
joint_features = [ "on_unimpleented" ]
27+
28+
# Grab the list of language features from the compiler
29+
language_gate_statuses = [ "Active", "Deprecated", "Removed", "Accepted" ]
30+
feature_gate_source = os.path.join(src_dir, "libsyntax", "feature_gate.rs")
31+
language_features = []
32+
language_feature_names = []
33+
with open(feature_gate_source, 'r') as f:
34+
for line in f:
35+
original_line = line
36+
line = line.strip()
37+
is_feature_line = False
38+
for status in language_gate_statuses:
39+
if status in line and line.startswith("("):
40+
is_feature_line = True
41+
42+
if is_feature_line:
43+
line = line.replace("(", "").replace("),", "").replace(")", "")
44+
parts = line.split(",")
45+
if len(parts) != 3:
46+
print "unexpected number of components in line: " + original_line
47+
sys.exit(1)
48+
feature_name = parts[0].strip().replace('"', "")
49+
since = parts[1].strip().replace('"', "")
50+
status = parts[2].strip()
51+
assert len(feature_name) > 0
52+
assert len(since) > 0
53+
assert len(status) > 0
54+
55+
language_feature_names += [feature_name]
56+
language_features += [(feature_name, since, status)]
57+
58+
assert len(language_features) > 0
59+
60+
errors = False
61+
62+
lib_features = { }
63+
lib_features_and_level = { }
64+
for (dirpath, dirnames, filenames) in os.walk(src_dir):
65+
# Don't look for feature names in tests
66+
if "src/test" in dirpath:
67+
continue
68+
69+
# Takes a long time to traverse LLVM
70+
if "src/llvm" in dirpath:
71+
continue
72+
73+
for filename in filenames:
74+
if not filename.endswith(".rs"):
75+
continue
76+
77+
path = os.path.join(dirpath, filename)
78+
with open(path, 'r') as f:
79+
line_num = 0
80+
for line in f:
81+
line_num += 1
82+
level = None
83+
if "[unstable(" in line:
84+
level = "unstable"
85+
elif "[stable(" in line:
86+
level = "stable"
87+
elif "[deprecated(" in line:
88+
level = "deprecated"
89+
else:
90+
continue
91+
92+
# This is a stability attribute. For the purposes of this
93+
# script we expect both the 'feature' and 'since' attributes on
94+
# the same line, e.g.
95+
# `#[unstable(feature = "foo", since = "1.0.0")]`
96+
97+
p = re.compile('feature *= *"(\w*)".*since *= *"([\w\.]*)"')
98+
m = p.search(line)
99+
if not m is None:
100+
feature_name = m.group(1)
101+
since = m.group(2)
102+
lib_features[feature_name] = feature_name
103+
if lib_features_and_level.get((feature_name, level)) is None:
104+
# Add it to the observed features
105+
lib_features_and_level[(feature_name, level)] = (since, path, line_num, line)
106+
else:
107+
# Verify that for this combination of feature_name and level the 'since'
108+
# attribute matches.
109+
(expected_since, source_path, source_line_num, source_line) = \
110+
lib_features_and_level.get((feature_name, level))
111+
if since != expected_since:
112+
print "mismatch in " + level + " feature '" + feature_name + "'"
113+
print "line " + str(source_line_num) + " of " + source_path + ":"
114+
print source_line
115+
print "line " + str(line_num) + " of " + path + ":"
116+
print line
117+
errors = True
118+
119+
# Verify that this lib feature doesn't duplicate a lang feature
120+
if feature_name in language_feature_names:
121+
print "lib feature '" + feature_name + "' duplicates a lang feature"
122+
print "line " + str(line_num) + " of " + path + ":"
123+
print line
124+
errors = True
125+
126+
else:
127+
print "misformed stability attribute"
128+
print "line " + str(line_num) + " of " + path + ":"
129+
print line
130+
errors = True
131+
132+
# Merge data about both lists
133+
# name, lang, lib, status, stable since, partially deprecated
134+
135+
language_feature_stats = {}
136+
137+
for f in language_features:
138+
name = f[0]
139+
lang = True
140+
lib = False
141+
status = "unstable"
142+
stable_since = None
143+
partially_deprecated = False
144+
145+
if f[2] == "Accepted":
146+
status = "stable"
147+
if status == "stable":
148+
stable_since = f[1]
149+
150+
language_feature_stats[name] = (name, lang, lib, status, stable_since, \
151+
partially_deprecated)
152+
153+
lib_feature_stats = {}
154+
155+
for f in lib_features:
156+
name = f
157+
lang = False
158+
lib = True
159+
status = "unstable"
160+
stable_since = None
161+
partially_deprecated = False
162+
163+
is_stable = lib_features_and_level.get((name, "stable")) is not None
164+
is_unstable = lib_features_and_level.get((name, "unstable")) is not None
165+
is_deprecated = lib_features_and_level.get((name, "deprecated")) is not None
166+
167+
if is_stable and is_unstable:
168+
print "feature '" + name + "' is both stable and unstable"
169+
errors = True
170+
171+
if is_stable:
172+
status = "stable"
173+
stable_since = lib_features_and_level[(name, "stable")][0]
174+
elif is_unstable:
175+
status = "unstable"
176+
stable_since = lib_features_and_level[(name, "unstable")][0]
177+
elif is_deprecated:
178+
status = "deprecated"
179+
180+
if (is_stable or is_unstable) and is_deprecated:
181+
partially_deprecated = True
182+
183+
lib_feature_stats[name] = (name, lang, lib, status, stable_since, \
184+
partially_deprecated)
185+
186+
# Check for overlap in two sets
187+
merged_stats = { }
188+
189+
for name in lib_feature_stats:
190+
if language_feature_stats.get(name) is not None:
191+
if not name in joint_features:
192+
print "feature '" + name + "' is both a lang and lib feature but not whitelisted"
193+
errors = True
194+
lang_status = lang_feature_stats[name][3]
195+
lib_status = lib_feature_stats[name][3]
196+
lang_stable_since = lang_feature_stats[name][4]
197+
lib_stable_since = lib_feature_stats[name][4]
198+
lang_partially_deprecated = lang_feature_stats[name][5]
199+
lib_partially_deprecated = lib_feature_stats[name][5]
200+
201+
if lang_status != lib_status and lib_status != "deprecated":
202+
print "feature '" + name + "' has lang status " + lang_status + \
203+
" but lib status " + lib_status
204+
errors = True
205+
206+
partially_deprecated = lang_partially_deprecated or lib_partially_deprecated
207+
if lib_status == "deprecated" and lang_status != "deprecated":
208+
partially_deprecated = True
209+
210+
if lang_stable_since != lib_stable_since:
211+
print "feature '" + name + "' has lang stable since " + lang_stable_since + \
212+
" but lib stable since " + lib_stable_since
213+
errors = True
214+
215+
merged_stats[name] = (name, True, True, lang_status, lang_stable_since, \
216+
partially_deprecated)
217+
218+
del language_feature_stats[name]
219+
del lib_feature_stats[name]
220+
221+
if errors:
222+
sys.exit(1)
223+
224+
# Finally, display the stats
225+
stats = {}
226+
stats.update(language_feature_stats)
227+
stats.update(lib_feature_stats)
228+
stats.update(merged_stats)
229+
lines = []
230+
for s in stats:
231+
s = stats[s]
232+
type_ = "lang"
233+
if s[1] and s[2]:
234+
type_ = "lang/lib"
235+
elif s[2]:
236+
type_ = "lib"
237+
line = s[0] + ",\t\t\t" + type_ + ",\t" + s[3] + ",\t" + str(s[4])
238+
line = "{: <32}".format(s[0]) + \
239+
"{: <8}".format(type_) + \
240+
"{: <12}".format(s[3]) + \
241+
"{: <8}".format(str(s[4]))
242+
if s[5]:
243+
line += "(partially deprecated)"
244+
lines += [line]
245+
246+
lines.sort()
247+
248+
print
249+
print "Rust feature summary:"
250+
print
251+
for line in lines:
252+
print line
253+
print
254+

src/librustc/middle/stability.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use syntax::ast::{TypeMethod, Method, Generics, StructField, TypeTraitItem};
2424
use syntax::ast_util::is_local;
2525
use syntax::attr::{Stability, AttrMetaMethods};
2626
use syntax::visit::{FnKind, FkMethod, Visitor};
27-
use syntax::feature_gate::emit_feature_err;
27+
use syntax::feature_gate::emit_feature_warn;
2828
use util::nodemap::{NodeMap, DefIdMap, FnvHashSet};
2929
use util::ppaux::Repr;
3030

@@ -221,8 +221,8 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
221221
None => format!("use of unstable library feature '{}'", feature.get())
222222
};
223223

224-
emit_feature_err(&self.tcx.sess.parse_sess.span_diagnostic,
225-
feature.get(), span, &msg[]);
224+
emit_feature_warn(&self.tcx.sess.parse_sess.span_diagnostic,
225+
feature.get(), span, &msg[]);
226226
}
227227
}
228228
Some(..) => {

0 commit comments

Comments
 (0)