about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAustin Bonander <austin.bonander@gmail.com>2017-01-09 01:31:14 -0800
committerAustin Bonander <austin.bonander@gmail.com>2017-01-16 22:41:22 -0800
commit375cbd20cfcc9dbf15682bcfc0081ce5ce95567b (patch)
tree6eee726bbefda54496b97963b696404cc50287aa
parentf6c0c4837c303e327a8b37649dd72f115b48f309 (diff)
downloadrust-375cbd20cfcc9dbf15682bcfc0081ce5ce95567b.tar.gz
rust-375cbd20cfcc9dbf15682bcfc0081ce5ce95567b.zip
Implement `#[proc_macro_attribute]`
* Add support for `#[proc_macro]`

* Reactivate `proc_macro` feature and gate `#[proc_macro_attribute]` under it

* Have `#![feature(proc_macro)]` imply `#![feature(use_extern_macros)]`,
error on legacy import of proc macros via `#[macro_use]`
-rw-r--r--src/librustc_driver/driver.rs1
-rw-r--r--src/librustc_metadata/creader.rs10
-rw-r--r--src/librustc_resolve/lib.rs46
-rw-r--r--src/librustc_resolve/macros.rs43
-rw-r--r--src/libsyntax/ext/expand.rs30
-rw-r--r--src/libsyntax/feature_gate.rs60
-rw-r--r--src/libsyntax_ext/lib.rs2
-rw-r--r--src/libsyntax_ext/proc_macro_impl.rs58
-rw-r--r--src/libsyntax_ext/proc_macro_registrar.rs204
-rw-r--r--src/test/compile-fail-fulldeps/proc-macro/auxiliary/attr_proc_macro.rs23
-rw-r--r--src/test/compile-fail-fulldeps/proc-macro/feature-gate-proc_macro.rs24
-rw-r--r--src/test/compile-fail-fulldeps/proc-macro/macro-use-attr.rs22
-rw-r--r--src/test/compile-fail-fulldeps/proc-macro/proc-macro-custom-attr-mutex.rs24
-rw-r--r--src/test/run-pass-fulldeps/proc-macro/attr-args.rs24
-rw-r--r--src/test/run-pass-fulldeps/proc-macro/auxiliary/attr-args.rs32
-rw-r--r--src/tools/tidy/src/features.rs2
16 files changed, 526 insertions, 79 deletions
diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs
index 442c139f14c..f34d1203a3d 100644
--- a/src/librustc_driver/driver.rs
+++ b/src/librustc_driver/driver.rs
@@ -677,6 +677,7 @@ pub fn phase_2_configure_and_expand<F>(sess: &Session,
             should_test: sess.opts.test,
             ..syntax::ext::expand::ExpansionConfig::default(crate_name.to_string())
         };
+
         let mut ecx = ExtCtxt::new(&sess.parse_sess, cfg, &mut resolver);
         let err_count = ecx.parse_sess.span_diagnostic.err_count();
 
diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs
index 8f7b9c24cbf..161331b1728 100644
--- a/src/librustc_metadata/creader.rs
+++ b/src/librustc_metadata/creader.rs
@@ -578,6 +578,7 @@ impl<'a> CrateLoader<'a> {
         use proc_macro::__internal::Registry;
         use rustc_back::dynamic_lib::DynamicLibrary;
         use syntax_ext::deriving::custom::CustomDerive;
+        use syntax_ext::proc_macro_impl::AttrProcMacro;
 
         let path = match dylib {
             Some(dylib) => dylib,
@@ -613,6 +614,15 @@ impl<'a> CrateLoader<'a> {
                 );
                 self.0.push((Symbol::intern(trait_name), Rc::new(derive)));
             }
+
+            fn register_attr_proc_macro(&mut self,
+                                        name: &str,
+                                        expand: fn(TokenStream, TokenStream) -> TokenStream) {
+                let expand = SyntaxExtension::AttrProcMacro(
+                    Box::new(AttrProcMacro { inner: expand })
+                );
+                self.0.push((Symbol::intern(name), Rc::new(expand)));
+            }
         }
 
         let mut my_registrar = MyRegistrar(Vec::new());
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index 56e8c75b859..2c5e3385638 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -61,7 +61,7 @@ use syntax::ast::{FnDecl, ForeignItem, ForeignItemKind, Generics};
 use syntax::ast::{Item, ItemKind, ImplItem, ImplItemKind};
 use syntax::ast::{Local, Mutability, Pat, PatKind, Path};
 use syntax::ast::{QSelf, TraitItemKind, TraitRef, Ty, TyKind};
-use syntax::feature_gate::{emit_feature_err, GateIssue};
+use syntax::feature_gate::{feature_err, emit_feature_err, GateIssue};
 
 use syntax_pos::{Span, DUMMY_SP, MultiSpan};
 use errors::DiagnosticBuilder;
