Skip to content

Commit a530cc9

Browse files
committed
Auto merge of #21248 - brson:feature-staging, r=alexcrichton
This implements the remaining bits of 'feature staging', as described in [RFC 507](https://github.com/rust-lang/rfcs/blob/master/text/0507-release-channels.md). This is not quite done, but the substance of the work is complete so submitting for early review. Key changes: * `unstable`, `stable` and `deprecated` attributes all require 'feature' and 'since', and support an optional 'reason'. * The `unstable` lint is removed. * A new 'stability checking' pass warns when a used unstable library feature has not been activated with the `feature` attribute. At 1.0 beta this will become an error. * A new 'unused feature checking' pass emits a lint ('unused_feature', renamed from 'unknown_feature') for any features that were activated but not used. * A new tidy script `featureck.py` performs some global sanity checking, particularly that 'since' numbers agree, and also prints out a summary of features. Differences from RFC: * As implemented `unstable` requires a `since` attribute. I do not know if this is useful. I included it in the original sed script and just left it. * RFC didn't specify the name of the optional 'reason' attribute. * This continues to use 'unstable', 'stable' and 'deprecated' names (the 'nice' names) instead of 'staged_unstable', but only activates them with the crate-level 'staged_api' attribute. I intend to update the RFC based on the outcome of this PR. Issues: * The unused feature check doesn't account for language features - i.e. you can activate a language feature, not use it, and not get the error. Open questions: * All unstable and deprecated features are named 'unnamed_feature', which i picked just because it is uniquely greppable. This is the 'catch-all' feature. What should it be? * All stable features are named 'grandfathered'. What should this be? TODO: * Add check that all `deprecated` attributes are paired with a `stable` attribute in order to preserve the knowledge about when a feature became stable. * Update rustdoc in various ways. * Remove obsolete stability discussion from reference. * Add features for 'path', 'io', 'os', 'hash' and 'rand'. cc #20445 @alexcrichton @aturon
2 parents 92ff8ea + 7122305 commit a530cc9

File tree

249 files changed

+4327
-3145
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

249 files changed

+4327
-3145
lines changed

mk/tests.mk

+1
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ tidy:
301301
| grep '^$(S)src/rust-installer' -v \
302302
| xargs $(CFG_PYTHON) $(S)src/etc/check-binaries.py
303303
$(Q) $(CFG_PYTHON) $(S)src/etc/errorck.py $(S)src/
304+
$(Q) $(CFG_PYTHON) $(S)src/etc/featureck.py $(S)src/
304305

305306

306307
endif

src/compiletest/compiletest.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@
1313
#![feature(slicing_syntax, unboxed_closures)]
1414
#![feature(box_syntax)]
1515
#![feature(int_uint)]
16-
#![allow(unstable)]
16+
#![feature(test)]
17+
#![feature(rustc_private)]
18+
#![feature(std_misc)]
19+
#![feature(path)]
20+
#![feature(io)]
21+
#![feature(core)]
22+
#![feature(collections)]
23+
#![feature(os)]
24+
#![feature(unicode)]
1725

1826
#![deny(warnings)]
1927

src/doc/reference.md

-71
Original file line numberDiff line numberDiff line change
@@ -2359,77 +2359,6 @@ Supported traits for `derive` are:
23592359
* `Show`, to format a value using the `{}` formatter.
23602360
* `Zero`, to create a zero instance of a numeric data type.
23612361

2362-
### Stability
2363-
2364-
One can indicate the stability of an API using the following attributes:
2365-
2366-
* `deprecated`: This item should no longer be used, e.g. it has been
2367-
replaced. No guarantee of backwards-compatibility.
2368-
* `experimental`: This item was only recently introduced or is
2369-
otherwise in a state of flux. It may change significantly, or even
2370-
be removed. No guarantee of backwards-compatibility.
2371-
* `unstable`: This item is still under development, but requires more
2372-
testing to be considered stable. No guarantee of backwards-compatibility.
2373-
* `stable`: This item is considered stable, and will not change
2374-
significantly. Guarantee of backwards-compatibility.
2375-
* `frozen`: This item is very stable, and is unlikely to
2376-
change. Guarantee of backwards-compatibility.
2377-
* `locked`: This item will never change unless a serious bug is
2378-
found. Guarantee of backwards-compatibility.
2379-
2380-
These levels are directly inspired by
2381-
[Node.js' "stability index"](http://nodejs.org/api/documentation.html).
2382-
2383-
Stability levels are inherited, so an item's stability attribute is the default
2384-
stability for everything nested underneath it.
2385-
2386-
There are lints for disallowing items marked with certain levels: `deprecated`,
2387-
`experimental` and `unstable`. For now, only `deprecated` warns by default, but
2388-
this will change once the standard library has been stabilized. Stability
2389-
levels are meant to be promises at the crate level, so these lints only apply
2390-
when referencing items from an _external_ crate, not to items defined within
2391-
the current crate. Items with no stability level are considered to be unstable
2392-
for the purposes of the lint. One can give an optional string that will be
2393-
displayed when the lint flags the use of an item.
2394-
2395-
For example, if we define one crate called `stability_levels`:
2396-
2397-
```{.ignore}
2398-
#[deprecated="replaced by `best`"]
2399-
pub fn bad() {
2400-
// delete everything
2401-
}
2402-
2403-
pub fn better() {
2404-
// delete fewer things
2405-
}
2406-
2407-
#[stable]
2408-
pub fn best() {
2409-
// delete nothing
2410-
}
2411-
```
2412-
2413-
then the lints will work as follows for a client crate:
2414-
2415-
```{.ignore}
2416-
#![warn(unstable)]
2417-
extern crate stability_levels;
2418-
use stability_levels::{bad, better, best};
2419-
2420-
fn main() {
2421-
bad(); // "warning: use of deprecated item: replaced by `best`"
2422-
2423-
better(); // "warning: use of unmarked item"
2424-
2425-
best(); // no warning
2426-
}
2427-
```
2428-
2429-
> **Note:** Currently these are only checked when applied to individual
2430-
> functions, structs, methods and enum variants, *not* to entire modules,
2431-
> traits, impls or enums themselves.
2432-
24332362
### Compiler Features
24342363

24352364
Certain aspects of Rust may be implemented in the compiler, but they're not

src/driver/driver.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
#![allow(unstable)]
11+
#![allow(unknown_features)]
12+
#![cfg_attr(rustc, feature(rustc_private))]
13+
#![cfg_attr(rustdoc, feature(rustdoc))]
1214

1315
#[cfg(rustdoc)]
1416
extern crate "rustdoc" as this;

src/etc/featureck.py

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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 = [ ]
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 "error: 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+
else:
88+
continue
89+
90+
# This is a stability attribute. For the purposes of this
91+
# script we expect both the 'feature' and 'since' attributes on
92+
# the same line, e.g.
93+
# `#[unstable(feature = "foo", since = "1.0.0")]`
94+
95+
p = re.compile('(unstable|stable).*feature *= *"(\w*)"')
96+
m = p.search(line)
97+
if not m is None:
98+
feature_name = m.group(2)
99+
since = None
100+
if re.compile("\[ *stable").search(line) is not None:
101+
pp = re.compile('since *= *"([\w\.]*)"')
102+
mm = pp.search(line)
103+
if not mm is None:
104+
since = mm.group(1)
105+
else:
106+
print "error: misformed stability attribute"
107+
print "line " + str(line_num) + " of " + path + ":"
108+
print line
109+
errors = True
110+
111+
lib_features[feature_name] = feature_name
112+
if lib_features_and_level.get((feature_name, level)) is None:
113+
# Add it to the observed features
114+
lib_features_and_level[(feature_name, level)] = \
115+
(since, path, line_num, line)
116+
else:
117+
# Verify that for this combination of feature_name and level the 'since'
118+
# attribute matches.
119+
(expected_since, source_path, source_line_num, source_line) = \
120+
lib_features_and_level.get((feature_name, level))
121+
if since != expected_since:
122+
print "error: mismatch in " + level + " feature '" + feature_name + "'"
123+
print "line " + str(source_line_num) + " of " + source_path + ":"
124+
print source_line
125+
print "line " + str(line_num) + " of " + path + ":"
126+
print line
127+
errors = True
128+
129+
# Verify that this lib feature doesn't duplicate a lang feature
130+
if feature_name in language_feature_names:
131+
print "error: lib feature '" + feature_name + "' duplicates a lang feature"
132+
print "line " + str(line_num) + " of " + path + ":"
133+
print line
134+
errors = True
135+
136+
else:
137+
print "error: misformed stability attribute"
138+
print "line " + str(line_num) + " of " + path + ":"
139+
print line
140+
errors = True
141+
142+
# Merge data about both lists
143+
# name, lang, lib, status, stable since
144+
145+
language_feature_stats = {}
146+
147+
for f in language_features:
148+
name = f[0]
149+
lang = True
150+
lib = False
151+
status = "unstable"
152+
stable_since = None
153+
154+
if f[2] == "Accepted":
155+
status = "stable"
156+
if status == "stable":
157+
stable_since = f[1]
158+
159+
language_feature_stats[name] = (name, lang, lib, status, stable_since)
160+
161+
lib_feature_stats = {}
162+
163+
for f in lib_features:
164+
name = f
165+
lang = False
166+
lib = True
167+
status = "unstable"
168+
stable_since = None
169+
170+
is_stable = lib_features_and_level.get((name, "stable")) is not None
171+
is_unstable = lib_features_and_level.get((name, "unstable")) is not None
172+
173+
if is_stable and is_unstable:
174+
print "error: feature '" + name + "' is both stable and unstable"
175+
errors = True
176+
177+
if is_stable:
178+
status = "stable"
179+
stable_since = lib_features_and_level[(name, "stable")][0]
180+
elif is_unstable:
181+
status = "unstable"
182+
183+
lib_feature_stats[name] = (name, lang, lib, status, stable_since)
184+
185+
# Check for overlap in two sets
186+
merged_stats = { }
187+
188+
for name in lib_feature_stats:
189+
if language_feature_stats.get(name) is not None:
190+
if not name in joint_features:
191+
print "error: feature '" + name + "' is both a lang and lib feature but not whitelisted"
192+
errors = True
193+
lang_status = lang_feature_stats[name][3]
194+
lib_status = lib_feature_stats[name][3]
195+
lang_stable_since = lang_feature_stats[name][4]
196+
lib_stable_since = lib_feature_stats[name][4]
197+
198+
if lang_status != lib_status and lib_status != "deprecated":
199+
print "error: feature '" + name + "' has lang status " + lang_status + \
200+
" but lib status " + lib_status
201+
errors = True
202+
203+
if lang_stable_since != lib_stable_since:
204+
print "error: feature '" + name + "' has lang stable since " + lang_stable_since + \
205+
" but lib stable since " + lib_stable_since
206+
errors = True
207+
208+
merged_stats[name] = (name, True, True, lang_status, lang_stable_since)
209+
210+
del language_feature_stats[name]
211+
del lib_feature_stats[name]
212+
213+
if errors:
214+
sys.exit(1)
215+
216+
# Finally, display the stats
217+
stats = {}
218+
stats.update(language_feature_stats)
219+
stats.update(lib_feature_stats)
220+
stats.update(merged_stats)
221+
lines = []
222+
for s in stats:
223+
s = stats[s]
224+
type_ = "lang"
225+
if s[1] and s[2]:
226+
type_ = "lang/lib"
227+
elif s[2]:
228+
type_ = "lib"
229+
line = "{: <32}".format(s[0]) + \
230+
"{: <8}".format(type_) + \
231+
"{: <12}".format(s[3]) + \
232+
"{: <8}".format(str(s[4]))
233+
lines += [line]
234+
235+
lines.sort()
236+
237+
print
238+
print "Rust feature summary:"
239+
print
240+
for line in lines:
241+
print line
242+
print
243+

0 commit comments

Comments
 (0)