Skip to content

Commit

Permalink
Rollup merge of rust-lang#40129 - abonander:proc_macro_bang, r=jseyfried
Browse files Browse the repository at this point in the history
Implement function-like procedural macros ( `#[proc_macro]`)

Adds the `#[proc_macro]` attribute, which expects bare functions of the kind `fn(TokenStream) -> TokenStream`, which can be invoked like `my_macro!()`.

cc rust-lang/rfcs#1913, rust-lang#38356

r? @jseyfried
cc @nrc
  • Loading branch information
frewsxcv committed Mar 2, 2017
2 parents dcb2932 + 2fcbb48 commit 618cad5
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 10 deletions.
4 changes: 4 additions & 0 deletions src/libproc_macro/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ pub mod __internal {
fn register_attr_proc_macro(&mut self,
name: &str,
expand: fn(TokenStream, TokenStream) -> TokenStream);

fn register_bang_proc_macro(&mut self,
name: &str,
expand: fn(TokenStream) -> TokenStream);
}

// Emulate scoped_thread_local!() here essentially
Expand Down
11 changes: 10 additions & 1 deletion src/librustc_metadata/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ impl<'a> CrateLoader<'a> {
use proc_macro::__internal::Registry;
use rustc_back::dynamic_lib::DynamicLibrary;
use syntax_ext::deriving::custom::ProcMacroDerive;
use syntax_ext::proc_macro_impl::AttrProcMacro;
use syntax_ext::proc_macro_impl::{AttrProcMacro, BangProcMacro};

let path = match dylib {
Some(dylib) => dylib,
Expand Down Expand Up @@ -630,6 +630,15 @@ impl<'a> CrateLoader<'a> {
);
self.0.push((Symbol::intern(name), Rc::new(expand)));
}

fn register_bang_proc_macro(&mut self,
name: &str,
expand: fn(TokenStream) -> TokenStream) {
let expand = SyntaxExtension::ProcMacro(
Box::new(BangProcMacro { inner: expand })
);
self.0.push((Symbol::intern(name), Rc::new(expand)));
}
}

let mut my_registrar = MyRegistrar(Vec::new());
Expand Down
5 changes: 5 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,11 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
"attribute proc macros are currently unstable",
cfg_fn!(proc_macro))),

("proc_macro", Normal, Gated(Stability::Unstable,
"proc_macro",
"function-like proc macros are currently unstable",
cfg_fn!(proc_macro))),

("rustc_derive_registrar", Normal, Gated(Stability::Unstable,
"rustc_derive_registrar",
"used internally by rustc",
Expand Down
35 changes: 35 additions & 0 deletions src/libsyntax_ext/proc_macro_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,38 @@ impl base::AttrProcMacro for AttrProcMacro {
}
}
}

pub struct BangProcMacro {
pub inner: fn(TsShim) -> TsShim,
}

impl base::ProcMacro for BangProcMacro {
fn expand<'cx>(&self,
ecx: &'cx mut ExtCtxt,
span: Span,
input: TokenStream)
-> TokenStream {
let input = __internal::token_stream_wrap(input);

let res = __internal::set_parse_sess(&ecx.parse_sess, || {
panic::catch_unwind(panic::AssertUnwindSafe(|| (self.inner)(input)))
});

match res {
Ok(stream) => __internal::token_stream_inner(stream),
Err(e) => {
let msg = "proc macro 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);
}
}
}
}
66 changes: 57 additions & 9 deletions src/libsyntax_ext/proc_macro_registrar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,25 @@ use syntax_pos::{Span, DUMMY_SP};

use deriving;

