diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index 5fc2a955c0602..d39bcd0fbb2bb 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -528,19 +528,13 @@ pub fn phase_2_configure_and_expand(sess: &Session, middle::recursion_limit::update_recursion_limit(sess, &krate); }); - time(time_passes, "gated macro checking", || { - sess.track_errors(|| { - let features = - syntax::feature_gate::check_crate_macros(sess.codemap(), - &sess.parse_sess.span_diagnostic, - &krate); - - // these need to be set "early" so that expansion sees `quote` if enabled. - *sess.features.borrow_mut() = features; - }) + // these need to be set "early" so that expansion sees `quote` if enabled. + sess.track_errors(|| { + *sess.features.borrow_mut() = + syntax::feature_gate::get_features(&sess.parse_sess.span_diagnostic, + &krate); })?; - krate = time(time_passes, "crate injection", || { syntax::std_inject::maybe_inject_crates_ref(krate, sess.opts.alt_std_name.clone()) }); diff --git a/src/librustc_plugin/load.rs b/src/librustc_plugin/load.rs index ac40215bbb1d0..036e46c380398 100644 --- a/src/librustc_plugin/load.rs +++ b/src/librustc_plugin/load.rs @@ -51,27 +51,32 @@ pub fn load_plugins(sess: &Session, addl_plugins: Option>) -> Vec { let mut loader = PluginLoader::new(sess, cstore, crate_name); - for attr in &krate.attrs { - if !attr.check_name("plugin") { - continue; - } - - let plugins = match attr.meta_item_list() { - Some(xs) => xs, - None => { - call_malformed_plugin_attribute(sess, attr.span); + // do not report any error now. since crate attributes are + // not touched by expansion, every use of plugin without + // the feature enabled will result in an error later... + if sess.features.borrow().plugin { + for attr in &krate.attrs { + if !attr.check_name("plugin") { continue; } - }; - for plugin in plugins { - if plugin.value_str().is_some() { - call_malformed_plugin_attribute(sess, attr.span); - continue; + let plugins = match attr.meta_item_list() { + Some(xs) => xs, + None => { + call_malformed_plugin_attribute(sess, attr.span); + continue; + } + }; + + for plugin in plugins { + if plugin.value_str().is_some() { + call_malformed_plugin_attribute(sess, attr.span); + continue; + } + + let args = plugin.meta_item_list().map(ToOwned::to_owned).unwrap_or_default(); + loader.load_plugin(plugin.span, &plugin.name(), args); } - - let args = plugin.meta_item_list().map(ToOwned::to_owned).unwrap_or_default(); - loader.load_plugin(plugin.span, &plugin.name(), args); } } diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index a2c8ae898e173..57500f92b79d8 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -35,6 +35,16 @@ use std_inject; use std::collections::HashSet; use std::env; +// this function is called to detect use of feature-gated or invalid attributes +// on macro invoations since they will not be detected after macro expansion +fn check_attributes(attrs: &[ast::Attribute], fld: &MacroExpander) { + for attr in attrs.iter() { + feature_gate::check_attribute(&attr, &fld.cx.parse_sess.span_diagnostic, + &fld.cx.parse_sess.codemap(), + &fld.cx.ecfg.features.unwrap()); + } +} + pub fn expand_expr(e: P, fld: &mut MacroExpander) -> P { let expr_span = e.span; return e.and_then(|ast::Expr {id, node, span, attrs}| match node { @@ -42,6 +52,9 @@ pub fn expand_expr(e: P, fld: &mut MacroExpander) -> P { // expr_mac should really be expr_ext or something; it's the // entry-point for all syntax extensions. ast::ExprKind::Mac(mac) => { + if let Some(ref attrs) = attrs { + check_attributes(attrs, fld); + } // Assert that we drop any macro attributes on the floor here drop(attrs); @@ -70,10 +83,12 @@ pub fn expand_expr(e: P, fld: &mut MacroExpander) -> P { ast::ExprKind::InPlace(placer, value_expr) => { // Ensure feature-gate is enabled - feature_gate::check_for_placement_in( - fld.cx.ecfg.features, - &fld.cx.parse_sess.span_diagnostic, - expr_span); + if !fld.cx.ecfg.features.unwrap().placement_in_syntax { + feature_gate::emit_feature_err( + &fld.cx.parse_sess.span_diagnostic, "placement_in_syntax", expr_span, + feature_gate::GateIssue::Language, feature_gate::EXPLAIN_PLACEMENT_IN + ); + } let placer = fld.fold_expr(placer); let value_expr = fld.fold_expr(value_expr); @@ -370,6 +385,8 @@ pub fn expand_item_mac(it: P, _ => fld.cx.span_bug(it.span, "invalid item macro invocation") }); + check_attributes(&attrs, fld); + let fm = fresh_mark(); let items = { let expanded = match fld.cx.syntax_env.find(extname) { @@ -444,18 +461,6 @@ pub fn expand_item_mac(it: P, let allow_internal_unstable = attr::contains_name(&attrs, "allow_internal_unstable"); - // ensure any #[allow_internal_unstable]s are - // detected (including nested macro definitions - // etc.) - if allow_internal_unstable && !fld.cx.ecfg.enable_allow_internal_unstable() { - feature_gate::emit_feature_err( - &fld.cx.parse_sess.span_diagnostic, - "allow_internal_unstable", - span, - feature_gate::GateIssue::Language, - feature_gate::EXPLAIN_ALLOW_INTERNAL_UNSTABLE) - } - let export = attr::contains_name(&attrs, "macro_export"); let def = ast::MacroDef { ident: ident, @@ -519,6 +524,10 @@ fn expand_stmt(stmt: Stmt, fld: &mut MacroExpander) -> SmallVector { _ => return expand_non_macro_stmt(stmt, fld) }; + if let Some(ref attrs) = attrs { + check_attributes(attrs, fld); + } + // Assert that we drop any macro attributes on the floor here drop(attrs); @@ -1066,7 +1075,7 @@ fn expand_impl_item(ii: ast::ImplItem, fld: &mut MacroExpander) attrs: ii.attrs, vis: ii.vis, defaultness: ii.defaultness, - node: match ii.node { + node: match ii.node { ast::ImplItemKind::Method(sig, body) => { let (sig, body) = expand_and_rename_method(sig, body, fld); ast::ImplItemKind::Method(sig, body) @@ -1075,13 +1084,11 @@ fn expand_impl_item(ii: ast::ImplItem, fld: &mut MacroExpander) }, span: fld.new_span(ii.span) }), - ast::ImplItemKind::Macro(_) => { - let (span, mac) = match ii.node { - ast::ImplItemKind::Macro(mac) => (ii.span, mac), - _ => unreachable!() - }; + ast::ImplItemKind::Macro(mac) => { + check_attributes(&ii.attrs, fld); + let maybe_new_items = - expand_mac_invoc(mac, span, + expand_mac_invoc(mac, ii.span, |r| r.make_impl_items(), |meths, mark| meths.move_map(|m| mark_impl_item(m, mark)), fld); @@ -1348,14 +1355,14 @@ impl<'feat> ExpansionConfig<'feat> { } feature_tests! { - fn enable_quotes = allow_quote, - fn enable_asm = allow_asm, - fn enable_log_syntax = allow_log_syntax, - fn enable_concat_idents = allow_concat_idents, - fn enable_trace_macros = allow_trace_macros, + fn enable_quotes = quote, + fn enable_asm = asm, + fn enable_log_syntax = log_syntax, + fn enable_concat_idents = concat_idents, + fn enable_trace_macros = trace_macros, fn enable_allow_internal_unstable = allow_internal_unstable, - fn enable_custom_derive = allow_custom_derive, - fn enable_pushpop_unsafe = allow_pushpop_unsafe, + fn enable_custom_derive = custom_derive, + fn enable_pushpop_unsafe = pushpop_unsafe, } } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index c77671d89f88f..ed2371fc34823 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -22,7 +22,6 @@ //! gate usage is added, *do not remove it again* even once the feature //! becomes stable. -use self::Status::*; use self::AttributeType::*; use self::AttributeGate::*; @@ -40,125 +39,146 @@ use parse::token::InternedString; use std::ascii::AsciiExt; use std::cmp; +macro_rules! setter { + ($field: ident) => {{ + fn f(features: &mut Features) -> &mut bool { + &mut features.$field + } + f as fn(&mut Features) -> &mut bool + }} +} + +macro_rules! declare_features { + ($((active, $feature: ident, $ver: expr, $issue: expr)),+) => { + /// Represents active features that are currently being implemented or + /// currently being considered for addition/removal. + const ACTIVE_FEATURES: &'static [(&'static str, &'static str, + Option, fn(&mut Features) -> &mut bool)] = &[ + $((stringify!($feature), $ver, $issue, setter!($feature))),+ + ]; + + /// A set of features to be used by later passes. + pub struct Features { + /// spans of #![feature] attrs for stable language features. for error reporting + pub declared_stable_lang_features: Vec, + /// #![feature] attrs for non-language (library) features + pub declared_lib_features: Vec<(InternedString, Span)>, + $(pub $feature: bool),+ + } + + impl Features { + pub fn new() -> Features { + Features { + declared_stable_lang_features: Vec::new(), + declared_lib_features: Vec::new(), + $($feature: false),+ + } + } + } + }; + + ($((removed, $feature: ident, $ver: expr, $issue: expr)),+) => { + /// Represents features which has since been removed (it was once Active) + const REMOVED_FEATURES: &'static [(&'static str, &'static str, Option)] = &[ + $((stringify!($feature), $ver, $issue)),+ + ]; + }; + + ($((accepted, $feature: ident, $ver: expr, $issue: expr)),+) => { + /// Those language feature has since been Accepted (it was once Active) + const ACCEPTED_FEATURES: &'static [(&'static str, &'static str, Option)] = &[ + $((stringify!($feature), $ver, $issue)),+ + ]; + } +} + // 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 tidy tool parses this information directly out of the source so take -// care when modifying it. -const KNOWN_FEATURES: &'static [(&'static str, &'static str, Option, Status)] = &[ - ("globs", "1.0.0", None, Accepted), - ("macro_rules", "1.0.0", None, Accepted), - ("struct_variant", "1.0.0", None, Accepted), - ("asm", "1.0.0", Some(29722), Active), - ("managed_boxes", "1.0.0", None, Removed), - ("non_ascii_idents", "1.0.0", Some(28979), Active), - ("thread_local", "1.0.0", Some(29594), Active), - ("link_args", "1.0.0", Some(29596), Active), - ("plugin_registrar", "1.0.0", Some(29597), Active), - ("log_syntax", "1.0.0", Some(29598), Active), - ("trace_macros", "1.0.0", Some(29598), Active), - ("concat_idents", "1.0.0", Some(29599), Active), +// NB: The featureck.py script parses this information directly out of the source +// so take care when modifying it. + +declare_features! ( + (active, asm, "1.0.0", Some(29722)), + (active, concat_idents, "1.0.0", Some(29599)), + (active, link_args, "1.0.0", Some(29596)), + (active, log_syntax, "1.0.0", Some(29598)), + (active, non_ascii_idents, "1.0.0", Some(28979)), + (active, plugin_registrar, "1.0.0", Some(29597)), + (active, thread_local, "1.0.0", Some(29594)), + (active, trace_macros, "1.0.0", Some(29598)), // rustc internal, for now: - ("intrinsics", "1.0.0", None, Active), - ("lang_items", "1.0.0", None, Active), + (active, intrinsics, "1.0.0", None), + (active, lang_items, "1.0.0", None), - ("simd", "1.0.0", Some(27731), Active), - ("default_type_params", "1.0.0", None, Accepted), - ("quote", "1.0.0", Some(29601), Active), - ("link_llvm_intrinsics", "1.0.0", Some(29602), Active), - ("linkage", "1.0.0", Some(29603), Active), - ("struct_inherit", "1.0.0", None, Removed), + (active, link_llvm_intrinsics, "1.0.0", Some(29602)), + (active, linkage, "1.0.0", Some(29603)), + (active, quote, "1.0.0", Some(29601)), + (active, simd, "1.0.0", Some(27731)), - ("quad_precision_float", "1.0.0", None, Removed), // rustc internal - ("rustc_diagnostic_macros", "1.0.0", None, Active), - ("unboxed_closures", "1.0.0", Some(29625), Active), - ("reflect", "1.0.0", Some(27749), Active), - ("import_shadowing", "1.0.0", None, Removed), - ("advanced_slice_patterns", "1.0.0", Some(23121), Active), - ("tuple_indexing", "1.0.0", None, Accepted), - ("associated_types", "1.0.0", None, Accepted), - ("visible_private_types", "1.0.0", None, Removed), - ("slicing_syntax", "1.0.0", None, Accepted), - ("box_syntax", "1.0.0", Some(27779), Active), - ("placement_in_syntax", "1.0.0", Some(27779), Active), + (active, rustc_diagnostic_macros, "1.0.0", None), + (active, advanced_slice_patterns, "1.0.0", Some(23121)), + (active, box_syntax, "1.0.0", Some(27779)), + (active, placement_in_syntax, "1.0.0", Some(27779)), + (active, reflect, "1.0.0", Some(27749)), + (active, unboxed_closures, "1.0.0", Some(29625)), // rustc internal. - ("pushpop_unsafe", "1.2.0", None, Active), - - ("on_unimplemented", "1.0.0", Some(29628), Active), - ("simd_ffi", "1.0.0", Some(27731), Active), - ("allocator", "1.0.0", Some(27389), Active), - ("needs_allocator", "1.4.0", Some(27389), Active), - ("linked_from", "1.3.0", Some(29629), Active), - - ("if_let", "1.0.0", None, Accepted), - ("while_let", "1.0.0", None, Accepted), - - ("plugin", "1.0.0", Some(29597), Active), - ("start", "1.0.0", Some(29633), Active), - ("main", "1.0.0", Some(29634), Active), - - ("fundamental", "1.0.0", Some(29635), Active), - - // A temporary feature gate used to enable parser extensions needed - // to bootstrap fix for #5723. - ("issue_5723_bootstrap", "1.0.0", None, Accepted), - - ("structural_match", "1.8.0", Some(31434), Active), - - // A way to temporarily opt out of opt in copy. This will *never* be accepted. - ("opt_out_copy", "1.0.0", None, Removed), + (active, pushpop_unsafe, "1.2.0", None), + + (active, allocator, "1.0.0", Some(27389)), + (active, fundamental, "1.0.0", Some(29635)), + (active, linked_from, "1.3.0", Some(29629)), + (active, main, "1.0.0", Some(29634)), + (active, needs_allocator, "1.4.0", Some(27389)), + (active, on_unimplemented, "1.0.0", Some(29628)), + (active, plugin, "1.0.0", Some(29597)), + (active, simd_ffi, "1.0.0", Some(27731)), + (active, start, "1.0.0", Some(29633)), + (active, structural_match, "1.8.0", Some(31434)), // OIBIT specific features - ("optin_builtin_traits", "1.0.0", Some(13231), Active), + (active, optin_builtin_traits, "1.0.0", Some(13231)), // macro reexport needs more discussion and stabilization - ("macro_reexport", "1.0.0", Some(29638), Active), - - // These are used to test this portion of the compiler, they don't actually - // mean anything - ("test_accepted_feature", "1.0.0", None, Accepted), - ("test_removed_feature", "1.0.0", None, Removed), + (active, macro_reexport, "1.0.0", Some(29638)), // Allows use of #[staged_api] // rustc internal - ("staged_api", "1.0.0", None, Active), + (active, staged_api, "1.0.0", None), // Allows using items which are missing stability attributes // rustc internal - ("unmarked_api", "1.0.0", None, Active), - - // Allows using #![no_std] - ("no_std", "1.0.0", None, Accepted), + (active, unmarked_api, "1.0.0", None), // Allows using #![no_core] - ("no_core", "1.3.0", Some(29639), Active), + (active, no_core, "1.3.0", Some(29639)), // Allows using `box` in patterns; RFC 469 - ("box_patterns", "1.0.0", Some(29641), Active), + (active, box_patterns, "1.0.0", Some(29641)), // Allows using the unsafe_no_drop_flag attribute (unlikely to // switch to Accepted; see RFC 320) - ("unsafe_no_drop_flag", "1.0.0", None, Active), + (active, unsafe_no_drop_flag, "1.0.0", None), // Allows using the unsafe_destructor_blind_to_params attribute; // RFC 1238 - ("dropck_parametricity", "1.3.0", Some(28498), Active), + (active, dropck_parametricity, "1.3.0", Some(28498)), // Allows the use of custom attributes; RFC 572 - ("custom_attribute", "1.0.0", Some(29642), Active), + (active, custom_attribute, "1.0.0", Some(29642)), // Allows the use of #[derive(Anything)] as sugar for // #[derive_Anything]. - ("custom_derive", "1.0.0", Some(29644), Active), + (active, custom_derive, "1.0.0", Some(29644)), // Allows the use of rustc_* attributes; RFC 572 - ("rustc_attrs", "1.0.0", Some(29642), Active), + (active, rustc_attrs, "1.0.0", Some(29642)), // Allows the use of #[allow_internal_unstable]. This is an // attribute on macro_rules! and can't use the attribute handling @@ -166,112 +186,171 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Option, Status // macros disappear). // // rustc internal - ("allow_internal_unstable", "1.0.0", None, Active), + (active, allow_internal_unstable, "1.0.0", None), // #23121. Array patterns have some hazards yet. - ("slice_patterns", "1.0.0", Some(23121), Active), - - // Allows use of unary negate on unsigned integers, e.g. -e for e: u8 - ("negate_unsigned", "1.0.0", Some(29645), Removed), + (active, slice_patterns, "1.0.0", Some(23121)), // Allows the definition of associated constants in `trait` or `impl` // blocks. - ("associated_consts", "1.0.0", Some(29646), Active), + (active, associated_consts, "1.0.0", Some(29646)), // Allows the definition of `const fn` functions. - ("const_fn", "1.2.0", Some(24111), Active), + (active, const_fn, "1.2.0", Some(24111)), // Allows indexing into constant arrays. - ("const_indexing", "1.4.0", Some(29947), Active), + (active, const_indexing, "1.4.0", Some(29947)), // Allows using #[prelude_import] on glob `use` items. // // rustc internal - ("prelude_import", "1.2.0", None, Active), + (active, prelude_import, "1.2.0", None), // Allows the definition recursive static items. - ("static_recursion", "1.3.0", Some(29719), Active), + (active, static_recursion, "1.3.0", Some(29719)), // Allows default type parameters to influence type inference. - ("default_type_parameter_fallback", "1.3.0", Some(27336), Active), + (active, default_type_parameter_fallback, "1.3.0", Some(27336)), // Allows associated type defaults - ("associated_type_defaults", "1.2.0", Some(29661), Active), + (active, associated_type_defaults, "1.2.0", Some(29661)), // Allows macros to appear in the type position. - ("type_macros", "1.3.0", Some(27245), Active), + (active, type_macros, "1.3.0", Some(27245)), // allow `repr(simd)`, and importing the various simd intrinsics - ("repr_simd", "1.4.0", Some(27731), Active), + (active, repr_simd, "1.4.0", Some(27731)), // Allows cfg(target_feature = "..."). - ("cfg_target_feature", "1.4.0", Some(29717), Active), + (active, cfg_target_feature, "1.4.0", Some(29717)), // allow `extern "platform-intrinsic" { ... }` - ("platform_intrinsics", "1.4.0", Some(27731), Active), + (active, platform_intrinsics, "1.4.0", Some(27731)), // allow `#[unwind]` // rust runtime internal - ("unwind_attributes", "1.4.0", None, Active), + (active, unwind_attributes, "1.4.0", None), // allow the use of `#[naked]` on functions. - ("naked_functions", "1.9.0", Some(32408), Active), - - // allow empty structs and enum variants with braces - ("braced_empty_structs", "1.8.0", Some(29720), Accepted), - - // allow overloading augmented assignment operations like `a += b` - ("augmented_assignments", "1.8.0", Some(28235), Accepted), + (active, naked_functions, "1.9.0", Some(32408)), // allow `#[no_debug]` - ("no_debug", "1.5.0", Some(29721), Active), + (active, no_debug, "1.5.0", Some(29721)), // allow `#[omit_gdb_pretty_printer_section]` // rustc internal. - ("omit_gdb_pretty_printer_section", "1.5.0", None, Active), + (active, omit_gdb_pretty_printer_section, "1.5.0", None), // Allows cfg(target_vendor = "..."). - ("cfg_target_vendor", "1.5.0", Some(29718), Active), + (active, cfg_target_vendor, "1.5.0", Some(29718)), // Allow attributes on expressions and non-item statements - ("stmt_expr_attributes", "1.6.0", Some(15701), Active), - - // Allows `#[deprecated]` attribute - ("deprecated", "1.9.0", Some(29935), Accepted), + (active, stmt_expr_attributes, "1.6.0", Some(15701)), // allow using type ascription in expressions - ("type_ascription", "1.6.0", Some(23416), Active), + (active, type_ascription, "1.6.0", Some(23416)), // Allows cfg(target_thread_local) - ("cfg_target_thread_local", "1.7.0", Some(29594), Active), + (active, cfg_target_thread_local, "1.7.0", Some(29594)), // rustc internal - ("abi_vectorcall", "1.7.0", None, Active), + (active, abi_vectorcall, "1.7.0", None), // a...b and ...b - ("inclusive_range_syntax", "1.7.0", Some(28237), Active), + (active, inclusive_range_syntax, "1.7.0", Some(28237)), // `expr?` - ("question_mark", "1.9.0", Some(31436), Active), + (active, question_mark, "1.9.0", Some(31436)), // impl specialization (RFC 1210) - ("specialization", "1.7.0", Some(31844), Active), + (active, specialization, "1.7.0", Some(31844)), // pub(restricted) visibilities (RFC 1422) - ("pub_restricted", "1.9.0", Some(32409), Active), -]; + (active, pub_restricted, "1.9.0", Some(32409)) +); + +declare_features! ( + (removed, import_shadowing, "1.0.0", None), + (removed, managed_boxes, "1.0.0", None), + // Allows use of unary negate on unsigned integers, e.g. -e for e: u8 + (removed, negate_unsigned, "1.0.0", Some(29645)), + // A way to temporarily opt out of opt in copy. This will *never* be accepted. + (removed, opt_out_copy, "1.0.0", None), + (removed, quad_precision_float, "1.0.0", None), + (removed, struct_inherit, "1.0.0", None), + (removed, test_removed_feature, "1.0.0", None), + (removed, visible_private_types, "1.0.0", None) +); + +declare_features! ( + (accepted, associated_types, "1.0.0", None), + // allow overloading augmented assignment operations like `a += b` + (accepted, augmented_assignments, "1.8.0", Some(28235)), + // allow empty structs and enum variants with braces + (accepted, braced_empty_structs, "1.8.0", Some(29720)), + (accepted, default_type_params, "1.0.0", None), + (accepted, globs, "1.0.0", None), + (accepted, if_let, "1.0.0", None), + // A temporary feature gate used to enable parser extensions needed + // to bootstrap fix for #5723. + (accepted, issue_5723_bootstrap, "1.0.0", None), + (accepted, macro_rules, "1.0.0", None), + // Allows using #![no_std] + (accepted, no_std, "1.0.0", None), + (accepted, slicing_syntax, "1.0.0", None), + (accepted, struct_variant, "1.0.0", None), + // These are used to test this portion of the compiler, they don't actually + // mean anything + (accepted, test_accepted_feature, "1.0.0", None), + (accepted, tuple_indexing, "1.0.0", None), + (accepted, while_let, "1.0.0", None), + // Allows `#[deprecated]` attribute + (accepted, deprecated, "1.9.0", Some(29935)) +); + // (changing above list without updating src/doc/reference.md makes @cmr sad) -enum Status { - /// Represents an active feature that is currently being implemented or - /// currently being considered for addition/removal. - Active, +#[derive(PartialEq, Copy, Clone, Debug)] +pub enum AttributeType { + /// Normal, builtin attribute that is consumed + /// by the compiler before the unused_attribute check + Normal, - /// Represents a feature which has since been removed (it was once Active) - Removed, + /// Builtin attribute that may not be consumed by the compiler + /// before the unused_attribute check. These attributes + /// will be ignored by the unused_attribute lint + Whitelisted, - /// This language feature has since been Accepted (it was once Active) - Accepted, + /// Builtin attribute that is only allowed at the crate level + CrateLevel, +} + +pub enum AttributeGate { + /// Is gated by a given feature gate, reason + /// and function to check if enabled + Gated(&'static str, &'static str, fn(&Features) -> bool), + + /// Ungated attribute, can be used on all release channels + Ungated, +} + +// fn() is not Debug +impl ::std::fmt::Debug for AttributeGate { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match *self { + Gated(ref name, ref expl, _) => write!(fmt, "Gated({}, {})", name, expl), + Ungated => write!(fmt, "Ungated") + } + } +} + +macro_rules! cfg_fn { + ($field: ident) => {{ + fn f(features: &Features) -> bool { + features.$field + } + f as fn(&Features) -> bool + }} } // Attributes that have a special meaning to rustc or rustdoc @@ -312,88 +391,112 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat // RFC #1445. ("structural_match", Whitelisted, Gated("structural_match", "the semantics of constant patterns is \ - not yet settled")), + not yet settled", + cfg_fn!(structural_match))), // Not used any more, but we can't feature gate it ("no_stack_check", Normal, Ungated), ("plugin", CrateLevel, Gated("plugin", "compiler plugins are experimental \ - and possibly buggy")), + and possibly buggy", + cfg_fn!(plugin))), + ("no_std", CrateLevel, Ungated), ("no_core", CrateLevel, Gated("no_core", - "no_core is experimental")), + "no_core is experimental", + cfg_fn!(no_core))), ("lang", Normal, Gated("lang_items", - "language items are subject to change")), + "language items are subject to change", + cfg_fn!(lang_items))), ("linkage", Whitelisted, Gated("linkage", "the `linkage` attribute is experimental \ - and not portable across platforms")), + and not portable across platforms", + cfg_fn!(linkage))), ("thread_local", Whitelisted, Gated("thread_local", "`#[thread_local]` is an experimental feature, and does \ not currently handle destructors. There is no \ corresponding `#[task_local]` mapping to the task \ - model")), + model", + cfg_fn!(thread_local))), ("rustc_on_unimplemented", Normal, Gated("on_unimplemented", "the `#[rustc_on_unimplemented]` attribute \ - is an experimental feature")), + is an experimental feature", + cfg_fn!(on_unimplemented))), ("allocator", Whitelisted, Gated("allocator", - "the `#[allocator]` attribute is an experimental feature")), + "the `#[allocator]` attribute is an experimental feature", + cfg_fn!(allocator))), ("needs_allocator", Normal, Gated("needs_allocator", "the `#[needs_allocator]` \ attribute is an experimental \ - feature")), + feature", + cfg_fn!(needs_allocator))), ("rustc_variance", Normal, Gated("rustc_attrs", "the `#[rustc_variance]` attribute \ is just used for rustc unit tests \ - and will never be stable")), + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_error", Whitelisted, Gated("rustc_attrs", "the `#[rustc_error]` attribute \ is just used for rustc unit tests \ - and will never be stable")), + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_if_this_changed", Whitelisted, Gated("rustc_attrs", - "the `#[rustc_if_this_changed]` attribute \ - is just used for rustc unit tests \ - and will never be stable")), + "the `#[rustc_if_this_changed]` attribute \ + is just used for rustc unit tests \ + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_then_this_would_need", Whitelisted, Gated("rustc_attrs", - "the `#[rustc_if_this_changed]` attribute \ - is just used for rustc unit tests \ - and will never be stable")), + "the `#[rustc_if_this_changed]` attribute \ + is just used for rustc unit tests \ + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_dirty", Whitelisted, Gated("rustc_attrs", "the `#[rustc_dirty]` attribute \ is just used for rustc unit tests \ - and will never be stable")), + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_clean", Whitelisted, Gated("rustc_attrs", "the `#[rustc_clean]` attribute \ is just used for rustc unit tests \ - and will never be stable")), + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_symbol_name", Whitelisted, Gated("rustc_attrs", - "internal rustc attributes will never be stable")), + "internal rustc attributes will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_item_path", Whitelisted, Gated("rustc_attrs", - "internal rustc attributes will never be stable")), + "internal rustc attributes will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_move_fragments", Normal, Gated("rustc_attrs", "the `#[rustc_move_fragments]` attribute \ is just used for rustc unit tests \ - and will never be stable")), + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_mir", Whitelisted, Gated("rustc_attrs", "the `#[rustc_mir]` attribute \ is just used for rustc unit tests \ - and will never be stable")), + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_no_mir", Whitelisted, Gated("rustc_attrs", "the `#[rustc_no_mir]` attribute \ is just used to make tests pass \ - and will never be stable")), + and will never be stable", + cfg_fn!(rustc_attrs))), ("allow_internal_unstable", Normal, Gated("allow_internal_unstable", - EXPLAIN_ALLOW_INTERNAL_UNSTABLE)), + EXPLAIN_ALLOW_INTERNAL_UNSTABLE, + cfg_fn!(allow_internal_unstable))), ("fundamental", Whitelisted, Gated("fundamental", "the `#[fundamental]` attribute \ - is an experimental feature")), + is an experimental feature", + cfg_fn!(fundamental))), ("linked_from", Normal, Gated("linked_from", "the `#[linked_from]` attribute \ - is an experimental feature")), + is an experimental feature", + cfg_fn!(linked_from))), // FIXME: #14408 whitelist docs since rustdoc looks at them ("doc", Whitelisted, Ungated), @@ -403,7 +506,8 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat ("cold", Whitelisted, Ungated), ("naked", Whitelisted, Gated("naked_functions", "the `#[naked]` attribute \ - is an experimental feature")), + is an experimental feature", + cfg_fn!(naked_functions))), ("export_name", Whitelisted, Ungated), ("inline", Whitelisted, Ungated), ("link", Whitelisted, Ungated), @@ -413,24 +517,30 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat ("no_mangle", Whitelisted, Ungated), ("no_debug", Whitelisted, Gated("no_debug", "the `#[no_debug]` attribute \ - is an experimental feature")), + is an experimental feature", + cfg_fn!(no_debug))), ("omit_gdb_pretty_printer_section", Whitelisted, Gated("omit_gdb_pretty_printer_section", "the `#[omit_gdb_pretty_printer_section]` \ attribute is just used for the Rust test \ - suite")), + suite", + cfg_fn!(omit_gdb_pretty_printer_section))), ("unsafe_no_drop_flag", Whitelisted, Gated("unsafe_no_drop_flag", "unsafe_no_drop_flag has unstable semantics \ - and may be removed in the future")), + and may be removed in the future", + cfg_fn!(unsafe_no_drop_flag))), ("unsafe_destructor_blind_to_params", Normal, Gated("dropck_parametricity", "unsafe_destructor_blind_to_params has unstable semantics \ - and may be removed in the future")), - ("unwind", Whitelisted, Gated("unwind_attributes", "#[unwind] is experimental")), + and may be removed in the future", + cfg_fn!(dropck_parametricity))), + ("unwind", Whitelisted, Gated("unwind_attributes", "#[unwind] is experimental", + cfg_fn!(unwind_attributes))), // used in resolve ("prelude_import", Whitelisted, Gated("prelude_import", - "`#[prelude_import]` is for use by rustc only")), + "`#[prelude_import]` is for use by rustc only", + cfg_fn!(prelude_import))), // FIXME: #14407 these are only looked at on-demand so we can't // guarantee they'll have already been checked @@ -441,9 +551,11 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat ("deprecated", Normal, Ungated), ("rustc_paren_sugar", Normal, Gated("unboxed_closures", - "unboxed_closures are still evolving")), + "unboxed_closures are still evolving", + cfg_fn!(unboxed_closures))), ("rustc_reflect_like", Whitelisted, Gated("reflect", - "defining reflective traits is still evolving")), + "defining reflective traits is still evolving", + cfg_fn!(reflect))), // Crate level attributes ("crate_name", CrateLevel, Ungated), @@ -456,21 +568,12 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat ("recursion_limit", CrateLevel, Ungated), ]; -macro_rules! cfg_fn { - (|$x: ident| $e: expr) => {{ - fn f($x: &Features) -> bool { - $e - } - f as fn(&Features) -> bool - }} -} // cfg(...)'s that are feature gated const GATED_CFGS: &'static [(&'static str, &'static str, fn(&Features) -> bool)] = &[ // (name in cfg, feature, function to check if the feature is enabled) - ("target_feature", "cfg_target_feature", cfg_fn!(|x| x.cfg_target_feature)), - ("target_vendor", "cfg_target_vendor", cfg_fn!(|x| x.cfg_target_vendor)), - ("target_thread_local", "cfg_target_thread_local", - cfg_fn!(|x| x.cfg_target_thread_local)), + ("target_feature", "cfg_target_feature", cfg_fn!(cfg_target_feature)), + ("target_vendor", "cfg_target_vendor", cfg_fn!(cfg_target_vendor)), + ("target_thread_local", "cfg_target_thread_local", cfg_fn!(cfg_target_thread_local)), ]; #[derive(Debug, Eq, PartialEq)] @@ -551,172 +654,38 @@ impl GatedCfg { } } - -#[derive(PartialEq, Copy, Clone, Debug)] -pub enum AttributeType { - /// Normal, builtin attribute that is consumed - /// by the compiler before the unused_attribute check - Normal, - - /// Builtin attribute that may not be consumed by the compiler - /// before the unused_attribute check. These attributes - /// will be ignored by the unused_attribute lint - Whitelisted, - - /// Builtin attribute that is only allowed at the crate level - CrateLevel, -} - -#[derive(PartialEq, Copy, Clone, Debug)] -pub enum AttributeGate { - /// Is gated by a given feature gate and reason - Gated(&'static str, &'static str), - - /// Ungated attribute, can be used on all release channels - Ungated, -} - -/// A set of features to be used by later passes. -pub struct Features { - pub unboxed_closures: bool, - pub rustc_diagnostic_macros: bool, - pub allow_quote: bool, - pub allow_asm: bool, - pub allow_log_syntax: bool, - pub allow_concat_idents: bool, - pub allow_trace_macros: bool, - pub allow_internal_unstable: bool, - pub allow_custom_derive: bool, - pub allow_placement_in: bool, - pub allow_box: bool, - pub allow_pushpop_unsafe: bool, - pub allow_inclusive_range: bool, - pub simd_ffi: bool, - pub unmarked_api: bool, - /// spans of #![feature] attrs for stable language features. for error reporting - pub declared_stable_lang_features: Vec, - /// #![feature] attrs for non-language (library) features - pub declared_lib_features: Vec<(InternedString, Span)>, - pub const_fn: bool, - pub const_indexing: bool, - pub static_recursion: bool, - pub default_type_parameter_fallback: bool, - pub rustc_attrs: bool, - pub type_macros: bool, - pub cfg_target_feature: bool, - pub cfg_target_vendor: bool, - pub cfg_target_thread_local: bool, - pub staged_api: bool, - pub stmt_expr_attributes: bool, - pub deprecated: bool, - pub question_mark: bool, - pub specialization: bool, - pub pub_restricted: bool, -} - -impl Features { - pub fn new() -> Features { - Features { - unboxed_closures: false, - rustc_diagnostic_macros: false, - allow_quote: false, - allow_asm: false, - allow_log_syntax: false, - allow_concat_idents: false, - allow_trace_macros: false, - allow_internal_unstable: false, - allow_custom_derive: false, - allow_placement_in: false, - allow_box: false, - allow_pushpop_unsafe: false, - allow_inclusive_range: false, - simd_ffi: false, - unmarked_api: false, - declared_stable_lang_features: Vec::new(), - declared_lib_features: Vec::new(), - const_fn: false, - const_indexing: false, - static_recursion: false, - default_type_parameter_fallback: false, - rustc_attrs: false, - type_macros: false, - cfg_target_feature: false, - cfg_target_vendor: false, - cfg_target_thread_local: false, - staged_api: false, - stmt_expr_attributes: false, - deprecated: false, - question_mark: false, - specialization: false, - pub_restricted: false, - } - } -} - -const EXPLAIN_BOX_SYNTAX: &'static str = - "box expression syntax is experimental; you can call `Box::new` instead."; - -const EXPLAIN_PLACEMENT_IN: &'static str = - "placement-in expression syntax is experimental and subject to change."; - -const EXPLAIN_PUSHPOP_UNSAFE: &'static str = - "push/pop_unsafe macros are experimental and subject to change."; - -const EXPLAIN_STMT_ATTR_SYNTAX: &'static str = - "attributes on non-item statements and expressions are experimental."; - -pub fn check_for_box_syntax(f: Option<&Features>, diag: &Handler, span: Span) { - if let Some(&Features { allow_box: true, .. }) = f { - return; - } - emit_feature_err(diag, "box_syntax", span, GateIssue::Language, EXPLAIN_BOX_SYNTAX); -} - -pub fn check_for_placement_in(f: Option<&Features>, diag: &Handler, span: Span) { - if let Some(&Features { allow_placement_in: true, .. }) = f { - return; - } - emit_feature_err(diag, "placement_in_syntax", span, GateIssue::Language, EXPLAIN_PLACEMENT_IN); -} - -pub fn check_for_pushpop_syntax(f: Option<&Features>, diag: &Handler, span: Span) { - if let Some(&Features { allow_pushpop_unsafe: true, .. }) = f { - return; - } - emit_feature_err(diag, "pushpop_unsafe", span, GateIssue::Language, EXPLAIN_PUSHPOP_UNSAFE); -} - struct Context<'a> { - features: Vec<&'static str>, + features: &'a Features, span_handler: &'a Handler, cm: &'a CodeMap, plugin_attributes: &'a [(String, AttributeType)], } -impl<'a> Context<'a> { - fn enable_feature(&mut self, feature: &'static str) { - debug!("enabling feature: {}", feature); - self.features.push(feature); - } - - fn gate_feature(&self, feature: &str, span: Span, explain: &str) { - let has_feature = self.has_feature(feature); - debug!("gate_feature(feature = {:?}, span = {:?}); has? {}", feature, span, has_feature); - if !has_feature && !self.cm.span_allows_unstable(span) { - emit_feature_err(self.span_handler, feature, span, GateIssue::Language, explain); +macro_rules! gate_feature_fn { + ($cx: expr, $has_feature: expr, $span: expr, $name: expr, $explain: expr) => {{ + let (cx, has_feature, span, name, explain) = ($cx, $has_feature, $span, $name, $explain); + let has_feature: bool = has_feature(&$cx.features); + debug!("gate_feature(feature = {:?}, span = {:?}); has? {}", name, span, has_feature); + if !has_feature && !cx.cm.span_allows_unstable(span) { + emit_feature_err(cx.span_handler, name, span, GateIssue::Language, explain); } + }} +} + +macro_rules! gate_feature { + ($cx: expr, $feature: ident, $span: expr, $explain: expr) => { + gate_feature_fn!($cx, |x:&Features| x.$feature, $span, stringify!($feature), $explain) } - fn has_feature(&self, feature: &str) -> bool { - self.features.iter().any(|&n| n == feature) - } +} +impl<'a> Context<'a> { fn check_attribute(&self, attr: &ast::Attribute, is_macro: bool) { debug!("check_attribute(attr = {:?})", attr); let name = &*attr.name(); - for &(n, ty, gateage) in KNOWN_ATTRIBUTES { + for &(n, ty, ref gateage) in KNOWN_ATTRIBUTES { if n == name { - if let Gated(gate, desc) = gateage { - self.gate_feature(gate, attr.span, desc); + if let &Gated(ref name, ref desc, ref has_feature) = gateage { + gate_feature_fn!(self, has_feature, attr.span, name, desc); } debug!("check_attribute: {:?} is known, {:?}, {:?}", name, ty, gateage); return; @@ -732,41 +701,50 @@ impl<'a> Context<'a> { } } if name.starts_with("rustc_") { - self.gate_feature("rustc_attrs", attr.span, - "unless otherwise specified, attributes \ - with the prefix `rustc_` \ - are reserved for internal compiler diagnostics"); + gate_feature!(self, rustc_attrs, attr.span, + "unless otherwise specified, attributes \ + with the prefix `rustc_` \ + are reserved for internal compiler diagnostics"); } else if name.starts_with("derive_") { - self.gate_feature("custom_derive", attr.span, - "attributes of the form `#[derive_*]` are reserved \ - for the compiler"); + gate_feature!(self, custom_derive, attr.span, EXPLAIN_DERIVE_UNDERSCORE); } else { // Only run the custom attribute lint during regular // feature gate checking. Macro gating runs // before the plugin attributes are registered // so we skip this then if !is_macro { - self.gate_feature("custom_attribute", attr.span, - &format!("The attribute `{}` is currently \ - unknown to the compiler and \ - may have meaning \ - added to it in the future", - name)); + gate_feature!(self, custom_attribute, attr.span, + &format!("The attribute `{}` is currently \ + unknown to the compiler and \ + may have meaning \ + added to it in the future", + name)); } } } } +pub fn check_attribute(attr: &ast::Attribute, handler: &Handler, + cm: &CodeMap, features: &Features) { + let cx = Context { + features: features, span_handler: handler, + cm: cm, plugin_attributes: &[] + }; + cx.check_attribute(attr, true); +} + fn find_lang_feature_issue(feature: &str) -> Option { - let info = KNOWN_FEATURES.iter() - .find(|t| t.0 == feature) - .unwrap(); - let issue = info.2; - if let Active = info.3 { + if let Some(info) = ACTIVE_FEATURES.iter().find(|t| t.0 == feature) { + let issue = info.2; // FIXME (#28244): enforce that active features have issue numbers // assert!(issue.is_some()) + issue + } else { + // search in Accepted or Removed features + ACCEPTED_FEATURES.iter().chain(REMOVED_FEATURES.iter()) + .find(|t| t.0 == feature) + .unwrap().2 } - issue } pub enum GateIssue { @@ -798,6 +776,12 @@ pub fn emit_feature_err(diag: &Handler, feature: &str, span: Span, issue: GateIs err.emit(); } +const EXPLAIN_BOX_SYNTAX: &'static str = + "box expression syntax is experimental; you can call `Box::new` instead."; + +const EXPLAIN_STMT_ATTR_SYNTAX: &'static str = + "attributes on non-item statements and expressions are experimental."; + pub const EXPLAIN_ASM: &'static str = "inline assembly is not stable enough for use and is subject to change"; @@ -815,75 +799,23 @@ pub const EXPLAIN_ALLOW_INTERNAL_UNSTABLE: &'static str = pub const EXPLAIN_CUSTOM_DERIVE: &'static str = "`#[derive]` for custom traits is not stable enough for use and is subject to change"; -struct MacroVisitor<'a> { - context: &'a Context<'a> -} - -impl<'a, 'v> Visitor<'v> for MacroVisitor<'a> { - fn visit_mac(&mut self, mac: &ast::Mac) { - let path = &mac.node.path; - let name = path.segments.last().unwrap().identifier.name.as_str(); - - // Issue 22234: If you add a new case here, make sure to also - // add code to catch the macro during or after expansion. - // - // We still keep this MacroVisitor (rather than *solely* - // relying on catching cases during or after expansion) to - // catch uses of these macros within conditionally-compiled - // code, e.g. `#[cfg]`-guarded functions. - - if name == "asm" { - self.context.gate_feature("asm", path.span, EXPLAIN_ASM); - } - - else if name == "log_syntax" { - self.context.gate_feature("log_syntax", path.span, EXPLAIN_LOG_SYNTAX); - } - - else if name == "trace_macros" { - self.context.gate_feature("trace_macros", path.span, EXPLAIN_TRACE_MACROS); - } - - else if name == "concat_idents" { - self.context.gate_feature("concat_idents", path.span, EXPLAIN_CONCAT_IDENTS); - } - } - - fn visit_attribute(&mut self, attr: &'v ast::Attribute) { - self.context.check_attribute(attr, true); - } - - fn visit_expr(&mut self, e: &ast::Expr) { - // Issue 22181: overloaded-`box` and placement-`in` are - // implemented via a desugaring expansion, so their feature - // gates go into MacroVisitor since that works pre-expansion. - // - // Issue 22234: we also check during expansion as well. - // But we keep these checks as a pre-expansion check to catch - // uses in e.g. conditionalized code. - - if let ast::ExprKind::Box(_) = e.node { - self.context.gate_feature("box_syntax", e.span, EXPLAIN_BOX_SYNTAX); - } - - if let ast::ExprKind::InPlace(..) = e.node { - self.context.gate_feature("placement_in_syntax", e.span, EXPLAIN_PLACEMENT_IN); - } +pub const EXPLAIN_DERIVE_UNDERSCORE: &'static str = + "attributes of the form `#[derive_*]` are reserved for the compiler"; - visit::walk_expr(self, e); - } -} +pub const EXPLAIN_PLACEMENT_IN: &'static str = + "placement-in expression syntax is experimental and subject to change."; struct PostExpansionVisitor<'a> { context: &'a Context<'a>, } -impl<'a> PostExpansionVisitor<'a> { - fn gate_feature(&self, feature: &str, span: Span, explain: &str) { - if !self.context.cm.span_allows_unstable(span) { - self.context.gate_feature(feature, span, explain) +macro_rules! gate_feature_post { + ($cx: expr, $feature: ident, $span: expr, $explain: expr) => {{ + let (cx, span) = ($cx, $span); + if !cx.context.cm.span_allows_unstable(span) { + gate_feature!(cx.context, $feature, span, $explain) } - } + }} } impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { @@ -895,8 +827,8 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { fn visit_name(&mut self, sp: Span, name: ast::Name) { if !name.as_str().is_ascii() { - self.gate_feature("non_ascii_idents", sp, - "non-ascii idents are not fully supported."); + gate_feature_post!(&self, non_ascii_idents, sp, + "non-ascii idents are not fully supported."); } } @@ -904,60 +836,59 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { match i.node { ast::ItemKind::ExternCrate(_) => { if attr::contains_name(&i.attrs[..], "macro_reexport") { - self.gate_feature("macro_reexport", i.span, - "macros reexports are experimental \ - and possibly buggy"); + gate_feature_post!(&self, macro_reexport, i.span, + "macros reexports are experimental \ + and possibly buggy"); } } ast::ItemKind::ForeignMod(ref foreign_module) => { if attr::contains_name(&i.attrs[..], "link_args") { - self.gate_feature("link_args", i.span, + gate_feature_post!(&self, link_args, i.span, "the `link_args` attribute is not portable \ across platforms, it is recommended to \ use `#[link(name = \"foo\")]` instead") } - let maybe_feature = match foreign_module.abi { - Abi::RustIntrinsic => Some(("intrinsics", "intrinsics are subject to change")), + match foreign_module.abi { + Abi::RustIntrinsic => + gate_feature_post!(&self, intrinsics, i.span, + "intrinsics are subject to change"), Abi::PlatformIntrinsic => { - Some(("platform_intrinsics", - "platform intrinsics are experimental and possibly buggy")) + gate_feature_post!(&self, platform_intrinsics, i.span, + "platform intrinsics are experimental \ + and possibly buggy") }, Abi::Vectorcall => { - Some(("abi_vectorcall", - "vectorcall is experimental and subject to change" - )) + gate_feature_post!(&self, abi_vectorcall, i.span, + "vectorcall is experimental and subject to change") } - _ => None - }; - if let Some((feature, msg)) = maybe_feature { - self.gate_feature(feature, i.span, msg) + _ => () } } ast::ItemKind::Fn(..) => { if attr::contains_name(&i.attrs[..], "plugin_registrar") { - self.gate_feature("plugin_registrar", i.span, - "compiler plugins are experimental and possibly buggy"); + gate_feature_post!(&self, plugin_registrar, i.span, + "compiler plugins are experimental and possibly buggy"); } if attr::contains_name(&i.attrs[..], "start") { - self.gate_feature("start", i.span, + gate_feature_post!(&self, start, i.span, "a #[start] function is an experimental \ feature whose signature may change \ over time"); } if attr::contains_name(&i.attrs[..], "main") { - self.gate_feature("main", i.span, - "declaration of a nonstandard #[main] \ - function may change over time, for now \ - a top-level `fn main()` is required"); + gate_feature_post!(&self, main, i.span, + "declaration of a nonstandard #[main] \ + function may change over time, for now \ + a top-level `fn main()` is required"); } } ast::ItemKind::Struct(..) => { if attr::contains_name(&i.attrs[..], "simd") { - self.gate_feature("simd", i.span, - "SIMD types are experimental and possibly buggy"); + gate_feature_post!(&self, simd, i.span, + "SIMD types are experimental and possibly buggy"); self.context.span_handler.span_warn(i.span, "the `#[simd]` attribute is deprecated, \ use `#[repr(simd)]` instead"); @@ -966,8 +897,9 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { if attr.name() == "repr" { for item in attr.meta_item_list().unwrap_or(&[]) { if item.name() == "simd" { - self.gate_feature("repr_simd", i.span, - "SIMD types are experimental and possibly buggy"); + gate_feature_post!(&self, repr_simd, i.span, + "SIMD types are experimental \ + and possibly buggy"); } } @@ -976,19 +908,19 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { } ast::ItemKind::DefaultImpl(..) => { - self.gate_feature("optin_builtin_traits", - i.span, - "default trait implementations are experimental \ - and possibly buggy"); + gate_feature_post!(&self, optin_builtin_traits, + i.span, + "default trait implementations are experimental \ + and possibly buggy"); } ast::ItemKind::Impl(_, polarity, _, _, _, _) => { match polarity { ast::ImplPolarity::Negative => { - self.gate_feature("optin_builtin_traits", - i.span, - "negative trait bounds are not yet fully implemented; \ - use marker types for now"); + gate_feature_post!(&self, optin_builtin_traits, + i.span, + "negative trait bounds are not yet fully implemented; \ + use marker types for now"); }, _ => {} } @@ -1001,7 +933,7 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { } fn visit_variant_data(&mut self, s: &'v ast::VariantData, _: ast::Ident, - _: &'v ast::Generics, _: ast::NodeId, span: Span) { + _: &'v ast::Generics, _: ast::NodeId, span: Span) { if s.fields().is_empty() { if s.is_tuple() { self.context.span_handler.struct_span_err(span, "empty tuple structs and enum \ @@ -1023,7 +955,7 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { _ => false }; if links_to_llvm { - self.gate_feature("link_llvm_intrinsics", i.span, + gate_feature_post!(&self, link_llvm_intrinsics, i.span, "linking to LLVM intrinsics is experimental"); } @@ -1033,22 +965,19 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { fn visit_expr(&mut self, e: &ast::Expr) { match e.node { ast::ExprKind::Box(_) => { - self.gate_feature("box_syntax", - e.span, - "box expression syntax is experimental; \ - you can call `Box::new` instead."); + gate_feature_post!(&self, box_syntax, e.span, EXPLAIN_BOX_SYNTAX); } ast::ExprKind::Type(..) => { - self.gate_feature("type_ascription", e.span, + gate_feature_post!(&self, type_ascription, e.span, "type ascription is experimental"); } ast::ExprKind::Range(_, _, ast::RangeLimits::Closed) => { - self.gate_feature("inclusive_range_syntax", + gate_feature_post!(&self, inclusive_range_syntax, e.span, "inclusive range syntax is experimental"); } ast::ExprKind::Try(..) => { - self.gate_feature("question_mark", e.span, "the `?` operator is not stable"); + gate_feature_post!(&self, question_mark, e.span, "the `?` operator is not stable"); } _ => {} } @@ -1058,19 +987,19 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { fn visit_pat(&mut self, pattern: &ast::Pat) { match pattern.node { PatKind::Vec(_, Some(_), ref last) if !last.is_empty() => { - self.gate_feature("advanced_slice_patterns", + gate_feature_post!(&self, advanced_slice_patterns, pattern.span, "multiple-element slice matches anywhere \ but at the end of a slice (e.g. \ `[0, ..xs, 0]`) are experimental") } PatKind::Vec(..) => { - self.gate_feature("slice_patterns", + gate_feature_post!(&self, slice_patterns, pattern.span, "slice pattern syntax is experimental"); } PatKind::Box(..) => { - self.gate_feature("box_patterns", + gate_feature_post!(&self, box_patterns, pattern.span, "box pattern syntax is experimental"); } @@ -1088,7 +1017,7 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { // check for const fn declarations match fn_kind { FnKind::ItemFn(_, _, _, ast::Constness::Const, _, _) => { - self.gate_feature("const_fn", span, "const fn is unstable"); + gate_feature_post!(&self, const_fn, span, "const fn is unstable"); } _ => { // stability of const fn methods are covered in @@ -1100,18 +1029,18 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { match fn_kind { FnKind::ItemFn(_, _, _, _, abi, _) if abi == Abi::RustIntrinsic => { - self.gate_feature("intrinsics", + gate_feature_post!(&self, intrinsics, span, "intrinsics are subject to change") } FnKind::ItemFn(_, _, _, _, abi, _) | FnKind::Method(_, &ast::MethodSig { abi, .. }, _) => match abi { Abi::RustCall => { - self.gate_feature("unboxed_closures", span, + gate_feature_post!(&self, unboxed_closures, span, "rust-call ABI is subject to change"); }, Abi::Vectorcall => { - self.gate_feature("abi_vectorcall", span, + gate_feature_post!(&self, abi_vectorcall, span, "vectorcall is experimental and subject to change"); }, _ => {} @@ -1124,17 +1053,17 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { fn visit_trait_item(&mut self, ti: &'v ast::TraitItem) { match ti.node { ast::TraitItemKind::Const(..) => { - self.gate_feature("associated_consts", + gate_feature_post!(&self, associated_consts, ti.span, "associated constants are experimental") } ast::TraitItemKind::Method(ref sig, _) => { if sig.constness == ast::Constness::Const { - self.gate_feature("const_fn", ti.span, "const fn is unstable"); + gate_feature_post!(&self, const_fn, ti.span, "const fn is unstable"); } } ast::TraitItemKind::Type(_, Some(_)) => { - self.gate_feature("associated_type_defaults", ti.span, + gate_feature_post!(&self, associated_type_defaults, ti.span, "associated type defaults are unstable"); } _ => {} @@ -1144,20 +1073,20 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { fn visit_impl_item(&mut self, ii: &'v ast::ImplItem) { if ii.defaultness == ast::Defaultness::Default { - self.gate_feature("specialization", + gate_feature_post!(&self, specialization, ii.span, "specialization is unstable"); } match ii.node { ast::ImplItemKind::Const(..) => { - self.gate_feature("associated_consts", + gate_feature_post!(&self, associated_consts, ii.span, "associated constants are experimental") } ast::ImplItemKind::Method(ref sig, _) => { if sig.constness == ast::Constness::Const { - self.gate_feature("const_fn", ii.span, "const fn is unstable"); + gate_feature_post!(&self, const_fn, ii.span, "const fn is unstable"); } } _ => {} @@ -1183,26 +1112,12 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { } _ => return, }; - self.gate_feature("pub_restricted", span, "`pub(restricted)` syntax is experimental"); + gate_feature_post!(&self, pub_restricted, span, "`pub(restricted)` syntax is experimental"); } } -fn check_crate_inner(cm: &CodeMap, span_handler: &Handler, - krate: &ast::Crate, - plugin_attributes: &[(String, AttributeType)], - check: F) - -> Features - where F: FnOnce(&mut Context, &ast::Crate) -{ - let mut cx = Context { - features: Vec::new(), - span_handler: span_handler, - cm: cm, - plugin_attributes: plugin_attributes, - }; - - let mut accepted_features = Vec::new(); - let mut unknown_features = Vec::new(); +pub fn get_features(span_handler: &Handler, krate: &ast::Crate) -> Features { + let mut features = Features::new(); for attr in &krate.attrs { if !attr.check_name("feature") { @@ -1225,82 +1140,43 @@ fn check_crate_inner(cm: &CodeMap, span_handler: &Handler, continue } }; - match KNOWN_FEATURES.iter() - .find(|& &(n, _, _, _)| name == n) { - Some(&(name, _, _, Active)) => { - cx.enable_feature(name); - } - Some(&(_, _, _, Removed)) => { - span_handler.span_err(mi.span, "feature has been removed"); - } - Some(&(_, _, _, Accepted)) => { - accepted_features.push(mi.span); - } - None => { - unknown_features.push((name, mi.span)); - } + if let Some(&(_, _, _, setter)) = ACTIVE_FEATURES.iter() + .find(|& &(n, _, _, _)| name == n) { + *(setter(&mut features)) = true; + } + else if let Some(&(_, _, _)) = REMOVED_FEATURES.iter() + .find(|& &(n, _, _)| name == n) { + span_handler.span_err(mi.span, "feature has been removed"); + } + else if let Some(&(_, _, _)) = ACCEPTED_FEATURES.iter() + .find(|& &(n, _, _)| name == n) { + features.declared_stable_lang_features.push(mi.span); + } else { + features.declared_lib_features.push((name, mi.span)); } } } } } - check(&mut cx, krate); - - // FIXME (pnkfelix): Before adding the 99th entry below, change it - // to a single-pass (instead of N calls to `.has_feature`). - - Features { - unboxed_closures: cx.has_feature("unboxed_closures"), - rustc_diagnostic_macros: cx.has_feature("rustc_diagnostic_macros"), - allow_quote: cx.has_feature("quote"), - allow_asm: cx.has_feature("asm"), - allow_log_syntax: cx.has_feature("log_syntax"), - allow_concat_idents: cx.has_feature("concat_idents"), - allow_trace_macros: cx.has_feature("trace_macros"), - allow_internal_unstable: cx.has_feature("allow_internal_unstable"), - allow_custom_derive: cx.has_feature("custom_derive"), - allow_placement_in: cx.has_feature("placement_in_syntax"), - allow_box: cx.has_feature("box_syntax"), - allow_pushpop_unsafe: cx.has_feature("pushpop_unsafe"), - allow_inclusive_range: cx.has_feature("inclusive_range_syntax"), - simd_ffi: cx.has_feature("simd_ffi"), - unmarked_api: cx.has_feature("unmarked_api"), - declared_stable_lang_features: accepted_features, - declared_lib_features: unknown_features, - const_fn: cx.has_feature("const_fn"), - const_indexing: cx.has_feature("const_indexing"), - static_recursion: cx.has_feature("static_recursion"), - default_type_parameter_fallback: cx.has_feature("default_type_parameter_fallback"), - rustc_attrs: cx.has_feature("rustc_attrs"), - type_macros: cx.has_feature("type_macros"), - cfg_target_feature: cx.has_feature("cfg_target_feature"), - cfg_target_vendor: cx.has_feature("cfg_target_vendor"), - cfg_target_thread_local: cx.has_feature("cfg_target_thread_local"), - staged_api: cx.has_feature("staged_api"), - stmt_expr_attributes: cx.has_feature("stmt_expr_attributes"), - deprecated: cx.has_feature("deprecated"), - question_mark: cx.has_feature("question_mark"), - specialization: cx.has_feature("specialization"), - pub_restricted: cx.has_feature("pub_restricted"), - } -} - -pub fn check_crate_macros(cm: &CodeMap, span_handler: &Handler, krate: &ast::Crate) --> Features { - check_crate_inner(cm, span_handler, krate, &[] as &'static [_], - |ctx, krate| visit::walk_crate(&mut MacroVisitor { context: ctx }, krate)) + features } pub fn check_crate(cm: &CodeMap, span_handler: &Handler, krate: &ast::Crate, plugin_attributes: &[(String, AttributeType)], - unstable: UnstableFeatures) -> Features -{ + unstable: UnstableFeatures) -> Features { maybe_stage_features(span_handler, krate, unstable); - - check_crate_inner(cm, span_handler, krate, plugin_attributes, - |ctx, krate| visit::walk_crate(&mut PostExpansionVisitor { context: ctx }, - krate)) + let features = get_features(span_handler, krate); + { + let ctx = Context { + features: &features, + span_handler: span_handler, + cm: cm, + plugin_attributes: plugin_attributes, + }; + visit::walk_crate(&mut PostExpansionVisitor { context: &ctx }, krate); + } + features } #[derive(Clone, Copy)] diff --git a/src/libsyntax_ext/deriving/mod.rs b/src/libsyntax_ext/deriving/mod.rs index 92a141fb4ec86..4ca3196b9c5ec 100644 --- a/src/libsyntax_ext/deriving/mod.rs +++ b/src/libsyntax_ext/deriving/mod.rs @@ -96,6 +96,36 @@ fn expand_derive(cx: &mut ExtCtxt, let mut found_partial_eq = false; let mut found_eq = false; + // This span is **very** sensitive and crucial to + // getting the stability behavior we want. What we are + // doing is marking the generated `#[derive_*]` with the + // span of the `#[deriving(...)]` attribute (the + // entire attribute, not just the `PartialEq` or `Eq` + // part), but with the current backtrace. The current + // backtrace will contain a topmost entry that IS this + // `#[deriving(...)]` attribute and with the + // "allow-unstable" flag set to true. + // + // Note that we do NOT use the span of the `Eq` + // text itself. You might think this is + // equivalent, because the `Eq` appears within the + // `#[deriving(Eq)]` attribute, and hence we would + // inherit the "allows unstable" from the + // backtrace. But in fact this is not always the + // case. The actual source text that led to + // deriving can be `#[$attr]`, for example, where + // `$attr == deriving(Eq)`. In that case, the + // "#[derive_*]" would be considered to + // originate not from the deriving call but from + // text outside the deriving call, and hence would + // be forbidden from using unstable + // content. + // + // See tests src/run-pass/rfc1445 for + // examples. --nmatsakis + let span = Span { expn_id: cx.backtrace(), .. span }; + assert!(cx.parse_sess.codemap().span_allows_unstable(span)); + for titem in traits.iter().rev() { let tname = match titem.node { MetaItemKind::Word(ref tname) => tname, @@ -121,42 +151,13 @@ fn expand_derive(cx: &mut ExtCtxt, } // #[derive(Foo, Bar)] expands to #[derive_Foo] #[derive_Bar] - item.attrs.push(cx.attribute(titem.span, cx.meta_word(titem.span, + item.attrs.push(cx.attribute(span, cx.meta_word(titem.span, intern_and_get_ident(&format!("derive_{}", tname))))); } // RFC #1445. `#[derive(PartialEq, Eq)]` adds a (trusted) // `#[structural_match]` attribute. if found_partial_eq && found_eq { - // This span is **very** sensitive and crucial to - // getting the stability behavior we want. What we are - // doing is marking `#[structural_match]` with the - // span of the `#[deriving(...)]` attribute (the - // entire attribute, not just the `PartialEq` or `Eq` - // part), but with the current backtrace. The current - // backtrace will contain a topmost entry that IS this - // `#[deriving(...)]` attribute and with the - // "allow-unstable" flag set to true. - // - // Note that we do NOT use the span of the `Eq` - // text itself. You might think this is - // equivalent, because the `Eq` appears within the - // `#[deriving(Eq)]` attribute, and hence we would - // inherit the "allows unstable" from the - // backtrace. But in fact this is not always the - // case. The actual source text that led to - // deriving can be `#[$attr]`, for example, where - // `$attr == deriving(Eq)`. In that case, the - // "#[structural_match]" would be considered to - // originate not from the deriving call but from - // text outside the deriving call, and hence would - // be forbidden from using unstable - // content. - // - // See tests src/run-pass/rfc1445 for - // examples. --nmatsakis - let span = Span { expn_id: cx.backtrace(), .. span }; - assert!(cx.parse_sess.codemap().span_allows_unstable(span)); debug!("inserting structural_match with span {:?}", span); let structural_match = intern_and_get_ident("structural_match"); item.attrs.push(cx.attribute(span, @@ -188,6 +189,39 @@ macro_rules! derive_traits { mitem: &MetaItem, annotatable: &Annotatable, push: &mut FnMut(Annotatable)) { + if !ecx.parse_sess.codemap().span_allows_unstable(sp) + && !ecx.ecfg.features.unwrap().custom_derive { + // FIXME: + // /~https://github.com/rust-lang/rust/pull/32671#issuecomment-206245303 + // This is just to avoid breakage with syntex. + // Remove that to spawn an error instead. + let cm = ecx.parse_sess.codemap(); + let parent = cm.with_expn_info(ecx.backtrace(), + |info| info.unwrap().call_site.expn_id); + cm.with_expn_info(parent, |info| { + if info.is_some() { + let mut w = ecx.parse_sess.span_diagnostic.struct_span_warn( + sp, feature_gate::EXPLAIN_DERIVE_UNDERSCORE, + ); + if option_env!("CFG_DISABLE_UNSTABLE_FEATURES").is_none() { + w.fileline_help( + sp, &format!("add #![feature(custom_derive)] to \ + the crate attributes to enable") + ); + } + w.emit(); + } else { + feature_gate::emit_feature_err( + &ecx.parse_sess.span_diagnostic, + "custom_derive", sp, feature_gate::GateIssue::Language, + feature_gate::EXPLAIN_DERIVE_UNDERSCORE + ); + + return; + } + }) + } + warn_if_deprecated(ecx, sp, $name); $func(ecx, sp, mitem, annotatable, push); } diff --git a/src/test/compile-fail/feature-gate-allow-internal-unstable-nested-macro.rs b/src/test/compile-fail/feature-gate-allow-internal-unstable-nested-macro.rs index c9251c925cc40..9ebf8a9b74a6c 100644 --- a/src/test/compile-fail/feature-gate-allow-internal-unstable-nested-macro.rs +++ b/src/test/compile-fail/feature-gate-allow-internal-unstable-nested-macro.rs @@ -11,8 +11,8 @@ macro_rules! bar { () => { // more layers don't help: - #[allow_internal_unstable] - macro_rules! baz { //~ ERROR allow_internal_unstable side-steps + #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps + macro_rules! baz { () => {} } } diff --git a/src/test/compile-fail/trace_macros-gate2.rs b/src/test/compile-fail/feature-gate-allow-internal-unstable-struct.rs similarity index 54% rename from src/test/compile-fail/trace_macros-gate2.rs rename to src/test/compile-fail/feature-gate-allow-internal-unstable-struct.rs index 71cc45e132d33..b186278ef8b7b 100644 --- a/src/test/compile-fail/trace_macros-gate2.rs +++ b/src/test/compile-fail/feature-gate-allow-internal-unstable-struct.rs @@ -1,4 +1,4 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -8,13 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// Test that the trace_macros feature gate is on. +// checks that this attribute is caught on non-macro items. +// this needs a different test since this is done after expansion -fn main() { - // (Infrastructure does not attempt to detect uses in macro definitions.) - macro_rules! expando { - ($x: ident) => { trace_macros!($x) } - } +#[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps +struct S; - expando!(true); //~ ERROR `trace_macros` is not stable -} +fn main() {} diff --git a/src/test/compile-fail/issue-32655.rs b/src/test/compile-fail/issue-32655.rs new file mode 100644 index 0000000000000..edd7fe4a1e588 --- /dev/null +++ b/src/test/compile-fail/issue-32655.rs @@ -0,0 +1,33 @@ +// Copyright 2016 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(dead_code)] +#![feature(rustc_attrs)] + +macro_rules! foo ( + () => ( + #[derive_Clone] //~ WARN attributes of the form + struct T; + ); +); + +macro_rules! bar ( + ($e:item) => ($e) +); + +foo!(); + +bar!( + #[derive_Clone] //~ WARN attributes of the form + struct S; +); + +#[rustc_error] +fn main() {} //~ ERROR compilation successful diff --git a/src/test/compile-fail/issue-32782.rs b/src/test/compile-fail/issue-32782.rs new file mode 100644 index 0000000000000..696ea0ef5473b --- /dev/null +++ b/src/test/compile-fail/issue-32782.rs @@ -0,0 +1,23 @@ +// Copyright 2016 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +macro_rules! bar ( + () => () +); + +macro_rules! foo ( + () => ( + #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps + bar!(); + ); +); + +foo!(); +fn main() {} diff --git a/src/test/compile-fail/trace_macros-gate.rs b/src/test/compile-fail/trace_macros-gate.rs index 6473bcece91b6..d627de24d6794 100644 --- a/src/test/compile-fail/trace_macros-gate.rs +++ b/src/test/compile-fail/trace_macros-gate.rs @@ -26,5 +26,5 @@ fn main() { ($x: ident) => { trace_macros!($x) } } - expando!(true); + expando!(true); //~ ERROR `trace_macros` is not stable } diff --git a/src/test/compile-fail/trace_macros-gate3.rs b/src/test/compile-fail/trace_macros-gate3.rs deleted file mode 100644 index 66d03cf9d8046..0000000000000 --- a/src/test/compile-fail/trace_macros-gate3.rs +++ /dev/null @@ -1,20 +0,0 @@ -// 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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// Test that the trace_macros feature gate is on. - -pub fn main() { - println!("arg: {}", trace_macros!()); //~ ERROR `trace_macros` is not stable - println!("arg: {}", trace_macros!(1)); //~ ERROR `trace_macros` is not stable - println!("arg: {}", trace_macros!(ident)); //~ ERROR `trace_macros` is not stable - println!("arg: {}", trace_macros!(for)); //~ ERROR `trace_macros` is not stable - println!("arg: {}", trace_macros!(true,)); //~ ERROR `trace_macros` is not stable - println!("arg: {}", trace_macros!(false 1)); //~ ERROR `trace_macros` is not stable -} diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs index 991cf201d44f0..0b989d92b3d1d 100644 --- a/src/tools/tidy/src/features.rs +++ b/src/tools/tidy/src/features.rs @@ -136,18 +136,18 @@ fn collect_lang_features(path: &Path) -> Vec { let mut features = Vec::new(); for line in contents.lines().map(|l| l.trim()) { - if !STATUSES.iter().any(|s| line.contains(s) && line.starts_with("(")) { + if !STATUSES.iter().any(|s| line.starts_with(&format!("({}", s))) { continue } let mut parts = line.split(","); - let name = parts.next().unwrap().replace("\"", "").replace("(", ""); - let since = parts.next().unwrap().trim().replace("\"", ""); - let status = match parts.skip(1).next().unwrap() { - s if s.contains("Active") => "unstable", - s if s.contains("Removed") => "unstable", - s if s.contains("Accepted") => "stable", + let status = match &parts.next().unwrap().trim().replace("(", "")[..] { + "active" => "unstable", + "removed" => "unstable", + "accepted" => "stable", s => panic!("unknown status: {}", s), }; + let name = parts.next().unwrap().trim().to_owned(); + let since = parts.next().unwrap().trim().replace("\"", ""); features.push(Feature { name: name,