Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement function-like procedural macros ( #[proc_macro]) #40129

Merged
merged 1 commit into from
Mar 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshed: "fnlike" instead of "bang"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer Bang inside the compiler to avoid overloading "function". Also, we might want to use "proc macro function" for the underlying function item annotated with #[proc_macro*].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we might want to use "proc macro function" for the underlying function item annotated with #[proc_macro*].

Clarify?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For #[proc_macro_derive(A)] fn f(..) { .. }, we would say A is a [derive] proc macro and f is the [derive] proc macro function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. That doesn't affect anything in this PR, though, right? That's purely wording for documentation and stuff?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's just an argument for "bang proc macro" instead of "function / fnlike procedural macro".

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 @@ -763,6 +763,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));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to factor this out to avoid duplication across macro kinds (not needed for this PR though).


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>,
Copy link
Contributor Author

@abonander abonander Feb 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm on the fence about reusing the typedef. I didn't see much point to having a unique type with the same signature.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good as is.

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]`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: trailing newline

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!""#);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might fail the test because the wrapping delimiters might get included. I forget what the executive decision was on whether or not to include those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, works as-is.


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!");
}