const PROC_MACRO_KINDS: [&'static str; 3] =
["proc_macro_derive", "proc_macro_attribute", "proc_macro"];

struct ProcMacroDerive {
trait_name: ast::Name,
function_name: Ident,
span: Span,
attrs: Vec<ast::Name>,
}

struct AttrProcMacro {
struct ProcMacroDef {
function_name: Ident,
span: Span,
}

struct CollectProcMacros<'a> {
derives: Vec<ProcMacroDerive>,
attr_macros: Vec<AttrProcMacro>,
attr_macros: Vec<ProcMacroDef>,
bang_macros: Vec<ProcMacroDef>,
in_root: bool,
handler: &'a errors::Handler,
is_proc_macro_crate: bool,
Expand All @@ -58,17 +62,18 @@ pub fn modify(sess: &ParseSess,
let ecfg = ExpansionConfig::default("proc_macro".to_string());
let mut cx = ExtCtxt::new(sess, ecfg, resolver);

let (derives, attr_macros) = {
let (derives, attr_macros, bang_macros) = {
let mut collect = CollectProcMacros {
derives: Vec::new(),
attr_macros: Vec::new(),
bang_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.attr_macros)
(collect.derives, collect.attr_macros, collect.bang_macros)
};

if !is_proc_macro_crate {
Expand All @@ -83,7 +88,7 @@ pub fn modify(sess: &ParseSess,
return krate;
}

krate.module.items.push(mk_registrar(&mut cx, &derives, &attr_macros));
krate.module.items.push(mk_registrar(&mut cx, &derives, &attr_macros, &bang_macros));

if krate.exported_macros.len() > 0 {
handler.err("cannot export macro_rules! macros from a `proc-macro` \
Expand All @@ -93,6 +98,10 @@ pub fn modify(sess: &ParseSess,
return krate
}

fn is_proc_macro_attr(attr: &ast::Attribute) -> bool {
PROC_MACRO_KINDS.iter().any(|kind| attr.check_name(kind))
}

impl<'a> CollectProcMacros<'a> {
fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) {
if self.is_proc_macro_crate &&
Expand Down Expand Up @@ -196,12 +205,12 @@ impl<'a> CollectProcMacros<'a> {
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");
does not take any arguments");
return;
}

if self.in_root && item.vis == ast::Visibility::Public {
self.attr_macros.push(AttrProcMacro {
self.attr_macros.push(ProcMacroDef {
span: item.span,
function_name: item.ident,
});
Expand All @@ -215,6 +224,29 @@ impl<'a> CollectProcMacros<'a> {
self.handler.span_err(item.span, msg);
}
}

fn collect_bang_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
does not take any arguments");
return;
}

if self.in_root && item.vis == ast::Visibility::Public {
self.bang_macros.push(ProcMacroDef {
span: item.span,
function_name: item.ident,
});
} else {
let msg = if !self.in_root {
"functions tagged with `#[proc_macro]` must \
currently reside in the root of the crate"
} else {
"functions tagged with `#[proc_macro]` must be `pub`"
};
self.handler.span_err(item.span, msg);
}
}
}

impl<'a> Visitor<'a> for CollectProcMacros<'a> {
Expand All @@ -232,7 +264,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
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 is_proc_macro_attr(&attr) {
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",
Expand Down Expand Up @@ -285,6 +317,8 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
self.collect_custom_derive(item, attr);
} else if attr.check_name("proc_macro_attribute") {
self.collect_attr_proc_macro(item, attr);
} else if attr.check_name("proc_macro") {
self.collect_bang_proc_macro(item, attr);
};

visit::walk_item(self, item);
Expand Down Expand Up @@ -320,7 +354,8 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
// }
fn mk_registrar(cx: &mut ExtCtxt,
custom_derives: &[ProcMacroDerive],
custom_attrs: &[AttrProcMacro]) -> P<ast::Item> {
custom_attrs: &[ProcMacroDef],
custom_macros: &[ProcMacroDef]) -> P<ast::Item> {
let eid = cx.codemap().record_expansion(ExpnInfo {
call_site: DUMMY_SP,
callee: NameAndSpan {
Expand All @@ -342,6 +377,7 @@ fn mk_registrar(cx: &mut ExtCtxt,
let registrar = Ident::from_str("registrar");
let register_custom_derive = Ident::from_str("register_custom_derive");
let register_attr_proc_macro = Ident::from_str("register_attr_proc_macro");
let register_bang_proc_macro = Ident::from_str("register_bang_proc_macro");

let mut stmts = custom_derives.iter().map(|cd| {
let path = cx.path_global(cd.span, vec![cd.function_name]);
Expand Down Expand Up @@ -371,6 +407,18 @@ fn mk_registrar(cx: &mut ExtCtxt,
vec![registrar, name, cx.expr_path(path)]))
}));

stmts.extend(custom_macros.iter().map(|cm| {
let name = cx.expr_str(cm.span, cm.function_name.name);
let path = cx.path_global(cm.span, vec![cm.function_name]);
let registrar = cx.expr_ident(cm.span, registrar);

let ufcs_path = cx.path(span,
vec![proc_macro, __internal, registry, register_bang_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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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]
pub fn bang_proc_macro(input: TokenStream) -> TokenStream {
input
}
21 changes: 21 additions & 0 deletions src/test/compile-fail-fulldeps/proc-macro/macro-use-bang.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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:bang_proc_macro.rs

#![feature(proc_macro)]

#[macro_use]
extern crate bang_proc_macro;

fn main() {
bang_proc_macro!(println!("Hello, world!"));
//~^ ERROR: procedural macros cannot be imported with `#[macro_use]`
}
12 changes: 12 additions & 0 deletions src/test/compile-fail-fulldeps/proc-macro/resolve-error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// aux-build:derive-foo.rs
// aux-build:derive-clona.rs
// aux-build:attr_proc_macro.rs
// aux-build:bang_proc_macro.rs

#![feature(proc_macro)]

Expand All @@ -19,13 +20,19 @@ extern crate derive_foo;
#[macro_use]
extern crate derive_clona;
extern crate attr_proc_macro;
extern crate bang_proc_macro;

use attr_proc_macro::attr_proc_macro;
use bang_proc_macro::bang_proc_macro;

macro_rules! FooWithLongNam {
() => {}
}

macro_rules! attr_proc_mac {
() => {}
}

#[derive(FooWithLongNan)]
//~^ ERROR cannot find derive macro `FooWithLongNan` in this scope
//~^^ HELP did you mean `FooWithLongName`?
Expand Down Expand Up @@ -61,7 +68,12 @@ fn main() {

attr_proc_macra!();
//~^ ERROR cannot find macro `attr_proc_macra!` in this scope
//~^^ HELP did you mean `attr_proc_mac!`?

Dlona!();
//~^ ERROR cannot find macro `Dlona!` in this scope

bang_proc_macrp!();
//~^ ERROR cannot find macro `bang_proc_macrp!` in this scope
//~^^ HELP did you mean `bang_proc_macro!`?
}
26 changes: 26 additions & 0 deletions src/test/run-pass-fulldeps/proc-macro/auxiliary/bang-macro.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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]
pub fn rewrite(input: TokenStream) -> TokenStream {
let input = input.to_string();

assert_eq!(input, r#""Hello, world!""#);

r#""NOT Hello, world!""#.parse().unwrap()
}
20 changes: 20 additions & 0 deletions src/test/run-pass-fulldeps/proc-macro/bang-macro.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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:bang-macro.rs

#![feature(proc_macro)]

extern crate bang_macro;
use bang_macro::rewrite;

fn main() {
assert_eq!(rewrite!("Hello, world!"), "NOT Hello, world!");
}

0 comments on commit 618cad5

Please sign in to comment.