Skip to content

[WIP] Expand #[derive(..)]s in the InvocationCollector #39391

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

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
6 changes: 3 additions & 3 deletions src/librustc_metadata/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ impl<'a> CrateLoader<'a> {
use proc_macro::TokenStream;
use proc_macro::__internal::Registry;
use rustc_back::dynamic_lib::DynamicLibrary;
use syntax_ext::deriving::custom::CustomDerive;
use syntax_ext::deriving::custom::ProcMacroDerive;
use syntax_ext::proc_macro_impl::AttrProcMacro;

let path = match dylib {
Expand Down Expand Up @@ -609,8 +609,8 @@ impl<'a> CrateLoader<'a> {
expand: fn(TokenStream) -> TokenStream,
attributes: &[&'static str]) {
let attrs = attributes.iter().cloned().map(Symbol::intern).collect();
let derive = SyntaxExtension::CustomDerive(
Box::new(CustomDerive::new(expand, attrs))
let derive = SyntaxExtension::ProcMacroDerive(
Box::new(ProcMacroDerive::new(expand, attrs))
);
self.0.push((Symbol::intern(trait_name), Rc::new(derive)));
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_resolve/build_reduced_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ impl<'a> Resolver<'a> {
"an `extern crate` loading macros must be at the crate root");
} else if !self.use_extern_macros && !used &&
self.session.cstore.dep_kind(module.def_id().unwrap().krate).macros_only() {
let msg = "custom derive crates and `#[no_link]` crates have no effect without \
let msg = "proc macro crates and `#[no_link]` crates have no effect without \
`#[macro_use]`";
self.session.span_warn(item.span, msg);
used = true; // Avoid the normal unused extern crate warning
Expand Down
14 changes: 12 additions & 2 deletions src/libsyntax/ext/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

pub use self::SyntaxExtension::{MultiDecorator, MultiModifier, NormalTT, IdentTT};

use ast::{self, Attribute, Name, PatKind};
use ast::{self, Attribute, Name, PatKind, MetaItem};
use attr::HasAttrs;
use codemap::{self, CodeMap, ExpnInfo, Spanned, respan};
use syntax_pos::{Span, ExpnId, NO_EXPANSION};
Expand Down Expand Up @@ -471,6 +471,9 @@ impl MacResult for DummyResult {
}
}

pub type BuiltinDeriveFn =
for<'cx> fn(&'cx mut ExtCtxt, Span, &MetaItem, &Annotatable, &mut FnMut(Annotatable));

/// An enum representing the different kinds of syntax extensions.
pub enum SyntaxExtension {
/// A syntax extension that is attached to an item and creates new items
Expand Down Expand Up @@ -507,7 +510,14 @@ pub enum SyntaxExtension {
///
IdentTT(Box<IdentMacroExpander>, Option<Span>, bool),

CustomDerive(Box<MultiItemModifier>),
/// An attribute-like procedural macro. TokenStream -> TokenStream.
/// The input is the annotated item.
/// Allows generating code to implement a Trait for a given struct
/// or enum item.
ProcMacroDerive(Box<MultiItemModifier>),

/// An attribute-like procedural macro that derives a builtin trait.
BuiltinDerive(BuiltinDeriveFn),
}

pub type NamedSyntaxExtension = (Name, SyntaxExtension);
Expand Down
226 changes: 226 additions & 0 deletions src/libsyntax/ext/derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright 2012-2017 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 ast::{self, NestedMetaItem};
use ext::base::{ExtCtxt, SyntaxExtension};
use ext::build::AstBuilder;
use feature_gate;
use symbol::Symbol;
use syntax_pos::Span;
use codemap;

pub fn derive_attr_trait<'a>(cx: &mut ExtCtxt, attr: &'a ast::Attribute)
-> Option<&'a NestedMetaItem> {
if attr.name() != "derive" {
return None;
}
if attr.value_str().is_some() {
cx.span_err(attr.span, "unexpected value in `derive`");
return None;
}

let traits = attr.meta_item_list().unwrap();
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 like it might panic on #[derive].


if traits.is_empty() {
cx.span_warn(attr.span, "empty trait list in `derive`");
return None;
}

return traits.get(0);
}

pub fn verify_derive_attrs(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think attrs: &[ast::Attribute] is sufficient here

for attr in attrs {
if attr.name() != "derive" {
continue;
}

if attr.value_str().is_some() {
cx.span_err(attr.span, "unexpected value in `derive`");
}

let traits = attr.meta_item_list().unwrap_or(&[]).to_owned();

if traits.is_empty() {
cx.span_warn(attr.span, "empty trait list in `derive`");
continue;
}
for titem in traits {
if titem.word().is_none() {
cx.span_err(titem.span, "malformed `derive` entry");
}
}
}
}

pub enum DeriveType {
Legacy,
ProcMacro,
Builtin
}

impl DeriveType {
pub fn is_derive_type(&self, cx: &mut ExtCtxt, titem: &NestedMetaItem) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we instead have a method DeriveType::classify(name, cx) here? The function would check legacy first (returning DeriveType::Legacy if we find derive_* in builtin_macros), then check builtins (returning DeriveType::Builtin), then finally return DeriveType::ProcMacro.

e.g. is_legacy_derive(cx, titem) would become DeriveType::classify(titem.name(), cx) == DeriveType::Legacy.

Even better, we could move this logic into cx.resolver.find_attr_invoc() or a new method of ext::base::Resolver.

match *self {
DeriveType::Legacy => is_legacy_derive(cx, titem),
DeriveType::ProcMacro => is_proc_macro_derive(cx, titem),
DeriveType::Builtin => is_builtin_derive(cx, titem),
}
}
}

pub fn get_derive_attr(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>,
derive_type: DeriveType) -> Option<ast::Attribute> {
for i in 0..attrs.len() {
if attrs[i].name() != "derive" {
continue;
}

if attrs[i].value_str().is_some() {
continue;
}

let mut traits = attrs[i].meta_item_list().unwrap_or(&[]).to_owned();

// First, weed out malformed #[derive]
traits.retain(|titem| titem.word().is_some());

let mut titem = None;

// See if we can find a matching trait.
for j in 0..traits.len() {
if derive_type.is_derive_type(cx, &traits[j]) {
titem = Some(traits.remove(j));
break;
}
}

// If we find a trait, remove the trait from the attribute.
if let Some(titem) = titem {
if traits.len() == 0 {
attrs.remove(i);
} else {
let derive = Symbol::intern("derive");
let mitem = cx.meta_list(titem.span, derive, traits);
attrs[i] = cx.attribute(titem.span, mitem);
}
let derive = Symbol::intern("derive");
let mitem = cx.meta_list(titem.span, derive, vec![titem]);
return Some(cx.attribute(mitem.span, mitem));
}
}
return None;
}

pub fn get_legacy_derive(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>)
-> Option<ast::Attribute> {
get_derive_attr(cx, attrs, DeriveType::Legacy).and_then(|a| {
let titem = derive_attr_trait(cx, &a);
if let Some(titem) = titem {
let tword = titem.word().unwrap();
let tname = tword.name();
if !cx.ecfg.enable_custom_derive() {
feature_gate::emit_feature_err(&cx.parse_sess,
"custom_derive",
titem.span,
feature_gate::GateIssue::Language,
feature_gate::EXPLAIN_CUSTOM_DERIVE);
} else {
let name = Symbol::intern(&format!("derive_{}", tname));
if !cx.resolver.is_whitelisted_legacy_custom_derive(name) {
cx.span_warn(titem.span, feature_gate::EXPLAIN_DEPR_CUSTOM_DERIVE);
}
let mitem = cx.meta_word(titem.span, name);
return Some(cx.attribute(mitem.span, mitem));
}
}
None
})
}

pub fn get_proc_macro_derive(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>)
-> Option<ast::Attribute> {
get_derive_attr(cx, attrs, DeriveType::ProcMacro)
}

pub fn get_builtin_derive(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>)
-> Option<ast::Attribute> {
get_derive_attr(cx, attrs, DeriveType::Builtin)
}

pub fn is_legacy_derive(cx: &mut ExtCtxt, titem: &NestedMetaItem) -> bool {
let tname = titem.name().unwrap();
let name = Symbol::intern(&format!("derive_{}", tname));
let path = ast::Path::from_ident(titem.span, ast::Ident::with_empty_ctxt(name));
!is_proc_macro_derive(cx, titem) && !is_builtin_derive(cx, titem) &&
cx.resolver.resolve_macro(cx.current_expansion.mark, &path, false).is_ok()
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we would just resolve in resolver.builtin_macros, either by moving this logic into resolve or adding a method to check builtin macros directly to ext::base::Resolver.

}

pub fn is_builtin_derive(cx: &mut ExtCtxt, titem: &NestedMetaItem) -> bool {
let tname = titem.name().unwrap();
let derive_mode = ast::Path::from_ident(titem.span, ast::Ident::with_empty_ctxt(tname));
cx.resolver.resolve_macro(cx.current_expansion.mark, &derive_mode, false).map(|ext| {
Copy link
Contributor

Choose a reason for hiding this comment

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

(same here, re resolver.builtin_macros)

if let SyntaxExtension::BuiltinDerive(_) = *ext { true } else { false }
}).unwrap_or(false)
}

pub fn is_proc_macro_derive(cx: &mut ExtCtxt, titem: &NestedMetaItem) -> bool {
let tname = titem.name().unwrap();
let derive_mode = ast::Path::from_ident(titem.span, ast::Ident::with_empty_ctxt(tname));
cx.resolver.resolve_macro(cx.current_expansion.mark, &derive_mode, false).map(|ext| {
if let SyntaxExtension::ProcMacroDerive(_) = *ext { true } else { false }
}).unwrap_or(false)
}

fn allow_unstable(cx: &mut ExtCtxt, span: Span, attr_name: &str) -> Span {
Span {
expn_id: cx.codemap().record_expansion(codemap::ExpnInfo {
call_site: span,
callee: codemap::NameAndSpan {
format: codemap::MacroAttribute(Symbol::intern(attr_name)),
span: Some(span),
allow_internal_unstable: true,
},
}),
..span
}
}

pub fn add_derived_markers(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>) {
if attrs.is_empty() {
return;
}

let titems = attrs.iter().filter(|a| {
a.name() == "derive"
}).flat_map(|a| {
a.meta_item_list().unwrap().iter()
}).filter_map(|titem| {
titem.name()
}).collect::<Vec<_>>();

let span = attrs[0].span;

if !attrs.iter().any(|a| a.name() == "structural_match") &&
titems.iter().any(|t| *t == "PartialEq") && titems.iter().any(|t| *t == "Eq") {
let structural_match = Symbol::intern("structural_match");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: indent the if body one more space

let span = allow_unstable(cx, span, "derive(PartialEq, Eq)");
let meta = cx.meta_word(span, structural_match);
attrs.push(cx.attribute(span, meta));
}

if !attrs.iter().any(|a| a.name() == "rustc_copy_clone_marker") &&
titems.iter().any(|t| *t == "Copy") && titems.iter().any(|t| *t == "Clone") {
let structural_match = Symbol::intern("structural_match");
Copy link
Contributor

Choose a reason for hiding this comment

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

(same here)

let span = allow_unstable(cx, span, "derive(Copy, Clone)");
let meta = cx.meta_word(span, structural_match);
attrs.push(cx.attribute(span, meta));
}
}
Loading