diff --git a/src/doc/reference.md b/src/doc/reference.md index 95ccc71532a..a772d98583e 100644 --- a/src/doc/reference.md +++ b/src/doc/reference.md @@ -2429,6 +2429,10 @@ The currently implemented features of the reference compiler are: so that new attributes can be added in a bacwards compatible manner (RFC 572). +* `custom_derive` - Allows the use of `#[derive(Foo,Bar)]` as sugar for + `#[derive_Foo] #[derive_Bar]`, which can be user-defined syntax + extensions. + * `intrinsics` - Allows use of the "rust-intrinsics" ABI. Compiler intrinsics are inherently unstable and no promise about them is made. diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 5513b44235d..8aeafe419da 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -491,10 +491,8 @@ fn initial_syntax_expander_table<'feat>(ecfg: &expand::ExpansionConfig<'feat>) syntax_expanders.insert(intern("log_syntax"), builtin_normal_expander( ext::log_syntax::expand_syntax_ext)); - syntax_expanders.insert(intern("derive"), - Decorator(Box::new(ext::deriving::expand_meta_derive))); - syntax_expanders.insert(intern("deriving"), - Decorator(Box::new(ext::deriving::expand_deprecated_deriving))); + + ext::deriving::register_all(&mut syntax_expanders); if ecfg.enable_quotes() { // Quasi-quoting expanders diff --git a/src/libsyntax/ext/deriving/bounds.rs b/src/libsyntax/ext/deriving/bounds.rs index 93098484ae0..e408c99935d 100644 --- a/src/libsyntax/ext/deriving/bounds.rs +++ b/src/libsyntax/ext/deriving/bounds.rs @@ -8,47 +8,34 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use ast::{MetaItem, MetaWord, Item}; +use ast::{MetaItem, Item}; use codemap::Span; use ext::base::ExtCtxt; use ext::deriving::generic::*; use ext::deriving::generic::ty::*; use ptr::P; -pub fn expand_deriving_bound(cx: &mut ExtCtxt, - span: Span, - mitem: &MetaItem, - item: &Item, - push: F) where +pub fn expand_deriving_unsafe_bound(cx: &mut ExtCtxt, + span: Span, + _: &MetaItem, + _: &Item, + _: F) where F: FnOnce(P), { - let name = match mitem.node { - MetaWord(ref tname) => { - match &tname[..] { - "Copy" => "Copy", - "Send" | "Sync" => { - return cx.span_err(span, - &format!("{} is an unsafe trait and it \ - should be implemented explicitly", - *tname)) - } - ref tname => { - cx.span_bug(span, - &format!("expected built-in trait name but \ - found {}", *tname)) - } - } - }, - _ => { - return cx.span_err(span, "unexpected value in deriving, expected \ - a trait") - } - }; + cx.span_err(span, "this unsafe trait should be implemented explicitly"); +} +pub fn expand_deriving_copy(cx: &mut ExtCtxt, + span: Span, + mitem: &MetaItem, + item: &Item, + push: F) where + F: FnOnce(P), +{ let path = Path::new(vec![ if cx.use_std { "std" } else { "core" }, "marker", - name + "Copy", ]); let trait_def = TraitDef { diff --git a/src/libsyntax/ext/deriving/mod.rs b/src/libsyntax/ext/deriving/mod.rs index 973c8f5fa1e..2631c28cf2f 100644 --- a/src/libsyntax/ext/deriving/mod.rs +++ b/src/libsyntax/ext/deriving/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT +// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -13,9 +13,13 @@ //! FIXME (#2810): hygiene. Search for "__" strings (in other files too). We also assume "extra" is //! the standard library, and "std" is the core library. -use ast::{Item, MetaItem, MetaList, MetaNameValue, MetaWord}; -use ext::base::ExtCtxt; +use ast::{Item, MetaItem, MetaWord}; +use attr::AttrMetaMethods; +use ext::base::{ExtCtxt, SyntaxEnv, Decorator, ItemDecorator, Modifier}; +use ext::build::AstBuilder; +use feature_gate; use codemap::Span; +use parse::token::{intern, intern_and_get_ident}; use ptr::P; macro_rules! pathvec { @@ -74,101 +78,133 @@ pub mod totalord; pub mod generic; -pub fn expand_deprecated_deriving(cx: &mut ExtCtxt, - span: Span, - _: &MetaItem, - _: &Item, - _: &mut FnMut(P)) { +fn expand_deprecated_deriving(cx: &mut ExtCtxt, + span: Span, + _: &MetaItem, + _: &Item, + _: &mut FnMut(P)) { cx.span_err(span, "`deriving` has been renamed to `derive`"); } -pub fn expand_meta_derive(cx: &mut ExtCtxt, - _span: Span, - mitem: &MetaItem, - item: &Item, - push: &mut FnMut(P)) { - match mitem.node { - MetaNameValue(_, ref l) => { - cx.span_err(l.span, "unexpected value in `derive`"); +fn expand_derive(cx: &mut ExtCtxt, + _: Span, + mitem: &MetaItem, + item: P) -> P { + item.map(|mut item| { + if mitem.value_str().is_some() { + cx.span_err(mitem.span, "unexpected value in `derive`"); } - MetaWord(_) => { + + let traits = mitem.meta_item_list().unwrap_or(&[]); + if traits.is_empty() { cx.span_warn(mitem.span, "empty trait list in `derive`"); } - MetaList(_, ref titems) if titems.len() == 0 => { - cx.span_warn(mitem.span, "empty trait list in `derive`"); + + for titem in traits.iter().rev() { + let tname = match titem.node { + MetaWord(ref tname) => tname, + _ => { + cx.span_err(titem.span, "malformed `derive` entry"); + continue; + } + }; + + if !(is_builtin_trait(tname) || cx.ecfg.enable_custom_derive()) { + feature_gate::emit_feature_err(&cx.parse_sess.span_diagnostic, + "custom_derive", + titem.span, + feature_gate::EXPLAIN_CUSTOM_DERIVE); + continue; + } + + // #[derive(Foo, Bar)] expands to #[derive_Foo] #[derive_Bar] + item.attrs.push(cx.attribute(titem.span, cx.meta_word(titem.span, + intern_and_get_ident(&format!("derive_{}", tname))))); } - MetaList(_, ref titems) => { - for titem in titems.iter().rev() { - match titem.node { - MetaNameValue(ref tname, _) | - MetaList(ref tname, _) | - MetaWord(ref tname) => { - macro_rules! expand { - ($func:path) => ($func(cx, titem.span, &**titem, item, - |i| push(i))) - } - match &tname[..] { - "Clone" => expand!(clone::expand_deriving_clone), + item + }) +} - "Hash" => expand!(hash::expand_deriving_hash), +macro_rules! derive_traits { + ($( $name:expr => $func:path, )*) => { + pub fn register_all(env: &mut SyntaxEnv) { + // Define the #[derive_*] extensions. + $({ + struct DeriveExtension; - "RustcEncodable" => { - expand!(encodable::expand_deriving_rustc_encodable) - } - "RustcDecodable" => { - expand!(decodable::expand_deriving_rustc_decodable) - } - "Encodable" => { - cx.span_warn(titem.span, - "derive(Encodable) is deprecated \ - in favor of derive(RustcEncodable)"); - - expand!(encodable::expand_deriving_encodable) - } - "Decodable" => { - cx.span_warn(titem.span, - "derive(Decodable) is deprecated \ - in favor of derive(RustcDecodable)"); - - expand!(decodable::expand_deriving_decodable) - } - - "PartialEq" => expand!(eq::expand_deriving_eq), - "Eq" => expand!(totaleq::expand_deriving_totaleq), - "PartialOrd" => expand!(ord::expand_deriving_ord), - "Ord" => expand!(totalord::expand_deriving_totalord), - - "Rand" => expand!(rand::expand_deriving_rand), - - "Show" => { - cx.span_warn(titem.span, - "derive(Show) is deprecated \ - in favor of derive(Debug)"); - - expand!(show::expand_deriving_show) - }, - - "Debug" => expand!(show::expand_deriving_show), - - "Default" => expand!(default::expand_deriving_default), - - "FromPrimitive" => expand!(primitive::expand_deriving_from_primitive), - - "Send" => expand!(bounds::expand_deriving_bound), - "Sync" => expand!(bounds::expand_deriving_bound), - "Copy" => expand!(bounds::expand_deriving_bound), - - ref tname => { - cx.span_err(titem.span, - &format!("unknown `derive` \ - trait: `{}`", - *tname)); - } - }; + impl ItemDecorator for DeriveExtension { + fn expand(&self, + ecx: &mut ExtCtxt, + sp: Span, + mitem: &MetaItem, + item: &Item, + push: &mut FnMut(P)) { + warn_if_deprecated(ecx, sp, $name); + $func(ecx, sp, mitem, item, |i| push(i)); } } + + env.insert(intern(concat!("derive_", $name)), + Decorator(Box::new(DeriveExtension))); + })* + + env.insert(intern("derive"), + Modifier(Box::new(expand_derive))); + env.insert(intern("deriving"), + Decorator(Box::new(expand_deprecated_deriving))); + } + + fn is_builtin_trait(name: &str) -> bool { + match name { + $( $name )|* => true, + _ => false, } } } } + +derive_traits! { + "Clone" => clone::expand_deriving_clone, + + "Hash" => hash::expand_deriving_hash, + + "RustcEncodable" => encodable::expand_deriving_rustc_encodable, + + "RustcDecodable" => decodable::expand_deriving_rustc_decodable, + + "PartialEq" => eq::expand_deriving_eq, + "Eq" => totaleq::expand_deriving_totaleq, + "PartialOrd" => ord::expand_deriving_ord, + "Ord" => totalord::expand_deriving_totalord, + + "Rand" => rand::expand_deriving_rand, + + "Debug" => show::expand_deriving_show, + + "Default" => default::expand_deriving_default, + + "FromPrimitive" => primitive::expand_deriving_from_primitive, + + "Send" => bounds::expand_deriving_unsafe_bound, + "Sync" => bounds::expand_deriving_unsafe_bound, + "Copy" => bounds::expand_deriving_copy, + + // deprecated + "Show" => show::expand_deriving_show, + "Encodable" => encodable::expand_deriving_encodable, + "Decodable" => decodable::expand_deriving_decodable, +} + +#[inline] // because `name` is a compile-time constant +fn warn_if_deprecated(ecx: &mut ExtCtxt, sp: Span, name: &str) { + if let Some(replacement) = match name { + "Show" => Some("Debug"), + "Encodable" => Some("RustcEncodable"), + "Decodable" => Some("RustcDecodable"), + _ => None, + } { + ecx.span_warn(sp, &format!("derive({}) is deprecated in favor of derive({})", + name, replacement)); + } +} diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 84b02999252..db8819ef82c 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -1476,6 +1476,7 @@ impl<'feat> ExpansionConfig<'feat> { fn enable_concat_idents = allow_concat_idents, fn enable_trace_macros = allow_trace_macros, fn enable_allow_internal_unstable = allow_internal_unstable, + fn enable_custom_derive = allow_custom_derive, } } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index eff86757fd0..425c517cb29 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -137,6 +137,10 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Status)] = &[ // Allows the use of custom attributes; RFC 572 ("custom_attribute", "1.0.0", Active), + // Allows the use of #[derive(Anything)] as sugar for + // #[derive_Anything]. + ("custom_derive", "1.0.0", Active), + // Allows the use of rustc_* attributes; RFC 572 ("rustc_attrs", "1.0.0", Active), @@ -319,6 +323,7 @@ pub struct Features { pub allow_concat_idents: bool, pub allow_trace_macros: bool, pub allow_internal_unstable: bool, + pub allow_custom_derive: bool, pub old_orphan_check: bool, pub simd_ffi: bool, pub unmarked_api: bool, @@ -340,6 +345,7 @@ impl Features { allow_concat_idents: false, allow_trace_macros: false, allow_internal_unstable: false, + allow_custom_derive: false, old_orphan_check: false, simd_ffi: false, unmarked_api: false, @@ -391,6 +397,10 @@ impl<'a> Context<'a> { "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"); } else { self.gate_feature("custom_attribute", attr.span, format!("The attribute `{}` is currently \ @@ -432,6 +442,9 @@ pub const EXPLAIN_TRACE_MACROS: &'static str = pub const EXPLAIN_ALLOW_INTERNAL_UNSTABLE: &'static str = "allow_internal_unstable side-steps feature gating and stability checks"; +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> } @@ -780,6 +793,7 @@ fn check_crate_inner(cm: &CodeMap, span_handler: &SpanHandler, krate: &ast::C 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"), old_orphan_check: cx.has_feature("old_orphan_check"), simd_ffi: cx.has_feature("simd_ffi"), unmarked_api: cx.has_feature("unmarked_api"), diff --git a/src/test/auxiliary/custom_derive_plugin.rs b/src/test/auxiliary/custom_derive_plugin.rs new file mode 100644 index 00000000000..e2688964804 --- /dev/null +++ b/src/test/auxiliary/custom_derive_plugin.rs @@ -0,0 +1,74 @@ +// 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. + +// force-host + +#![feature(plugin_registrar)] +#![feature(box_syntax)] +#![feature(rustc_private)] + +extern crate syntax; +extern crate rustc; + +use syntax::ast; +use syntax::codemap::Span; +use syntax::ext::base::{Decorator, ExtCtxt}; +use syntax::ext::build::AstBuilder; +use syntax::ext::deriving::generic::{cs_fold, TraitDef, MethodDef, combine_substructure}; +use syntax::ext::deriving::generic::ty::{Literal, LifetimeBounds, Path, borrowed_explicit_self}; +use syntax::parse::token; +use syntax::ptr::P; +use rustc::plugin::Registry; + +#[plugin_registrar] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_syntax_extension( + token::intern("derive_TotalSum"), + Decorator(box expand)); +} + +fn expand(cx: &mut ExtCtxt, + span: Span, + mitem: &ast::MetaItem, + item: &ast::Item, + push: &mut FnMut(P)) { + let trait_def = TraitDef { + span: span, + attributes: vec![], + path: Path::new(vec!["TotalSum"]), + additional_bounds: vec![], + generics: LifetimeBounds::empty(), + associated_types: vec![], + methods: vec![ + MethodDef { + name: "total_sum", + generics: LifetimeBounds::empty(), + explicit_self: borrowed_explicit_self(), + args: vec![], + ret_ty: Literal(Path::new_local("isize")), + attributes: vec![], + combine_substructure: combine_substructure(box |cx, span, substr| { + let zero = cx.expr_int(span, 0); + cs_fold(false, + |cx, span, subexpr, field, _| { + cx.expr_binary(span, ast::BiAdd, subexpr, + cx.expr_method_call(span, field, + token::str_to_ident("total_sum"), vec![])) + }, + zero, + box |cx, span, _, _| { cx.span_bug(span, "wtf??"); }, + cx, span, substr) + }), + }, + ], + }; + + trait_def.expand(cx, mitem, item, |i| push(i)) +} diff --git a/src/test/compile-fail/deriving-bounds.rs b/src/test/compile-fail/deriving-bounds.rs index c0bcbb284a1..72d06274de4 100644 --- a/src/test/compile-fail/deriving-bounds.rs +++ b/src/test/compile-fail/deriving-bounds.rs @@ -8,12 +8,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#[derive(Copy(Bad))] -//~^ ERROR unexpected value in deriving, expected a trait +#[derive(Send)] +//~^ ERROR this unsafe trait should be implemented explicitly struct Test; #[derive(Sync)] -//~^ ERROR Sync is an unsafe trait and it should be implemented explicitly +//~^ ERROR this unsafe trait should be implemented explicitly struct Test1; pub fn main() {} diff --git a/src/test/compile-fail/deriving-meta-unknown-trait.rs b/src/test/compile-fail/deriving-meta-unknown-trait.rs index 6b85656bdd9..e2234994693 100644 --- a/src/test/compile-fail/deriving-meta-unknown-trait.rs +++ b/src/test/compile-fail/deriving-meta-unknown-trait.rs @@ -8,7 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#[derive(Eqr)] //~ ERROR unknown `derive` trait: `Eqr` +#[derive(Eqr)] +//~^ ERROR `#[derive]` for custom traits is not stable enough for use and is subject to change struct Foo; pub fn main() {} diff --git a/src/test/compile-fail/malformed-derive-entry.rs b/src/test/compile-fail/malformed-derive-entry.rs new file mode 100644 index 00000000000..62dbc21495a --- /dev/null +++ b/src/test/compile-fail/malformed-derive-entry.rs @@ -0,0 +1,25 @@ +// 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. + +#[derive(Copy(Bad))] +//~^ ERROR malformed `derive` entry +struct Test1; + +#[derive(Copy="bad")] +//~^ ERROR malformed `derive` entry +struct Test2; + +#[derive()] +//~^ WARNING empty trait list +struct Test3; + +#[derive] +//~^ WARNING empty trait list +struct Test4; diff --git a/src/test/compile-fail/single-derive-attr.rs b/src/test/compile-fail/single-derive-attr.rs new file mode 100644 index 00000000000..0b1b3141f5b --- /dev/null +++ b/src/test/compile-fail/single-derive-attr.rs @@ -0,0 +1,15 @@ +// 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. + +#[derive_Clone] +//~^ ERROR attributes of the form `#[derive_*]` are reserved +struct Test; + +pub fn main() {} diff --git a/src/test/run-pass-fulldeps/derive-totalsum.rs b/src/test/run-pass-fulldeps/derive-totalsum.rs new file mode 100644 index 00000000000..848b2425e44 --- /dev/null +++ b/src/test/run-pass-fulldeps/derive-totalsum.rs @@ -0,0 +1,59 @@ +// 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. + +// aux-build:custom_derive_plugin.rs +// ignore-stage1 + +#![feature(plugin, custom_derive)] +#![plugin(custom_derive_plugin)] + +trait TotalSum { + fn total_sum(&self) -> isize; +} + +impl TotalSum for isize { + fn total_sum(&self) -> isize { + *self + } +} + +struct Seven; + +impl TotalSum for Seven { + fn total_sum(&self) -> isize { + 7 + } +} + +#[derive(TotalSum)] +struct Foo { + seven: Seven, + bar: Bar, + baz: isize, +} + +#[derive(TotalSum)] +struct Bar { + quux: isize, + bleh: isize, +} + + +pub fn main() { + let v = Foo { + seven: Seven, + bar: Bar { + quux: 9, + bleh: 3, + }, + baz: 80, + }; + assert_eq!(v.total_sum(), 99); +} diff --git a/src/test/run-pass/deprecated-derive.rs b/src/test/run-pass/deprecated-derive.rs new file mode 100644 index 00000000000..494d62c7737 --- /dev/null +++ b/src/test/run-pass/deprecated-derive.rs @@ -0,0 +1,15 @@ +// 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. + +#[derive(Show)] +//~^ WARNING derive(Show) is deprecated +struct Test1; + +fn main() { } diff --git a/src/test/run-pass/single-derive-attr-with-gate.rs b/src/test/run-pass/single-derive-attr-with-gate.rs new file mode 100644 index 00000000000..cc5d8fc7891 --- /dev/null +++ b/src/test/run-pass/single-derive-attr-with-gate.rs @@ -0,0 +1,18 @@ +// 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. + +#![feature(custom_derive)] + +#[derive_Clone] +struct Test; + +pub fn main() { + Test.clone(); +}