Allow proc_macro functions to whitelist specific attributes

By using a second attribute `attributes(Bar)` on
proc_macro_derive, whitelist any attributes with
the name `Bar` in the deriving item. This allows
a proc_macro function to use custom attribtues
without a custom attribute error or unused attribute
lint.
This commit is contained in:
Josh Driver 2016-11-08 21:45:02 +10:30
parent d377cf5b3f
commit 31a508e118
16 changed files with 260 additions and 46 deletions

View file

@ -95,7 +95,8 @@ pub mod __internal {
pub trait Registry {
fn register_custom_derive(&mut self,
trait_name: &str,
expand: fn(TokenStream) -> TokenStream);
expand: fn(TokenStream) -> TokenStream,
attributes: &[&'static str]);
}
// Emulate scoped_thread_local!() here essentially

View file

@ -624,8 +624,12 @@ impl<'a> CrateLoader<'a> {
impl Registry for MyRegistrar {
fn register_custom_derive(&mut self,
trait_name: &str,
expand: fn(TokenStream) -> TokenStream) {
let derive = SyntaxExtension::CustomDerive(Box::new(CustomDerive::new(expand)));
expand: fn(TokenStream) -> TokenStream,
attributes: &[&'static str]) {
let attrs = attributes.iter().map(|s| InternedString::new(s)).collect();
let derive = SyntaxExtension::CustomDerive(
Box::new(CustomDerive::new(expand, attrs))
);
self.0.push((intern(trait_name), derive));
}
}

View file

@ -12,20 +12,37 @@ use std::panic;
use errors::FatalError;
use proc_macro::{TokenStream, __internal};
use syntax::ast::{self, ItemKind};
use syntax::ast::{self, ItemKind, Attribute};
use syntax::attr::{mark_used, mark_known};
use syntax::codemap::{ExpnInfo, MacroAttribute, NameAndSpan, Span};
use syntax::ext::base::*;
use syntax::fold::Folder;
use syntax::parse::token::InternedString;
use syntax::parse::token::intern;
use syntax::print::pprust;
use syntax::visit::Visitor;
struct MarkAttrs<'a>(&'a [InternedString]);
impl<'a> Visitor for MarkAttrs<'a> {
fn visit_attribute(&mut self, attr: &Attribute) {
if self.0.contains(&attr.name()) {
mark_used(attr);
mark_known(attr);
}
}
}
pub struct CustomDerive {
inner: fn(TokenStream) -> TokenStream,
attrs: Vec<InternedString>,
}
impl CustomDerive {
pub fn new(inner: fn(TokenStream) -> TokenStream) -> CustomDerive {
CustomDerive { inner: inner }
pub fn new(inner: fn(TokenStream) -> TokenStream,
attrs: Vec<InternedString>)
-> CustomDerive {
CustomDerive { inner: inner, attrs: attrs }
}
}
@ -47,7 +64,7 @@ impl MultiItemModifier for CustomDerive {
};
match item.node {
ItemKind::Struct(..) |
ItemKind::Enum(..) => {}
ItemKind::Enum(..) => {},
_ => {
ecx.span_err(span, "custom derive attributes may only be \
applied to struct/enum items");
@ -55,6 +72,9 @@ impl MultiItemModifier for CustomDerive {
}
}
// Mark attributes as known, and used.
MarkAttrs(&self.attrs).visit_item(&item);
let input_span = Span {
expn_id: ecx.codemap().record_expansion(ExpnInfo {
call_site: span,
@ -66,12 +86,13 @@ impl MultiItemModifier for CustomDerive {
}),
..item.span
};
let input = __internal::new_token_stream(item);
let input = __internal::new_token_stream(item.clone());
let res = __internal::set_parse_sess(&ecx.parse_sess, || {
let inner = self.inner;
panic::catch_unwind(panic::AssertUnwindSafe(|| inner(input)))
});
let item = match res {
let new_items = match res {
Ok(stream) => __internal::token_stream_items(stream),
Err(e) => {
let msg = "custom derive attribute panicked";
@ -88,12 +109,13 @@ impl MultiItemModifier for CustomDerive {
}
};
// Right now we have no knowledge of spans at all in custom derive
// macros, everything is just parsed as a string. Reassign all spans to
// the input `item` for better errors here.
item.into_iter().flat_map(|item| {
let mut res = vec![Annotatable::Item(item)];
// Reassign spans of all expanded items to the input `item`
// for better errors here.
res.extend(new_items.into_iter().flat_map(|item| {
ChangeSpan { span: input_span }.fold_item(item)
}).map(Annotatable::Item).collect()
}).map(Annotatable::Item));
res
}
}

View file

@ -30,6 +30,7 @@ struct CustomDerive {
trait_name: InternedString,
function_name: Ident,
span: Span,
attrs: Vec<InternedString>,
}
struct CollectCustomDerives<'a> {
@ -133,7 +134,8 @@ impl<'a> Visitor for CollectCustomDerives<'a> {
}
// Once we've located the `#[proc_macro_derive]` attribute, verify
// that it's of the form `#[proc_macro_derive(Foo)]`
// that it's of the form `#[proc_macro_derive(Foo)]` or
// `#[proc_macro_derive(Foo, attributes(A, ..))]`
let list = match attr.meta_item_list() {
Some(list) => list,
None => {
@ -143,38 +145,69 @@ impl<'a> Visitor for CollectCustomDerives<'a> {
return
}
};
if list.len() != 1 {
if list.len() != 1 && list.len() != 2 {
self.handler.span_err(attr.span(),
"attribute must only have one argument");
"attribute must have either one or two arguments");
return
}
let attr = &list[0];
let trait_name = match attr.name() {
let trait_attr = &list[0];
let attributes_attr = list.get(1);
let trait_name = match trait_attr.name() {
Some(name) => name,
_ => {
self.handler.span_err(attr.span(), "not a meta item");
self.handler.span_err(trait_attr.span(), "not a meta item");
return
}
};
if !attr.is_word() {
self.handler.span_err(attr.span(), "must only be one word");
if !trait_attr.is_word() {
self.handler.span_err(trait_attr.span(), "must only be one word");
}
if deriving::is_builtin_trait(&trait_name) {
self.handler.span_err(attr.span(),
self.handler.span_err(trait_attr.span(),
"cannot override a built-in #[derive] mode");
}
if self.derives.iter().any(|d| d.trait_name == trait_name) {
self.handler.span_err(attr.span(),
self.handler.span_err(trait_attr.span(),
"derive mode defined twice in this crate");
}
let proc_attrs: Vec<_> = if let Some(attr) = attributes_attr {
if !attr.check_name("attributes") {
self.handler.span_err(attr.span(), "second argument must be `attributes`")
}
attr.meta_item_list().unwrap_or_else(|| {
self.handler.span_err(attr.span(),
"attribute must be of form: \
`attributes(foo, bar)`");
&[]
}).into_iter().filter_map(|attr| {
let name = match attr.name() {
Some(name) => name,
_ => {
self.handler.span_err(attr.span(), "not a meta item");
return None;
},
};
if !attr.is_word() {
self.handler.span_err(attr.span(), "must only be one word");
return None;
}
Some(name)
}).collect()
} else {
Vec::new()
};
if self.in_root {
self.derives.push(CustomDerive {
span: item.span,
trait_name: trait_name,
function_name: item.ident,
attrs: proc_attrs,
});
} else {
let msg = "functions tagged with `#[proc_macro_derive]` must \
@ -208,8 +241,8 @@ impl<'a> Visitor for CollectCustomDerives<'a> {
//
// #[plugin_registrar]
// fn registrar(registrar: &mut Registry) {
// registrar.register_custom_derive($name_trait1, ::$name1);
// registrar.register_custom_derive($name_trait2, ::$name2);
// registrar.register_custom_derive($name_trait1, ::$name1, &[]);
// registrar.register_custom_derive($name_trait2, ::$name2, &["attribute_name"]);
// // ...
// }
// }
@ -238,14 +271,18 @@ fn mk_registrar(cx: &mut ExtCtxt,
let 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.clone());
(path, trait_name)
}).map(|(path, trait_name)| {
let attrs = cx.expr_vec_slice(
span,
cd.attrs.iter().map(|s| cx.expr_str(cd.span, s.clone())).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)])
vec![registrar, trait_name, cx.expr_path(path), attrs])
}).map(|expr| {
cx.stmt_expr(expr)
}).collect::<Vec<_>>();

View file

@ -33,8 +33,8 @@ pub fn foo3(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input
}
#[proc_macro_derive(b, c)]
//~^ ERROR: attribute must only have one argument
#[proc_macro_derive(b, c, d)]
//~^ ERROR: attribute must have either one or two arguments
pub fn foo4(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input
}
@ -44,3 +44,21 @@ pub fn foo4(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
pub fn foo5(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input
}
#[proc_macro_derive(f, attributes(g = "h"))]
//~^ ERROR: must only be one word
pub fn foo6(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input
}
#[proc_macro_derive(i, attributes(j(k)))]
//~^ ERROR: must only be one word
pub fn foo7(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input
}
#[proc_macro_derive(l, attributes(m), n)]
//~^ ERROR: attribute must have either one or two arguments
pub fn foo8(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input
}

View file

@ -0,0 +1,25 @@
// 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)]
#![feature(proc_macro_lib)]
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(B, attributes(B))]
pub fn derive_b(input: TokenStream) -> TokenStream {
"".parse().unwrap()
}

View file

@ -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.
// aux-build:derive-b.rs
#![feature(proc_macro)]
#![allow(warnings)]
#[macro_use]
extern crate derive_b;
#[derive(B)]
struct A {
a: &u64
//~^ ERROR: missing lifetime specifier
}
fn main() {
}

View file

@ -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.
// aux-build:derive-b.rs
#![feature(proc_macro)]
#![allow(warnings)]
#[macro_use]
extern crate derive_b;
#[derive(B)]
#[B]
#[C] //~ ERROR: The attribute `C` is currently unknown to the compiler
#[B(D)]
#[B(E = "foo")]
struct B;
fn main() {}

View file

@ -21,13 +21,12 @@ use proc_macro::TokenStream;
#[proc_macro_derive(AddImpl)]
// #[cfg(proc_macro)]
pub fn derive(input: TokenStream) -> TokenStream {
(input.to_string() + "
impl B {
"impl B {
fn foo(&self) {}
}
fn foo() {}
mod bar { pub fn foo() {} }
").parse().unwrap()
".parse().unwrap()
}

View file

@ -21,11 +21,8 @@ use proc_macro::TokenStream;
#[proc_macro_derive(Append)]
pub fn derive_a(input: TokenStream) -> TokenStream {
let mut input = input.to_string();
input.push_str("
impl Append for A {
fn foo(&self) {}
}
");
input.parse().unwrap()
"impl Append for A {
fn foo(&self) {}
}
".parse().unwrap()
}

View file

@ -23,5 +23,5 @@ pub fn derive(input: TokenStream) -> TokenStream {
let input = input.to_string();
assert!(input.contains("struct A;"));
assert!(input.contains("#[derive(Debug, PartialEq, Eq, Copy, Clone)]"));
"#[derive(Debug, PartialEq, Eq, Copy, Clone)] struct A;".parse().unwrap()
"".parse().unwrap()
}

View file

@ -0,0 +1,29 @@
// 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
#![crate_type = "proc-macro"]
#![feature(proc_macro)]
#![feature(proc_macro_lib)]
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(B, attributes(B, C))]
pub fn derive(input: TokenStream) -> TokenStream {
let input = input.to_string();
assert!(input.contains("#[B]"));
assert!(input.contains("struct B {"));
assert!(input.contains("#[C]"));
assert!(input.contains("#[derive(Debug, PartialEq, Eq, Copy, Clone)]"));
"".parse().unwrap()
}

View file

@ -21,7 +21,7 @@ use proc_macro::TokenStream;
#[proc_macro_derive(AToB)]
pub fn derive1(input: TokenStream) -> TokenStream {
println!("input1: {:?}", input.to_string());
assert_eq!(input.to_string(), "#[derive(BToC)]\nstruct A;\n");
assert_eq!(input.to_string(), "struct A;\n");
"#[derive(BToC)] struct B;".parse().unwrap()
}

View file

@ -24,8 +24,6 @@ pub fn derive(input: TokenStream) -> TokenStream {
let input = input.to_string();
assert!(input.contains("struct A;"));
r#"
struct A;
impl A {
fn a(&self) {
panic!("hello");

View file

@ -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.
// aux-build:derive-b.rs
// ignore-stage1
#![feature(proc_macro)]
#[macro_use]
extern crate derive_b;
#[derive(Debug, PartialEq, B, Eq, Copy, Clone)]
#[B]
struct B {
#[C]
a: u64
}
fn main() {
B { a: 3 };
assert_eq!(B { a: 3 }, B { a: 3 });
let b = B { a: 3 };
let _d = b;
let _e = b;
}

View file

@ -15,7 +15,7 @@
#[macro_use]
extern crate derive_same_struct;
#[derive(AToB, BToC)]
#[derive(AToB)]
struct A;
fn main() {