about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorBrian Anderson <banderson@mozilla.com>2015-01-14 19:27:45 -0800
committerBrian Anderson <banderson@mozilla.com>2015-01-21 16:16:21 -0800
commit11f4d62a062a5c80fad414c457e2a7dbc9a1d6cc (patch)
treee7b345d6fd0eeabb866c493f9403cebefe3c981c /src
parent7b73ec469878e428c789b77320b3f8dc8d974d22 (diff)
downloadrust-11f4d62a062a5c80fad414c457e2a7dbc9a1d6cc.tar.gz
rust-11f4d62a062a5c80fad414c457e2a7dbc9a1d6cc.zip
Add a lint for library features
Does a sanity check of the version numbers.
Diffstat (limited to 'src')
-rw-r--r--src/etc/featureck.py254
-rw-r--r--src/librustc/middle/stability.rs6
-rw-r--r--src/libsyntax/feature_gate.rs141
3 files changed, 334 insertions, 67 deletions
diff --git a/src/etc/featureck.py b/src/etc/featureck.py
new file mode 100644
index 00000000000..06ef2d7c605
--- /dev/null
+++ b/src/etc/featureck.py
@@ -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
+
diff --git a/src/librustc/middle/stability.rs b/src/librustc/middle/stability.rs
index 4d69be7cbc2..0554533a4aa 100644
--- a/src/librustc/middle/stability.rs
+++ b/src/librustc/middle/stability.rs
@@ -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(..) => {
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index a5a2935d808..43af53aa2d2 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -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
-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", Active),
-    ("default_type_params", Accepted),
-    ("quote", Active),
-    ("link_llvm_intrinsics", Active),
-    ("linkage", Active),
-    ("struct_inherit", Removed),
-
-    ("quad_precision_float", Removed),
-
-    ("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", Accepted),
-    ("while_let", Accepted),
-
-    ("plugin", Active),
-    ("start", Active),
-    ("main", Active),
+// 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),
+
+    ("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),
+
+    ("quad_precision_float", "1.0.0", 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),
+
+    ("if_let", "1.0.0", Accepted),
+    ("while_let", "1.0.0", Accepted),
+
+    ("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");
                         }