Add a lint for library features

Does a sanity check of the version numbers.
This commit is contained in:
Brian Anderson 2015-01-14 19:27:45 -08:00
parent 7b73ec4698
commit 11f4d62a06
4 changed files with 330 additions and 62 deletions

View File

@ -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
View 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

View File

@ -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(..) => {

View File

@ -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");
}