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/libbacktrace' -v \
| grep '^$(S)src/rust-installer' -v \ | grep '^$(S)src/rust-installer' -v \
| xargs $(CFG_PYTHON) $(S)src/etc/check-binaries.py | xargs $(CFG_PYTHON) $(S)src/etc/check-binaries.py
$(CFG_PYTHON) $(S)src/etc/featureck.py $(S)src/
endif 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::ast_util::is_local;
use syntax::attr::{Stability, AttrMetaMethods}; use syntax::attr::{Stability, AttrMetaMethods};
use syntax::visit::{FnKind, FkMethod, Visitor}; 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::nodemap::{NodeMap, DefIdMap, FnvHashSet};
use util::ppaux::Repr; use util::ppaux::Repr;
@ -221,8 +221,8 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
None => format!("use of unstable library feature '{}'", feature.get()) None => format!("use of unstable library feature '{}'", feature.get())
}; };
emit_feature_err(&self.tcx.sess.parse_sess.span_diagnostic, emit_feature_warn(&self.tcx.sess.parse_sess.span_diagnostic,
feature.get(), span, &msg[]); feature.get(), span, &msg[]);
} }
} }
Some(..) => { Some(..) => {

View File

@ -17,6 +17,10 @@
//! //!
//! Features are enabled in programs via the crate-level attributes of //! Features are enabled in programs via the crate-level attributes of
//! `#![feature(...)]` with a comma-separated list of features. //! `#![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 self::Status::*;
use abi::RustIntrinsic; use abi::RustIntrinsic;
@ -33,77 +37,82 @@ use parse::token::{self, InternedString};
use std::slice; use std::slice;
use std::ascii::AsciiExt; 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 ("simd", "1.0.0", Active),
static KNOWN_FEATURES: &'static [(&'static str, Status)] = &[ ("default_type_params", "1.0.0", Accepted),
("globs", Accepted), ("quote", "1.0.0", Active),
("macro_rules", Accepted), ("link_llvm_intrinsics", "1.0.0", Active),
("struct_variant", Accepted), ("linkage", "1.0.0", Active),
("asm", Active), ("struct_inherit", "1.0.0", Removed),
("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", Active), ("quad_precision_float", "1.0.0", Removed),
("default_type_params", Accepted),
("quote", Active),
("link_llvm_intrinsics", Active),
("linkage", Active),
("struct_inherit", 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), ("if_let", "1.0.0", Accepted),
("unboxed_closures", Active), ("while_let", "1.0.0", Accepted),
("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", Accepted), ("plugin", "1.0.0", Active),
("while_let", Accepted), ("start", "1.0.0", Active),
("main", "1.0.0", Active),
("plugin", Active),
("start", Active),
("main", Active),
// A temporary feature gate used to enable parser extensions needed // A temporary feature gate used to enable parser extensions needed
// to bootstrap fix for #5723. // 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. // 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. // 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. // 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 // OIBIT specific features
("optin_builtin_traits", Active), ("optin_builtin_traits", "1.0.0", Active),
// int and uint are now deprecated // 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 // These are used to test this portion of the compiler, they don't actually
// mean anything // mean anything
("test_accepted_feature", Accepted), ("test_accepted_feature", "1.0.0", Accepted),
("test_removed_feature", Removed), ("test_removed_feature", "1.0.0", Removed),
]; ];
enum Status { enum Status {
@ -164,10 +173,7 @@ impl<'a> Context<'a> {
fn warn_feature(&self, feature: &str, span: Span, explain: &str) { fn warn_feature(&self, feature: &str, span: Span, explain: &str) {
if !self.has_feature(feature) { if !self.has_feature(feature) {
self.span_handler.span_warn(span, explain); emit_feature_warn(self.span_handler, feature, span, explain);
self.span_handler.span_help(span, &format!("add #![feature({})] to the \
crate attributes to silence this warning",
feature)[]);
} }
} }
fn has_feature(&self, feature: &str) -> bool { fn has_feature(&self, feature: &str) -> bool {
@ -182,6 +188,13 @@ pub fn emit_feature_err(diag: &SpanHandler, feature: &str, span: Span, explain:
feature)[]); 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> { struct MacroVisitor<'a> {
context: &'a Context<'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() match KNOWN_FEATURES.iter()
.find(|& &(n, _)| name == n) { .find(|& &(n, _, _)| name == n) {
Some(&(name, Active)) => { Some(&(name, _, Active)) => {
cx.features.push(name); cx.features.push(name);
} }
Some(&(name, Deprecated)) => { Some(&(name, _, Deprecated)) => {
cx.features.push(name); cx.features.push(name);
span_handler.span_warn( span_handler.span_warn(
mi.span, mi.span,
"feature is deprecated and will only be available \ "feature is deprecated and will only be available \
for a limited time, please rewrite code that relies on it"); 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"); 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, \ span_handler.span_warn(mi.span, "feature has been added to Rust, \
directive not necessary"); directive not necessary");
} }