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 { pub trait Registry {
fn register_custom_derive(&mut self, fn register_custom_derive(&mut self,
trait_name: &str, trait_name: &str,
expand: fn(TokenStream) -> TokenStream); expand: fn(TokenStream) -> TokenStream,
attributes: &[&'static str]);
} }
// Emulate scoped_thread_local!() here essentially // Emulate scoped_thread_local!() here essentially

View File

@@ -624,8 +624,12 @@ impl<'a> CrateLoader<'a> {
impl Registry for MyRegistrar { impl Registry for MyRegistrar {
fn register_custom_derive(&mut self, fn register_custom_derive(&mut self,
trait_name: &str, trait_name: &str,
expand: fn(TokenStream) -> TokenStream) { expand: fn(TokenStream) -> TokenStream,
let derive = SyntaxExtension::CustomDerive(Box::new(CustomDerive::new(expand))); 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)); self.0.push((intern(trait_name), derive));
} }
} }

View File

@@ -12,20 +12,37 @@ use std::panic;
use errors::FatalError; use errors::FatalError;
use proc_macro::{TokenStream, __internal}; 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::codemap::{ExpnInfo, MacroAttribute, NameAndSpan, Span};
use syntax::ext::base::*; use syntax::ext::base::*;
use syntax::fold::Folder; use syntax::fold::Folder;
use syntax::parse::token::InternedString;
use syntax::parse::token::intern; use syntax::parse::token::intern;
use syntax::print::pprust; 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 { pub struct CustomDerive {
inner: fn(TokenStream) -> TokenStream, inner: fn(TokenStream) -> TokenStream,
attrs: Vec<InternedString>,
} }
impl CustomDerive { impl CustomDerive {
pub fn new(inner: fn(TokenStream) -> TokenStream) -> CustomDerive { pub fn new(inner: fn(TokenStream) -> TokenStream,
CustomDerive { inner: inner } attrs: Vec<InternedString>)
-> CustomDerive {
CustomDerive { inner: inner, attrs: attrs }
} }
} }
@@ -47,7 +64,7 @@ impl MultiItemModifier for CustomDerive {
}; };
match item.node { match item.node {
ItemKind::Struct(..) | ItemKind::Struct(..) |
ItemKind::Enum(..) => {} ItemKind::Enum(..) => {},
_ => { _ => {
ecx.span_err(span, "custom derive attributes may only be \ ecx.span_err(span, "custom derive attributes may only be \
applied to struct/enum items"); 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 { let input_span = Span {
expn_id: ecx.codemap().record_expansion(ExpnInfo { expn_id: ecx.codemap().record_expansion(ExpnInfo {
call_site: span, call_site: span,
@@ -66,12 +86,13 @@ impl MultiItemModifier for CustomDerive {
}), }),
..item.span ..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 res = __internal::set_parse_sess(&ecx.parse_sess, || {
let inner = self.inner; let inner = self.inner;
panic::catch_unwind(panic::AssertUnwindSafe(|| inner(input))) panic::catch_unwind(panic::AssertUnwindSafe(|| inner(input)))
}); });
let item = match res { let new_items = match res {
Ok(stream) => __internal::token_stream_items(stream), Ok(stream) => __internal::token_stream_items(stream),
Err(e) => { Err(e) => {
let msg = "custom derive attribute panicked"; 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 let mut res = vec![Annotatable::Item(item)];
// macros, everything is just parsed as a string. Reassign all spans to // Reassign spans of all expanded items to the input `item`
// the input `item` for better errors here. // for better errors here.
item.into_iter().flat_map(|item| { res.extend(new_items.into_iter().flat_map(|item| {
ChangeSpan { span: input_span }.fold_item(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, trait_name: InternedString,
function_name: Ident, function_name: Ident,
span: Span, span: Span,
attrs: Vec<InternedString>,
} }
struct CollectCustomDerives<'a> { struct CollectCustomDerives<'a> {
@@ -133,7 +134,8 @@ impl<'a> Visitor for CollectCustomDerives<'a> {
} }
// Once we've located the `#[proc_macro_derive]` attribute, verify // 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() { let list = match attr.meta_item_list() {
Some(list) => list, Some(list) => list,
None => { None => {
@@ -143,38 +145,69 @@ impl<'a> Visitor for CollectCustomDerives<'a> {
return return
} }
}; };
if list.len() != 1 { if list.len() != 1 && list.len() != 2 {
self.handler.span_err(attr.span(), self.handler.span_err(attr.span(),
"attribute must only have one argument"); "attribute must have either one or two arguments");
return return
} }
let attr = &list[0]; let trait_attr = &list[0];
let trait_name = match attr.name() { let attributes_attr = list.get(1);
let trait_name = match trait_attr.name() {
Some(name) => 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 return
} }
}; };
if !attr.is_word() { if !trait_attr.is_word() {
self.handler.span_err(attr.span(), "must only be one word"); self.handler.span_err(trait_attr.span(), "must only be one word");
} }
if deriving::is_builtin_trait(&trait_name) { 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"); "cannot override a built-in #[derive] mode");
} }
if self.derives.iter().any(|d| d.trait_name == trait_name) { 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"); "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 { if self.in_root {
self.derives.push(CustomDerive { self.derives.push(CustomDerive {
span: item.span, span: item.span,
trait_name: trait_name, trait_name: trait_name,
function_name: item.ident, function_name: item.ident,
attrs: proc_attrs,
}); });
} else { } else {
let msg = "functions tagged with `#[proc_macro_derive]` must \ let msg = "functions tagged with `#[proc_macro_derive]` must \
@@ -208,8 +241,8 @@ impl<'a> Visitor for CollectCustomDerives<'a> {
// //
// #[plugin_registrar] // #[plugin_registrar]
// fn registrar(registrar: &mut Registry) { // fn registrar(registrar: &mut Registry) {
// registrar.register_custom_derive($name_trait1, ::$name1); // registrar.register_custom_derive($name_trait1, ::$name1, &[]);
// registrar.register_custom_derive($name_trait2, ::$name2); // 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 stmts = custom_derives.iter().map(|cd| {
let path = cx.path_global(cd.span, vec![cd.function_name]); let path = cx.path_global(cd.span, vec![cd.function_name]);
let trait_name = cx.expr_str(cd.span, cd.trait_name.clone()); let trait_name = cx.expr_str(cd.span, cd.trait_name.clone());
(path, trait_name) let attrs = cx.expr_vec_slice(
}).map(|(path, trait_name)| { 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 registrar = cx.expr_ident(span, registrar);
let ufcs_path = cx.path(span, vec![proc_macro, __internal, registry, let ufcs_path = cx.path(span, vec![proc_macro, __internal, registry,
register_custom_derive]); register_custom_derive]);
cx.expr_call(span, cx.expr_call(span,
cx.expr_path(ufcs_path), cx.expr_path(ufcs_path),
vec![registrar, trait_name, cx.expr_path(path)]) vec![registrar, trait_name, cx.expr_path(path), attrs])
}).map(|expr| { }).map(|expr| {
cx.stmt_expr(expr) cx.stmt_expr(expr)
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();

View File

@@ -33,8 +33,8 @@ pub fn foo3(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input input
} }
#[proc_macro_derive(b, c)] #[proc_macro_derive(b, c, d)]
//~^ ERROR: attribute must only have one argument //~^ ERROR: attribute must have either one or two arguments
pub fn foo4(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn foo4(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input 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 { pub fn foo5(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
input 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)] #[proc_macro_derive(AddImpl)]
// #[cfg(proc_macro)] // #[cfg(proc_macro)]
pub fn derive(input: TokenStream) -> TokenStream { pub fn derive(input: TokenStream) -> TokenStream {
(input.to_string() + " "impl B {
impl B {
fn foo(&self) {} fn foo(&self) {}
} }
fn foo() {} fn foo() {}
mod bar { pub 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)] #[proc_macro_derive(Append)]
pub fn derive_a(input: TokenStream) -> TokenStream { pub fn derive_a(input: TokenStream) -> TokenStream {
let mut input = input.to_string(); "impl Append for A {
input.push_str("
impl Append for A {
fn foo(&self) {} fn foo(&self) {}
} }
"); ".parse().unwrap()
input.parse().unwrap()
} }

View File

@@ -23,5 +23,5 @@ pub fn derive(input: TokenStream) -> TokenStream {
let input = input.to_string(); let input = input.to_string();
assert!(input.contains("struct A;")); assert!(input.contains("struct A;"));
assert!(input.contains("#[derive(Debug, PartialEq, Eq, Copy, Clone)]")); 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)] #[proc_macro_derive(AToB)]
pub fn derive1(input: TokenStream) -> TokenStream { pub fn derive1(input: TokenStream) -> TokenStream {
println!("input1: {:?}", input.to_string()); 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() "#[derive(BToC)] struct B;".parse().unwrap()
} }

View File

@@ -24,8 +24,6 @@ pub fn derive(input: TokenStream) -> TokenStream {
let input = input.to_string(); let input = input.to_string();
assert!(input.contains("struct A;")); assert!(input.contains("struct A;"));
r#" r#"
struct A;
impl A { impl A {
fn a(&self) { fn a(&self) {
panic!("hello"); 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] #[macro_use]
extern crate derive_same_struct; extern crate derive_same_struct;
#[derive(AToB, BToC)] #[derive(AToB)]
struct A; struct A;
fn main() { fn main() {