@@ -1123,6 +1123,12 @@ pub struct Resolver<'a> {
 
     // Avoid duplicated errors for "name already defined".
     name_already_seen: FxHashMap<Name, Span>,
+
+    // If `#![feature(proc_macro)]` is set
+    proc_macro_enabled: bool,
+
+    // A set of procedural macros imported by `#[macro_use]` that have already been warned about
+    warned_proc_macros: FxHashSet<Name>,
 }
 
 pub struct ResolverArenas<'a> {
@@ -1227,6 +1233,8 @@ impl<'a> Resolver<'a> {
         invocations.insert(Mark::root(),
                            arenas.alloc_invocation_data(InvocationData::root(graph_root)));
 
+        let features = session.features.borrow();
+
         Resolver {
             session: session,
 
@@ -1284,7 +1292,9 @@ impl<'a> Resolver<'a> {
                 span: DUMMY_SP,
                 vis: ty::Visibility::Public,
             }),
-            use_extern_macros: session.features.borrow().use_extern_macros,
+
+            // `#![feature(proc_macro)]` implies `#[feature(extern_macros)]`
+            use_extern_macros: features.use_extern_macros || features.proc_macro,
 
             exported_macros: Vec::new(),
             crate_loader: crate_loader,
@@ -1296,6 +1306,8 @@ impl<'a> Resolver<'a> {
             invocations: invocations,
             name_already_seen: FxHashMap(),
             whitelisted_legacy_custom_derives: Vec::new(),
+            proc_macro_enabled: features.proc_macro,
+            warned_proc_macros: FxHashSet(),
         }
     }
 
