Add a lint for library features
Does a sanity check of the version numbers.
This commit is contained in:
parent
7b73ec4698
commit
11f4d62a06
@ -300,6 +300,7 @@ tidy:
|
||||
| grep '^$(S)src/libbacktrace' -v \
|
||||
| grep '^$(S)src/rust-installer' -v \
|
||||
| xargs $(CFG_PYTHON) $(S)src/etc/check-binaries.py
|
||||
$(CFG_PYTHON) $(S)src/etc/featureck.py $(S)src/
|
||||
|
||||
|
||||
endif
|
||||
|
254
src/etc/featureck.py
Normal file
254
src/etc/featureck.py
Normal file
@ -0,0 +1,254 @@
|
||||
# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||
# file at the top-level directory of this distribution and at
|
||||
# http://rust-lang.org/COPYRIGHT.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
# option. This file may not be copied, modified, or distributed
|
||||
# except according to those terms.
|
||||
|
||||
# This script does a tree-wide sanity checks against stability
|
||||
# attributes, currently:
|
||||
# * For all feature_name/level pairs the 'since' field is the same
|
||||
# * That no features are both stable and unstable.
|
||||
# * That lib features don't have the same name as lang features
|
||||
# unless they are on the 'joint_features' whitelist
|
||||
# * That features that exist in both lang and lib and are stable
|
||||
# since the same version
|
||||
# * Prints information about features
|
||||
|
||||
import sys, os, re
|
||||
|
||||
src_dir = sys.argv[1]
|
||||
|
||||
# Features that are allowed to exist in both the language and the library
|
||||
joint_features = [ "on_unimpleented" ]
|
||||
|
||||
# Grab the list of language features from the compiler
|
||||
language_gate_statuses = [ "Active", "Deprecated", "Removed", "Accepted" ]
|
||||
feature_gate_source = os.path.join(src_dir, "libsyntax", "feature_gate.rs")
|
||||
language_features = []
|
||||
language_feature_names = []
|
||||
with open(feature_gate_source, 'r') as f:
|
||||
for line in f:
|
||||
original_line = line
|
||||
line = line.strip()
|
||||
is_feature_line = False
|
||||
for status in language_gate_statuses:
|
||||
if status in line and line.startswith("("):
|
||||
is_feature_line = True
|
||||
|
||||
if is_feature_line:
|
||||
line = line.replace("(", "").replace("),", "").replace(")", "")
|
||||
parts = line.split(",")
|
||||
if len(parts) != 3:
|
||||
print "unexpected number of components in line: " + original_line
|
||||
sys.exit(1)
|
||||
feature_name = parts[0].strip().replace('"', "")
|
||||
since = parts[1].strip().replace('"', "")
|
||||
status = parts[2].strip()
|
||||
assert len(feature_name) > 0
|
||||
assert len(since) > 0
|
||||
assert len(status) > 0
|
||||
|
||||
language_feature_names += [feature_name]
|
||||
language_features += [(feature_name, since, status)]
|
||||
|
||||
assert len(language_features) > 0
|
||||
|
||||
errors = False
|
||||
|
||||
lib_features = { }
|
||||
lib_features_and_level = { }
|
||||
for (dirpath, dirnames, filenames) in os.walk(src_dir):
|
||||
# Don't look for feature names in tests
|
||||
if "src/test" in dirpath:
|
||||
continue
|
||||
|
||||
# Takes a long time to traverse LLVM
|
||||
if "src/llvm" in dirpath:
|
||||
continue
|
||||
|
||||
for filename in filenames:
|
||||
if not filename.endswith(".rs"):
|
||||
continue
|
||||
|
||||
path = os.path.join(dirpath, filename)
|
||||
with open(path, 'r') as f:
|
||||
line_num = 0
|
||||
for line in f:
|
||||
line_num += 1
|
||||
level = None
|
||||
if "[unstable(" in line:
|
||||
level = "unstable"
|
||||
elif "[stable(" in line:
|
||||
level = "stable"
|
||||
elif "[deprecated(" in line:
|
||||
level = "deprecated"
|
||||
else:
|
||||
continue
|
||||
|
||||
# This is a stability attribute. For the purposes of this
|
||||
# script we expect both the 'feature' and 'since' attributes on
|
||||
# the same line, e.g.
|
||||
# `#[unstable(feature = "foo", since = "1.0.0")]`
|
||||
|
||||
p = re.compile('feature *= *"(\w*)".*since *= *"([\w\.]*)"')
|
||||
m = p.search(line)
|
||||
if not m is None:
|
||||
feature_name = m.group(1)
|
||||
since = m.group(2)
|
||||
lib_features[feature_name] = feature_name
|
||||
if lib_features_and_level.get((feature_name, level)) is None:
|
||||
# Add it to the observed features
|
||||
lib_features_and_level[(feature_name, level)] = (since, path, line_num, line)
|
||||
else:
|
||||
# Verify that for this combination of feature_name and level the 'since'
|
||||
# attribute matches.
|
||||
(expected_since, source_path, source_line_num, source_line) = \
|
||||
lib_features_and_level.get((feature_name, level))
|
||||
if since != expected_since:
|
||||
print "mismatch in " + level + " feature '" + feature_name + "'"
|
||||
print "line " + str(source_line_num) + " of " + source_path + ":"
|
||||
print source_line
|
||||
print "line " + str(line_num) + " of " + path + ":"
|
||||
print line
|
||||
errors = True
|
||||
|
||||
# Verify that this lib feature doesn't duplicate a lang feature
|
||||
if feature_name in language_feature_names:
|
||||
print "lib feature '" + feature_name + "' duplicates a lang feature"
|
||||
print "line " + str(line_num) + " of " + path + ":"
|
||||
print line
|
||||
errors = True
|
||||
|
||||
else:
|
||||
print "misformed stability attribute"
|
||||
print "line " + str(line_num) + " of " + path + ":"
|
||||
print line
|
||||
errors = True
|
||||
|
||||
# Merge data about both lists
|
||||
# name, lang, lib, status, stable since, partially deprecated
|
||||
|
||||
language_feature_stats = {}
|
||||
|
||||
for f in language_features:
|
||||
name = f[0]
|
||||
lang = True
|
||||
lib = False
|
||||
status = "unstable"
|
||||
stable_since = None
|
||||
partially_deprecated = False
|
||||
|
||||
if f[2] == "Accepted":
|
||||
status = "stable"
|
||||
if status == "stable":
|
||||
stable_since = f[1]
|
||||
|
||||
language_feature_stats[name] = (name, lang, lib, status, stable_since, \
|
||||
partially_deprecated)
|
||||
|
||||
lib_feature_stats = {}
|
||||
|
||||
for f in lib_features:
|
||||
name = f
|
||||
lang = False
|
||||
lib = True
|
||||
status = "unstable"
|
||||
stable_since = None
|
||||
partially_deprecated = False
|
||||
|
||||
is_stable = lib_features_and_level.get((name, "stable")) is not None
|
||||
is_unstable = lib_features_and_level.get((name, "unstable")) is not None
|
||||
is_deprecated = lib_features_and_level.get((name, "deprecated")) is not None
|
||||
|
||||
if is_stable and is_unstable:
|
||||
print "feature '" + name + "' is both stable and unstable"
|
||||
errors = True
|
||||
|
||||
if is_stable:
|
||||
status = "stable"
|
||||
stable_since = lib_features_and_level[(name, "stable")][0]
|
||||
elif is_unstable:
|
||||
status = "unstable"
|
||||
stable_since = lib_features_and_level[(name, "unstable")][0]
|
||||
elif is_deprecated:
|
||||
status = "deprecated"
|
||||
|
||||
if (is_stable or is_unstable) and is_deprecated:
|
||||
partially_deprecated = True
|
||||
|
||||
lib_feature_stats[name] = (name, lang, lib, status, stable_since, \
|
||||
partially_deprecated)
|
||||
|
||||
# Check for overlap in two sets
|
||||
merged_stats = { }
|
||||
|
||||
for name in lib_feature_stats:
|
||||
if language_feature_stats.get(name) is not None:
|
||||
if not name in joint_features:
|
||||
print "feature '" + name + "' is both a lang and lib feature but not whitelisted"
|
||||
errors = True
|
||||
lang_status = lang_feature_stats[name][3]
|
||||
lib_status = lib_feature_stats[name][3]
|
||||
lang_stable_since = lang_feature_stats[name][4]
|
||||
lib_stable_since = lib_feature_stats[name][4]
|
||||
lang_partially_deprecated = lang_feature_stats[name][5]
|
||||
lib_partially_deprecated = lib_feature_stats[name][5]
|
||||
|
||||
if lang_status != lib_status and lib_status != "deprecated":
|
||||
print "feature '" + name + "' has lang status " + lang_status + \
|
||||
" but lib status " + lib_status
|
||||
errors = True
|
||||
|
||||
partially_deprecated = lang_partially_deprecated or lib_partially_deprecated
|
||||
if lib_status == "deprecated" and lang_status != "deprecated":
|
||||
partially_deprecated = True
|
||||
|
||||
if lang_stable_since != lib_stable_since:
|
||||
print "feature '" + name + "' has lang stable since " + lang_stable_since + \
|
||||
" but lib stable since " + lib_stable_since
|
||||
errors = True
|
||||
|
||||
merged_stats[name] = (name, True, True, lang_status, lang_stable_since, \
|
||||
partially_deprecated)
|
||||
|
||||
del language_feature_stats[name]
|
||||
del lib_feature_stats[name]
|
||||
|
||||
if errors:
|
||||
sys.exit(1)
|
||||
|
||||
# Finally, display the stats
|
||||
stats = {}
|
||||
stats.update(language_feature_stats)
|
||||
stats.update(lib_feature_stats)
|
||||
stats.update(merged_stats)
|
||||
lines = []
|
||||
for s in stats:
|
||||
s = stats[s]
|
||||
type_ = "lang"
|
||||
if s[1] and s[2]:
|
||||
type_ = "lang/lib"
|
||||
elif s[2]:
|
||||
type_ = "lib"
|
||||
line = s[0] + ",\t\t\t" + type_ + ",\t" + s[3] + ",\t" + str(s[4])
|
||||
line = "{: <32}".format(s[0]) + \
|
||||
"{: <8}".format(type_) + \
|
||||
"{: <12}".format(s[3]) + \
|
||||
"{: <8}".format(str(s[4]))
|
||||
if s[5]:
|
||||
line += "(partially deprecated)"
|
||||
lines += [line]
|
||||
|
||||
lines.sort()
|
||||
|
||||
print
|
||||
print "Rust feature summary:"
|
||||
print
|
||||
for line in lines:
|
||||
print line
|
||||
print
|
||||
|
@ -24,7 +24,7 @@ use syntax::ast::{TypeMethod, Method, Generics, StructField, TypeTraitItem};
|
||||
use syntax::ast_util::is_local;
|
||||
use syntax::attr::{Stability, AttrMetaMethods};
|
||||
use syntax::visit::{FnKind, FkMethod, Visitor};
|
||||
use syntax::feature_gate::emit_feature_err;
|
||||
use syntax::feature_gate::emit_feature_warn;
|
||||
use util::nodemap::{NodeMap, DefIdMap, FnvHashSet};
|
||||
use util::ppaux::Repr;
|
||||
|
||||
@ -221,8 +221,8 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
|
||||
None => format!("use of unstable library feature '{}'", feature.get())
|
||||
};
|
||||
|
||||
emit_feature_err(&self.tcx.sess.parse_sess.span_diagnostic,
|
||||
feature.get(), span, &msg[]);
|
||||
emit_feature_warn(&self.tcx.sess.parse_sess.span_diagnostic,
|
||||
feature.get(), span, &msg[]);
|
||||
}
|
||||
}
|
||||
Some(..) => {
|
||||
|
@ -17,6 +17,10 @@
|
||||
//!
|
||||
//! Features are enabled in programs via the crate-level attributes of
|
||||
//! `#![feature(...)]` with a comma-separated list of features.
|
||||
//!
|
||||
//! For the purpose of future feature-tracking, once code for detection of feature
|
||||
//! gate usage is added, *do not remove it again* even once the feature
|
||||
//! becomes stable.
|
||||
use self::Status::*;
|
||||
|
||||
use abi::RustIntrinsic;
|
||||
@ -33,77 +37,82 @@ use parse::token::{self, InternedString};
|
||||
use std::slice;
|
||||
use std::ascii::AsciiExt;
|
||||
|
||||
// If you change this list without updating src/doc/reference.md, @cmr will be sad
|
||||
// Don't ever remove anything from this list; set them to 'Removed'.
|
||||
// The version numbers here correspond to the version in which the current status
|
||||
// was set. This is most important for knowing when a particular feature became
|
||||
// stable (active).
|
||||
// NB: The featureck.py script parses this information directly out of the source
|
||||
// so take care when modifying it.
|
||||
static KNOWN_FEATURES: &'static [(&'static str, &'static str, Status)] = &[
|
||||
("globs", "1.0.0", Accepted),
|
||||
("macro_rules", "1.0.0", Accepted),
|
||||
("struct_variant", "1.0.0", Accepted),
|
||||
("asm", "1.0.0", Active),
|
||||
("managed_boxes", "1.0.0", Removed),
|
||||
("non_ascii_idents", "1.0.0", Active),
|
||||
("thread_local", "1.0.0", Active),
|
||||
("link_args", "1.0.0", Active),
|
||||
("phase", "1.0.0", Removed),
|
||||
("plugin_registrar", "1.0.0", Active),
|
||||
("log_syntax", "1.0.0", Active),
|
||||
("trace_macros", "1.0.0", Active),
|
||||
("concat_idents", "1.0.0", Active),
|
||||
("unsafe_destructor", "1.0.0", Active),
|
||||
("intrinsics", "1.0.0", Active),
|
||||
("lang_items", "1.0.0", Active),
|
||||
|
||||
// if you change this list without updating src/doc/reference.md, @cmr will be sad
|
||||
static KNOWN_FEATURES: &'static [(&'static str, Status)] = &[
|
||||
("globs", Accepted),
|
||||
("macro_rules", Accepted),
|
||||
("struct_variant", Accepted),
|
||||
("asm", Active),
|
||||
("managed_boxes", Removed),
|
||||
("non_ascii_idents", Active),
|
||||
("thread_local", Active),
|
||||
("link_args", Active),
|
||||
("phase", Removed),
|
||||
("plugin_registrar", Active),
|
||||
("log_syntax", Active),
|
||||
("trace_macros", Active),
|
||||
("concat_idents", Active),
|
||||
("unsafe_destructor", Active),
|
||||
("intrinsics", Active),
|
||||
("lang_items", Active),
|
||||
("simd", "1.0.0", Active),
|
||||
("default_type_params", "1.0.0", Accepted),
|
||||
("quote", "1.0.0", Active),
|
||||
("link_llvm_intrinsics", "1.0.0", Active),
|
||||
("linkage", "1.0.0", Active),
|
||||
("struct_inherit", "1.0.0", Removed),
|
||||
|
||||
("simd", Active),
|
||||
("default_type_params", Accepted),
|
||||
("quote", Active),
|
||||
("link_llvm_intrinsics", Active),
|
||||
("linkage", Active),
|
||||
("struct_inherit", Removed),
|
||||
("quad_precision_float", "1.0.0", Removed),
|
||||
|
||||
("quad_precision_float", Removed),
|
||||
("rustc_diagnostic_macros", "1.0.0", Active),
|
||||
("unboxed_closures", "1.0.0", Active),
|
||||
("import_shadowing", "1.0.0", Active),
|
||||
("advanced_slice_patterns", "1.0.0", Active),
|
||||
("tuple_indexing", "1.0.0", Accepted),
|
||||
("associated_types", "1.0.0", Accepted),
|
||||
("visible_private_types", "1.0.0", Active),
|
||||
("slicing_syntax", "1.0.0", Active),
|
||||
("box_syntax", "1.0.0", Active),
|
||||
("on_unimplemented", "1.0.0", Active),
|
||||
("simd_ffi", "1.0.0", Active),
|
||||
|
||||
("rustc_diagnostic_macros", Active),
|
||||
("unboxed_closures", Active),
|
||||
("import_shadowing", Active),
|
||||
("advanced_slice_patterns", Active),
|
||||
("tuple_indexing", Accepted),
|
||||
("associated_types", Accepted),
|
||||
("visible_private_types", Active),
|
||||
("slicing_syntax", Active),
|
||||
("box_syntax", Active),
|
||||
("on_unimplemented", Active),
|
||||
("simd_ffi", Active),
|
||||
("if_let", "1.0.0", Accepted),
|
||||
("while_let", "1.0.0", Accepted),
|
||||
|
||||
("if_let", Accepted),
|
||||
("while_let", Accepted),
|
||||
|
||||
("plugin", Active),
|
||||
("start", Active),
|
||||
("main", Active),
|
||||
("plugin", "1.0.0", Active),
|
||||
("start", "1.0.0", Active),
|
||||
("main", "1.0.0", Active),
|
||||
|
||||
// A temporary feature gate used to enable parser extensions needed
|
||||
// to bootstrap fix for #5723.
|
||||
("issue_5723_bootstrap", Accepted),
|
||||
("issue_5723_bootstrap", "1.0.0", Accepted),
|
||||
|
||||
// A way to temporarily opt out of opt in copy. This will *never* be accepted.
|
||||
("opt_out_copy", Removed),
|
||||
("opt_out_copy", "1.0.0", Removed),
|
||||
|
||||
// A way to temporarily opt out of the new orphan rules. This will *never* be accepted.
|
||||
("old_orphan_check", Deprecated),
|
||||
("old_orphan_check", "1.0.0", Deprecated),
|
||||
|
||||
// A way to temporarily opt out of the new impl rules. This will *never* be accepted.
|
||||
("old_impl_check", Deprecated),
|
||||
("old_impl_check", "1.0.0", Deprecated),
|
||||
|
||||
// OIBIT specific features
|
||||
("optin_builtin_traits", Active),
|
||||
("optin_builtin_traits", "1.0.0", Active),
|
||||
|
||||
// int and uint are now deprecated
|
||||
("int_uint", Active),
|
||||
("int_uint", "1.0.0", Active),
|
||||
|
||||
// These are used to test this portion of the compiler, they don't actually
|
||||
// mean anything
|
||||
("test_accepted_feature", Accepted),
|
||||
("test_removed_feature", Removed),
|
||||
("test_accepted_feature", "1.0.0", Accepted),
|
||||
("test_removed_feature", "1.0.0", Removed),
|
||||
];
|
||||
|
||||
enum Status {
|
||||
@ -164,10 +173,7 @@ impl<'a> Context<'a> {
|
||||
|
||||
fn warn_feature(&self, feature: &str, span: Span, explain: &str) {
|
||||
if !self.has_feature(feature) {
|
||||
self.span_handler.span_warn(span, explain);
|
||||
self.span_handler.span_help(span, &format!("add #![feature({})] to the \
|
||||
crate attributes to silence this warning",
|
||||
feature)[]);
|
||||
emit_feature_warn(self.span_handler, feature, span, explain);
|
||||
}
|
||||
}
|
||||
fn has_feature(&self, feature: &str) -> bool {
|
||||
@ -182,6 +188,13 @@ pub fn emit_feature_err(diag: &SpanHandler, feature: &str, span: Span, explain:
|
||||
feature)[]);
|
||||
}
|
||||
|
||||
pub fn emit_feature_warn(diag: &SpanHandler, feature: &str, span: Span, explain: &str) {
|
||||
diag.span_warn(span, explain);
|
||||
diag.span_help(span, &format!("add #![feature({})] to the \
|
||||
crate attributes to silence this warning",
|
||||
feature)[]);
|
||||
}
|
||||
|
||||
struct MacroVisitor<'a> {
|
||||
context: &'a Context<'a>
|
||||
}
|
||||
@ -510,21 +523,21 @@ fn check_crate_inner<F>(cm: &CodeMap, span_handler: &SpanHandler, krate: &ast::C
|
||||
}
|
||||
};
|
||||
match KNOWN_FEATURES.iter()
|
||||
.find(|& &(n, _)| name == n) {
|
||||
Some(&(name, Active)) => {
|
||||
.find(|& &(n, _, _)| name == n) {
|
||||
Some(&(name, _, Active)) => {
|
||||
cx.features.push(name);
|
||||
}
|
||||
Some(&(name, Deprecated)) => {
|
||||
Some(&(name, _, Deprecated)) => {
|
||||
cx.features.push(name);
|
||||
span_handler.span_warn(
|
||||
mi.span,
|
||||
"feature is deprecated and will only be available \
|
||||
for a limited time, please rewrite code that relies on it");
|
||||
}
|
||||
Some(&(_, Removed)) => {
|
||||
Some(&(_, _, Removed)) => {
|
||||
span_handler.span_err(mi.span, "feature has been removed");
|
||||
}
|
||||
Some(&(_, Accepted)) => {
|
||||
Some(&(_, _, Accepted)) => {
|
||||
span_handler.span_warn(mi.span, "feature has been added to Rust, \
|
||||
directive not necessary");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user