@@ -1525,6 +1537,8 @@ impl<'a> Resolver<'a> {
 
         debug!("(resolving item) resolving {}", name);
 
+        self.check_proc_macro_attrs(&item.attrs);
+
         match item.node {
             ItemKind::Enum(_, ref generics) |
             ItemKind::Ty(_, ref generics) |
@@ -1554,6 +1568,8 @@ impl<'a> Resolver<'a> {
                         walk_list!(this, visit_ty_param_bound, bounds);
 
                         for trait_item in trait_items {
+                            this.check_proc_macro_attrs(&trait_item.attrs);
+
                             match trait_item.node {
                                 TraitItemKind::Const(_, ref default) => {
                                     // Only impose the restrictions of
@@ -1738,6 +1754,7 @@ impl<'a> Resolver<'a> {
                 this.with_self_rib(Def::SelfTy(trait_id, Some(item_def_id)), |this| {
                     this.with_current_self_type(self_type, |this| {
                         for impl_item in impl_items {
+                            this.check_proc_macro_attrs(&impl_item.attrs);
                             this.resolve_visibility(&impl_item.vis);
                             match impl_item.node {
                                 ImplItemKind::Const(..) => {
@@ -3184,6 +3201,31 @@ impl<'a> Resolver<'a> {
         let msg = "`self` no longer imports values".to_string();
         self.session.add_lint(lint::builtin::LEGACY_IMPORTS, id, span, msg);
     }
+
+    fn check_proc_macro_attrs(&mut self, attrs: &[ast::Attribute]) {
+        if self.proc_macro_enabled { return; }
+
+        for attr in attrs {
+            let maybe_binding = self.builtin_macros.get(&attr.name()).cloned().or_else(|| {
+                let ident = Ident::with_empty_ctxt(attr.name());
+                self.resolve_lexical_macro_path_segment(ident, MacroNS, None).ok()
+            });
+
+            if let Some(binding) = maybe_binding {
+                if let SyntaxExtension::AttrProcMacro(..) = *binding.get_macro(self) {
+                    attr::mark_known(attr);
+
+                    let msg = "attribute procedural macros are experimental";
+                    let feature = "proc_macro";
+
+                    feature_err(&self.session.parse_sess, feature,
+                                attr.span, GateIssue::Language, msg)
+                        .span_note(binding.span, "procedural macro imported here")
+                        .emit();
+                }
+            }
+        }
+    }
 }
 
 fn is_struct_like(def: Def) -> bool {
diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs
index 403797b6d31..9b7d6f33a7f 100644
--- a/src/librustc_resolve/macros.rs
+++ b/src/librustc_resolve/macros.rs
@@ -27,7 +27,7 @@ use syntax::ext::base::{NormalTT, Resolver as SyntaxResolver, SyntaxExtension};
 use syntax::ext::expand::{Expansion, mark_tts};
 use syntax::ext::hygiene::Mark;
 use syntax::ext::tt::macro_rules;
-use syntax::feature_gate::{emit_feature_err, GateIssue};
+use syntax::feature_gate::{emit_feature_err, GateIssue, is_builtin_attr};
 use syntax::fold::{self, Folder};
 use syntax::ptr::P;
 use syntax::symbol::keywords;
@@ -183,6 +183,10 @@ impl<'a> base::Resolver for Resolver<'a> {
                 },
                 None => {}
             }
+
+            if self.proc_macro_enabled && !is_builtin_attr(&attrs[i]) {
+                return Some(attrs.remove(i));
+            }
         }
         None
     }
@@ -373,6 +377,10 @@ impl<'a> Resolver<'a> {
             let resolution = self.resolve_lexical_macro_path_segment(ident, MacroNS, Some(span));
             let (legacy_resolution, resolution) = match (legacy_resolution, resolution) {
                 (Some(legacy_resolution), Ok(resolution)) => (legacy_resolution, resolution),
+                (Some(MacroBinding::Modern(binding)), Err(_)) => {
+                    self.err_if_macro_use_proc_macro(ident.name, span, binding);
+                    continue
+                },
                 _ => continue,
             };
             let (legacy_span, participle) = match legacy_resolution {
@@ -469,4 +477,37 @@ impl<'a> Resolver<'a> {
             self.exported_macros.push(def);
         }
     }
+
+    /// Error if `ext` is a Macros 1.1 procedural macro being imported by `#[macro_use]`
+    fn err_if_macro_use_proc_macro(&mut self, name: Name, use_span: Span,
+                                   binding: &NameBinding<'a>) {
+        use self::SyntaxExtension::*;
+
+        let krate = binding.def().def_id().krate;
+
+        // Plugin-based syntax extensions are exempt from this check
+        if krate == BUILTIN_MACROS_CRATE { return; }
+
+        let ext = binding.get_macro(self);
+
+        match *ext {
+            // If `ext` is a procedural macro, check if we've already warned about it
+            AttrProcMacro(_) | ProcMacro(_) => if !self.warned_proc_macros.insert(name) { return; },
+            _ => return,
+        }
+
+        let warn_msg = match *ext {
+            AttrProcMacro(_) => "attribute procedural macros cannot be \
+                                 imported with `#[macro_use]`",
+            ProcMacro(_) => "procedural macros cannot be imported with `#[macro_use]`",
+            _ => return,
+        };
+
+        let crate_name = self.session.cstore.crate_name(krate);
+
+        self.session.struct_span_err(use_span, warn_msg)
+            .help(&format!("instead, import the procedural macro like any other item: \
+                             `use {}::{};`", crate_name, name))
+            .emit();
+    }
 }
diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs
index 201e8d69494..1f7874274f7 100644
--- a/src/libsyntax/ext/expand.rs
+++ b/src/libsyntax/ext/expand.rs
@@ -364,7 +364,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 kind.expect_from_annotatables(items)
             }
             SyntaxExtension::AttrProcMacro(ref mac) => {
-                let attr_toks = TokenStream::from_tts(tts_for_attr(&attr, &self.cx.parse_sess));
+                let attr_toks = TokenStream::from_tts(tts_for_attr_args(&attr,
+                                                                        &self.cx.parse_sess));
+
                 let item_toks = TokenStream::from_tts(tts_for_item(&item, &self.cx.parse_sess));
 
                 let tok_result = mac.expand(self.cx, attr.span, attr_toks, item_toks);
@@ -640,8 +642,30 @@ fn tts_for_item(item: &Annotatable, parse_sess: &ParseSess) -> Vec<TokenTree> {
     string_to_tts(text, parse_sess)
 }
 
-fn tts_for_attr(attr: &ast::Attribute, parse_sess: &ParseSess) -> Vec<TokenTree> {
-    string_to_tts(pprust::attr_to_string(attr), parse_sess)
+fn tts_for_attr_args(attr: &ast::Attribute, parse_sess: &ParseSess) -> Vec<TokenTree> {
+    use ast::MetaItemKind::*;
+    use print::pp::Breaks;
+    use print::pprust::PrintState;
+
+    let token_string = match attr.value.node {
+        // For `#[foo]`, an empty token
+        Word => return vec![],
+        // For `#[foo(bar, baz)]`, returns `(bar, baz)`
+        List(ref items) => pprust::to_string(|s| {
+            s.popen()?;
+            s.commasep(Breaks::Consistent,
+                       &items[..],
+                       |s, i| s.print_meta_list_item(&i))?;
+            s.pclose()
+        }),
+        // For `#[foo = "bar"]`, returns `= "bar"`
+        NameValue(ref lit) => pprust::to_string(|s| {
+            s.word_space("=")?;
+            s.print_literal(lit)
+        }),
+    };
+
+    string_to_tts(token_string, parse_sess)
 }
 
 fn string_to_tts(text: String, parse_sess: &ParseSess) -> Vec<TokenTree> {
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index 90cca3129dc..2478ed169cd 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -30,7 +30,7 @@ use ast::{self, NodeId, PatKind};
 use attr;
 use codemap::{CodeMap, Spanned};
 use syntax_pos::Span;
-use errors::{DiagnosticBuilder, Handler};
+use errors::{DiagnosticBuilder, Handler, FatalError};
 use visit::{self, FnKind, Visitor};
 use parse::ParseSess;
 use symbol::Symbol;
@@ -325,6 +325,9 @@ declare_features! (
     // The `unadjusted` ABI. Perma unstable.
     (active, abi_unadjusted, "1.16.0", None),
 
+    // Macros 1.1
+    (active, proc_macro, "1.16.0", Some(35900)),
+
     // Allows attributes on struct literal fields.
     (active, struct_field_attributes, "1.16.0", Some(38814)),
 );
@@ -377,8 +380,6 @@ declare_features! (
     // Allows `..` in tuple (struct) patterns
     (accepted, dotdot_in_tuple_patterns, "1.14.0", Some(33627)),
     (accepted, item_like_imports, "1.14.0", Some(35120)),
-    // Macros 1.1
-    (accepted, proc_macro, "1.15.0", Some(35900)),
 );
 // (changing above list without updating src/doc/reference.md makes @cmr sad)
 
@@ -446,6 +447,10 @@ pub fn deprecated_attributes() -> Vec<&'static (&'static str, AttributeType, Att
     BUILTIN_ATTRIBUTES.iter().filter(|a| a.2.is_deprecated()).collect()
 }
 
+pub fn is_builtin_attr(attr: &ast::Attribute) -> bool {
+    BUILTIN_ATTRIBUTES.iter().any(|&(builtin_name, _, _)| attr.check_name(builtin_name))
+}
+
 // Attributes that have a special meaning to rustc or rustdoc
 pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGate)] = &[
     // Normal attributes
@@ -739,6 +744,16 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
                                               is currently unstable",
                                              cfg_fn!(windows_subsystem))),
 
+    ("proc_macro_attribute", Normal, Gated(Stability::Unstable,
+                                           "proc_macro",
+                                           "attribute proc macros are currently unstable",
+                                           cfg_fn!(proc_macro))),
+
+    ("rustc_derive_registrar", Normal, Gated(Stability::Unstable,
+                                             "rustc_derive_registrar",
+                                             "used internally by rustc",
+                                             cfg_fn!(rustc_attrs))),
+
     // Crate level attributes
     ("crate_name", CrateLevel, Ungated),
     ("crate_type", CrateLevel, Ungated),
@@ -1380,6 +1395,8 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
 pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute]) -> Features {
     let mut features = Features::new();
 
+    let mut feature_checker = MutexFeatureChecker::default();
+
     for attr in krate_attrs {
         if !attr.check_name("feature") {
             continue
@@ -1403,6 +1420,7 @@ pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute]) -> F
                     if let Some(&(_, _, _, setter)) = ACTIVE_FEATURES.iter()
                         .find(|& &(n, _, _, _)| name == n) {
                         *(setter(&mut features)) = true;
+                        feature_checker.collect(&features, mi.span);
                     }
                     else if let Some(&(_, _, _)) = REMOVED_FEATURES.iter()
                         .find(|& &(n, _, _)| name == n) {
@@ -1419,9 +1437,45 @@ pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute]) -> F
         }
     }
 
+    feature_checker.check(span_handler);
+
     features
 }
 
+// A collector for mutually-exclusive features and their flag spans
+#[derive(Default)]
+struct MutexFeatureChecker {
+    proc_macro: Option<Span>,
+    custom_attribute: Option<Span>,
+}
+
+impl MutexFeatureChecker {
+    // If this method turns out to be a hotspot due to branching,
+    // the branching can be eliminated by modifying `setter!()` to set these spans
+    // only for the features that need to be checked for mutual exclusion.
+    fn collect(&mut self, features: &Features, span: Span) {
+        if features.proc_macro {
+            // If self.proc_macro is None, set to Some(span)
+            self.proc_macro = self.proc_macro.or(Some(span));
+        }
+
+        if features.custom_attribute {
+            self.custom_attribute = self.custom_attribute.or(Some(span));
+        }
+    }
+
+    fn check(self, handler: &Handler) {
+        if let (Some(pm_span), Some(ca_span)) = (self.proc_macro, self.custom_attribute) {
+            handler.struct_span_err(pm_span, "Cannot use `#![feature(proc_macro)]` and \
+                                              `#![feature(custom_attribute)] at the same time")
+                .span_note(ca_span, "`#![feature(custom_attribute)]` declared here")
+                .emit();
+
+            panic!(FatalError);
+        }
+    }
+}
+
 pub fn check_crate(krate: &ast::Crate,
                    sess: &ParseSess,
                    features: &Features,
diff --git a/src/libsyntax_ext/lib.rs b/src/libsyntax_ext/lib.rs
index bdec86158a4..ebec23d0901 100644
--- a/src/libsyntax_ext/lib.rs
+++ b/src/libsyntax_ext/lib.rs
@@ -47,6 +47,8 @@ pub mod proc_macro_registrar;
 // for custom_derive
 pub mod deriving;
 
+pub mod proc_macro_impl;
+
 use std::rc::Rc;
 use syntax::ast;
 use syntax::ext::base::{MacroExpanderFn, NormalTT, MultiModifier, NamedSyntaxExtension};
diff --git a/src/libsyntax_ext/proc_macro_impl.rs b/src/libsyntax_ext/proc_macro_impl.rs
new file mode 100644
index 00000000000..b454628acb1
--- /dev/null
+++ b/src/libsyntax_ext/proc_macro_impl.rs
@@ -0,0 +1,58 @@
+// 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 <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.
+
+use std::panic;
+
+use errors::FatalError;
+
+use syntax::codemap::Span;
+use syntax::ext::base::*;
+use syntax::tokenstream::TokenStream;
+use syntax::ext::base;
+
+use proc_macro::TokenStream as TsShim;
+use proc_macro::__internal;
+
+pub struct AttrProcMacro {
+    pub inner: fn(TsShim, TsShim) -> TsShim,
+}
+
+impl base::AttrProcMacro for AttrProcMacro {
+    fn expand<'cx>(&self,
+                   ecx: &'cx mut ExtCtxt,
+                   span: Span,
+                   annotation: TokenStream,
+                   annotated: TokenStream)
+                   -> TokenStream {
+        let annotation = __internal::token_stream_wrap(annotation);
+        let annotated = __internal::token_stream_wrap(annotated);
+
+        let res = __internal::set_parse_sess(&ecx.parse_sess, || {
+            panic::catch_unwind(panic::AssertUnwindSafe(|| (self.inner)(annotation, annotated)))
+        });
+
+        match res {
+            Ok(stream) => __internal::token_stream_inner(stream),
+            Err(e) => {
+                let msg = "custom attribute panicked";
+                let mut err = ecx.struct_span_fatal(span, msg);
+                if let Some(s) = e.downcast_ref::<String>() {
+                    err.help(&format!("message: {}", s));
+                }
+                if let Some(s) = e.downcast_ref::<&'static str>() {
+                    err.help(&format!("message: {}", s));
+                }
+
+                err.emit();
+                panic!(FatalError);
+            }
+        }
+    }
+}
diff --git a/src/libsyntax_ext/proc_macro_registrar.rs b/src/libsyntax_ext/proc_macro_registrar.rs
index c93e2c054d2..c8af16e9242 100644
--- a/src/libsyntax_ext/proc_macro_registrar.rs
+++ b/src/libsyntax_ext/proc_macro_registrar.rs
@@ -11,18 +11,20 @@
 use std::mem;
 
 use errors;
+
 use syntax::ast::{self, Ident, NodeId};
 use syntax::codemap::{ExpnInfo, NameAndSpan, MacroAttribute};
 use syntax::ext::base::ExtCtxt;
 use syntax::ext::build::AstBuilder;
 use syntax::ext::expand::ExpansionConfig;
-use syntax::parse::ParseSess;
 use syntax::fold::Folder;
+use syntax::parse::ParseSess;
 use syntax::ptr::P;
 use syntax::symbol::Symbol;
-use syntax_pos::{Span, DUMMY_SP};
 use syntax::visit::{self, Visitor};
 
+use syntax_pos::{Span, DUMMY_SP};
+
 use deriving;
 
 struct CustomDerive {
@@ -32,8 +34,14 @@ struct CustomDerive {
     attrs: Vec<ast::Name>,
 }
 
-struct CollectCustomDerives<'a> {
+struct AttrProcMacro {
+    function_name: Ident,
+    span: Span,
+}
+
+struct CollectProcMacros<'a> {
     derives: Vec<CustomDerive>,
+    attr_macros: Vec<AttrProcMacro>,
     in_root: bool,
     handler: &'a errors::Handler,
     is_proc_macro_crate: bool,
@@ -50,16 +58,17 @@ pub fn modify(sess: &ParseSess,
     let ecfg = ExpansionConfig::default("proc_macro".to_string());
     let mut cx = ExtCtxt::new(sess, ecfg, resolver);
 
-    let derives = {
-        let mut collect = CollectCustomDerives {
+    let (derives, attr_macros) = {
+        let mut collect = CollectProcMacros {
             derives: Vec::new(),
+            attr_macros: Vec::new(),
             in_root: true,
             handler: handler,
             is_proc_macro_crate: is_proc_macro_crate,
             is_test_crate: is_test_crate,
         };
         visit::walk_crate(&mut collect, &krate);
-        collect.derives
+        (collect.derives, collect.attr_macros)
     };
 
     if !is_proc_macro_crate {
@@ -74,7 +83,7 @@ pub fn modify(sess: &ParseSess,
         return krate;
     }
 
-    krate.module.items.push(mk_registrar(&mut cx, &derives));
+    krate.module.items.push(mk_registrar(&mut cx, &derives, &attr_macros));
 
     if krate.exported_macros.len() > 0 {
         handler.err("cannot export macro_rules! macros from a `proc-macro` \
@@ -84,7 +93,7 @@ pub fn modify(sess: &ParseSess,
     return krate
 }
 
-impl<'a> CollectCustomDerives<'a> {
+impl<'a> CollectProcMacros<'a> {
     fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) {
         if self.is_proc_macro_crate &&
            self.in_root &&
@@ -92,61 +101,11 @@ impl<'a> CollectCustomDerives<'a> {
             self.handler.span_err(sp,
                                   "`proc-macro` crate types cannot \
                                    export any items other than functions \
-                                   tagged with `#[proc_macro_derive]` \
-                                   currently");
+                                   tagged with `#[proc_macro_derive]` currently");
         }
     }
-}
-
-impl<'a> Visitor<'a> for CollectCustomDerives<'a> {
-    fn visit_item(&mut self, item: &'a ast::Item) {
-        let mut attrs = item.attrs.iter().filter(|a| a.check_name("proc_macro_derive"));
-
-        // First up, make sure we're checking a bare function. If we're not then
-        // we're just not interested in this item.
-        //
-        // If we find one, try to locate a `#[proc_macro_derive]` attribute on
-        // it.
-        match item.node {
-            ast::ItemKind::Fn(..) => {}
-            _ => {
-                // Check for invalid use of proc_macro_derive
-                if let Some(attr) = attrs.next() {
-                    self.handler.span_err(attr.span(),
-                                          "the `#[proc_macro_derive]` \
-                                          attribute may only be used \
-                                          on bare functions");
-                    return;
-                }
-                self.check_not_pub_in_root(&item.vis, item.span);
-                return visit::walk_item(self, item)
-            }
-        }
-
-        let attr = match attrs.next() {
-            Some(attr) => attr,
-            None => {
-                self.check_not_pub_in_root(&item.vis, item.span);
-                return visit::walk_item(self, item)
-            }
-        };
-
-        if let Some(a) = attrs.next() {
-            self.handler.span_err(a.span(), "multiple `#[proc_macro_derive]` \
-                                             attributes found");
-        }
-
-        if self.is_test_crate {
-            return;
-        }
-
-        if !self.is_proc_macro_crate {
-            self.handler.span_err(attr.span(),
-                                  "the `#[proc_macro_derive]` attribute is \
-                                   only usable with crates of the `proc-macro` \
-                                   crate type");
-        }
 
+    fn collect_custom_derive(&mut self, item: &'a ast::Item, attr: &'a ast::Attribute) {
         // Once we've located the `#[proc_macro_derive]` attribute, verify
         // that it's of the form `#[proc_macro_derive(Foo)]` or
         // `#[proc_macro_derive(Foo, attributes(A, ..))]`
@@ -232,6 +191,101 @@ impl<'a> Visitor<'a> for CollectCustomDerives<'a> {
             };
             self.handler.span_err(item.span, msg);
         }
+    }
+
+    fn collect_attr_proc_macro(&mut self, item: &'a ast::Item, attr: &'a ast::Attribute) {
+        if let Some(_) = attr.meta_item_list() {
+            self.handler.span_err(attr.span, "`#[proc_macro_attribute]` attribute
+                cannot contain any meta items");
+            return;
+        }
+
+        if self.in_root && item.vis == ast::Visibility::Public {
+            self.attr_macros.push(AttrProcMacro {
+                span: item.span,
+                function_name: item.ident,
+            });
+        } else {
+            let msg = if !self.in_root {
+                "functions tagged with `#[proc_macro_attribute]` must \
+                 currently reside in the root of the crate"
+            } else {
+                "functions tagged with `#[proc_macro_attribute]` must be `pub`"
+            };
+            self.handler.span_err(item.span, msg);
+        }
+    }
+}
+
+impl<'a> Visitor<'a> for CollectProcMacros<'a> {
+    fn visit_item(&mut self, item: &'a ast::Item) {
+        // First up, make sure we're checking a bare function. If we're not then
+        // we're just not interested in this item.
+        //
+        // If we find one, try to locate a `#[proc_macro_derive]` attribute on
+        // it.
+        let is_fn = match item.node {
+            ast::ItemKind::Fn(..) => true,
+            _ => false,
+        };
+
+        let mut found_attr: Option<&'a ast::Attribute> = None;
+
+        for attr in &item.attrs {
+            if attr.check_name("proc_macro_derive") || attr.check_name("proc_macro_attribute") {
+                if let Some(prev_attr) = found_attr {
+                    let msg = if attr.name() == prev_attr.name() {
+                        format!("Only one `#[{}]` attribute is allowed on any given function",
+                                attr.name())
+                    } else {
+                        format!("`#[{}]` and `#[{}]` attributes cannot both be applied \
+                                to the same function", attr.name(), prev_attr.name())
+                    };
+
+                    self.handler.struct_span_err(attr.span(), &msg)
+                        .span_note(prev_attr.span(), "Previous attribute here")
+                        .emit();
+
+                    return;
+                }
+
+                found_attr = Some(attr);
+            }
+        }
+
+        let attr = match found_attr {
+            None => {
+                self.check_not_pub_in_root(&item.vis, item.span);
+                return visit::walk_item(self, item);
+            },
+            Some(attr) => attr,
+        };
+
+        if !is_fn {
+            let msg = format!("the `#[{}]` attribute may only be used on bare functions",
+                              attr.name());
+
+            self.handler.span_err(attr.span(), &msg);
+            return;
+        }
+
+        if self.is_test_crate {
+            return;
+        }
+
+        if !self.is_proc_macro_crate {
+            let msg = format!("the `#[{}]` attribute is only usable with crates of the \
+                              `proc-macro` crate type", attr.name());
+
+            self.handler.span_err(attr.span(), &msg);
+            return;
+        }
+
+        if attr.check_name("proc_macro_derive") {
+            self.collect_custom_derive(item, attr);
+        } else if attr.check_name("proc_macro_attribute") {
+            self.collect_attr_proc_macro(item, attr);
+        };
 
         visit::walk_item(self, item);
     }
@@ -265,7 +319,8 @@ impl<'a> Visitor<'a> for CollectCustomDerives<'a> {
 //          }
 //      }
 fn mk_registrar(cx: &mut ExtCtxt,
-                custom_derives: &[CustomDerive]) -> P<ast::Item> {
+                custom_derives: &[CustomDerive],
+                custom_attrs: &[AttrProcMacro]) -> P<ast::Item> {
     let eid = cx.codemap().record_expansion(ExpnInfo {
         call_site: DUMMY_SP,
         callee: NameAndSpan {
@@ -286,25 +341,36 @@ fn mk_registrar(cx: &mut ExtCtxt,
     let registry = Ident::from_str("Registry");
     let registrar = Ident::from_str("registrar");
     let register_custom_derive = Ident::from_str("register_custom_derive");
-    let stmts = custom_derives.iter().map(|cd| {
+    let register_attr_proc_macro = Ident::from_str("register_attr_proc_macro");
+
+    let mut stmts = custom_derives.iter().map(|cd| {
         let path = cx.path_global(cd.span, vec![cd.function_name]);
         let trait_name = cx.expr_str(cd.span, cd.trait_name);
         let attrs = cx.expr_vec_slice(
             span,
             cd.attrs.iter().map(|&s| cx.expr_str(cd.span, s)).collect::<Vec<_>>()
         );
-        (path, trait_name, attrs)
-    }).map(|(path, trait_name, attrs)| {
         let registrar = cx.expr_ident(span, registrar);
         let ufcs_path = cx.path(span, vec![proc_macro, __internal, registry,
                                            register_custom_derive]);
-        cx.expr_call(span,
-                     cx.expr_path(ufcs_path),
-                     vec![registrar, trait_name, cx.expr_path(path), attrs])
-    }).map(|expr| {
-        cx.stmt_expr(expr)
+
+        cx.stmt_expr(cx.expr_call(span, cx.expr_path(ufcs_path),
+                                  vec![registrar, trait_name, cx.expr_path(path), attrs]))
+
     }).collect::<Vec<_>>();
 
+    stmts.extend(custom_attrs.iter().map(|ca| {
+        let name = cx.expr_str(ca.span, ca.function_name.name);
+        let path = cx.path_global(ca.span, vec![ca.function_name]);
+        let registrar = cx.expr_ident(ca.span, registrar);
+
+        let ufcs_path = cx.path(span,
+                                vec![proc_macro, __internal, registry, register_attr_proc_macro]);
+
+        cx.stmt_expr(cx.expr_call(span, cx.expr_path(ufcs_path),
+                                  vec![registrar, name, cx.expr_path(path)]))
+    }));
+
     let path = cx.path(span, vec![proc_macro, __internal, registry]);
     let registrar_path = cx.ty_path(path);
     let arg_ty = cx.ty_rptr(span, registrar_path, None, ast::Mutability::Mutable);
diff --git a/src/test/compile-fail-fulldeps/proc-macro/auxiliary/attr_proc_macro.rs b/src/test/compile-fail-fulldeps/proc-macro/auxiliary/attr_proc_macro.rs
new file mode 100644
index 00000000000..db0c19e96f8
--- /dev/null
+++ b/src/test/compile-fail-fulldeps/proc-macro/auxiliary/attr_proc_macro.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 <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.
+
+// force-host
+// no-prefer-dynamic
+#![feature(proc_macro)]
+#![crate_type = "proc-macro"]
+
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+
+#[proc_macro_attribute]
+pub fn attr_proc_macro(_: TokenStream, input: TokenStream) -> TokenStream {
+    input
+}
diff --git a/src/test/compile-fail-fulldeps/proc-macro/feature-gate-proc_macro.rs b/src/test/compile-fail-fulldeps/proc-macro/feature-gate-proc_macro.rs
new file mode 100644
index 00000000000..7e32800e0f9
--- /dev/null
+++ b/src/test/compile-fail-fulldeps/proc-macro/feature-gate-proc_macro.rs
@@ -0,0 +1,24 @@
+// 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 <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.
+
+// aux-build:attr_proc_macro.rs
+// gate-test-proc_macro
+#![feature(use_extern_macros)]
+
+extern crate attr_proc_macro;
+use attr_proc_macro::attr_proc_macro;
+
+#[attr_proc_macro]
+//~^ ERROR: attribute procedural macros are experimental
+struct Foo;
+
+fn main() {
+    let _ = Foo;
+}
\ No newline at end of file
diff --git a/src/test/compile-fail-fulldeps/proc-macro/macro-use-attr.rs b/src/test/compile-fail-fulldeps/proc-macro/macro-use-attr.rs
new file mode 100644
index 00000000000..76253487b51
--- /dev/null
+++ b/src/test/compile-fail-fulldeps/proc-macro/macro-use-attr.rs
@@ -0,0 +1,22 @@
+// 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 <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.
+
+// aux-build:attr_proc_macro.rs
+#![feature(proc_macro)]
+
+#[macro_use] extern crate attr_proc_macro;
+
+#[attr_proc_macro]
+//~^ ERROR: attribute procedural macros cannot be imported with `#[macro_use]`
+struct Foo;
+
+fn main() {
+    let _ = Foo;
+}
diff --git a/src/test/compile-fail-fulldeps/proc-macro/proc-macro-custom-attr-mutex.rs b/src/test/compile-fail-fulldeps/proc-macro/proc-macro-custom-attr-mutex.rs
new file mode 100644
index 00000000000..288cab71ff4
--- /dev/null
+++ b/src/test/compile-fail-fulldeps/proc-macro/proc-macro-custom-attr-mutex.rs
@@ -0,0 +1,24 @@
+// 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 <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.
+
+// aux-build:attr_proc_macro.rs
+
+#![feature(proc_macro, custom_attribute)]
+//~^ ERROR Cannot use `#![feature(proc_macro)]` and `#![feature(custom_attribute)] at the same time
+
+extern crate attr_proc_macro;
+use attr_proc_macro::attr_proc_macro;
+
+#[attr_proc_macro]
+fn foo() {}
+
+fn main() {
+    foo();
+}
diff --git a/src/test/run-pass-fulldeps/proc-macro/attr-args.rs b/src/test/run-pass-fulldeps/proc-macro/attr-args.rs
new file mode 100644
index 00000000000..d28d75d81a2
--- /dev/null
+++ b/src/test/run-pass-fulldeps/proc-macro/attr-args.rs
@@ -0,0 +1,24 @@
+// 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 <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.
+
+// aux-build:attr-args.rs
+
+#![allow(warnings)]
+#![feature(proc_macro)]
+
+extern crate attr_args;
+use attr_args::attr_with_args;
+
+#[attr_with_args(text = "Hello, world!")]
+fn foo() {}
+
+fn main() {
+    assert_eq!(foo(), "Hello, world!");
+}
diff --git a/src/test/run-pass-fulldeps/proc-macro/auxiliary/attr-args.rs b/src/test/run-pass-fulldeps/proc-macro/auxiliary/attr-args.rs
new file mode 100644
index 00000000000..6e1eb395a0a
--- /dev/null
+++ b/src/test/run-pass-fulldeps/proc-macro/auxiliary/attr-args.rs
@@ -0,0 +1,32 @@
+// 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 <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.
+
+// no-prefer-dynamic
+#![feature(proc_macro)]
+#![crate_type = "proc-macro"]
+
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+
+#[proc_macro_attribute]
+pub fn attr_with_args(args: TokenStream, input: TokenStream) -> TokenStream {
+    let args = args.to_string();
+
+    assert_eq!(args, r#"( text = "Hello, world!" )"#);
+
+    let input = input.to_string();
+
+    assert_eq!(input, "fn foo (  ) {  }");
+
+    r#"
+        fn foo() -> &'static str { "Hello, world!" }
+    "#.parse().unwrap()
+}
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index b0da6647eb9..9df3e254943 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -172,7 +172,7 @@ pub fn check(path: &Path, bad: &mut bool) {
         "use_extern_macros", "staged_api", "const_indexing",
         "unboxed_closures", "stmt_expr_attributes",
         "cfg_target_thread_local", "unwind_attributes",
-        "inclusive_range_syntax"
+        "inclusive_range_syntax", "proc_macro"
     ];
 
     // Only check the number of lang features.