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

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

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 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
8 changes: 8 additions & 0 deletions src/librustc_resolve/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,14 @@ impl<'a> base::Resolver for Resolver<'a> {
}
result
}

fn resolve_builtin_macro(&mut self, tname: Name) -> Result<Rc<SyntaxExtension>, Determinacy> {
match self.builtin_macros.get(&tname).cloned() {
Some(binding) => Ok(binding.get_macro(self)),
None => Err(Determinacy::Undetermined),
}
}

}

impl<'a> Resolver<'a> {
Expand Down
18 changes: 16 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 All @@ -526,6 +536,7 @@ pub trait Resolver {
fn find_attr_invoc(&mut self, attrs: &mut Vec<Attribute>) -> Option<Attribute>;
fn resolve_macro(&mut self, scope: Mark, path: &ast::Path, force: bool)
-> Result<Rc<SyntaxExtension>, Determinacy>;
fn resolve_builtin_macro(&mut self, tname: Name) -> Result<Rc<SyntaxExtension>, Determinacy>;
}

#[derive(Copy, Clone, Debug)]
Expand All @@ -552,6 +563,9 @@ impl Resolver for DummyResolver {
-> Result<Rc<SyntaxExtension>, Determinacy> {
Err(Determinacy::Determined)
}
fn resolve_builtin_macro(&mut self, _tname: Name) -> Result<Rc<SyntaxExtension>, Determinacy> {
Err(Determinacy::Determined)
}
}

#[derive(Clone)]
Expand Down
220 changes: 220 additions & 0 deletions src/libsyntax/ext/derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// 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 ast::Name;
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_or(&[]);

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: &[ast::Attribute]) {
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`");
attr::mark_used(&attr);
continue;
}
for titem in traits {
if titem.word().is_none() {
cx.span_err(titem.span, "malformed `derive` entry");
}
}
}
}

#[derive(PartialEq, Debug, Clone, Copy)]
pub enum DeriveType {
Legacy,
ProcMacro,
Builtin
}

impl DeriveType {
// Classify a derive trait name by resolving the macro.
pub fn classify(cx: &mut ExtCtxt, tname: Name) -> Option<DeriveType> {
let legacy_derive_name = Symbol::intern(&format!("derive_{}", tname));

if let Ok(_) = cx.resolver.resolve_builtin_macro(legacy_derive_name) {
return Some(DeriveType::Legacy);
}

match cx.resolver.resolve_builtin_macro(tname) {
Ok(ext) => match *ext {
SyntaxExtension::BuiltinDerive(..) => Some(DeriveType::Builtin),
SyntaxExtension::ProcMacroDerive(..) => Some(DeriveType::ProcMacro),
_ => None,
},
Err(_) => Some(DeriveType::ProcMacro),
}
}
}

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() {
let tname = if let Some(tname) = traits[j].name() {
tname
} else {
continue;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: using a match is more idiomatic here


if DeriveType::classify(cx, tname) == Some(derive_type) {
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;
}

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_or(&[]).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") {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this line should be un-indented one space.

let structural_match = Symbol::intern("structural_match");
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") {
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 structural_match = Symbol::intern("rustc_copy_clone_marker");
let span = allow_unstable(cx, span, "derive(Copy, Clone)");
let meta = cx.meta_word(span, structural_match);
attrs.push(cx.attribute(span, meta));
}
}

pub fn find_derive_attr(cx: &mut ExtCtxt, attrs: &mut Vec<ast::Attribute>)
-> Option<ast::Attribute> {
verify_derive_attrs(cx, attrs);
get_derive_attr(cx, attrs, DeriveType::Legacy).and_then(|a| {
let titem = derive_attr_trait(cx, &a);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: too much indent

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

Choose a reason for hiding this comment

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

the above four lines should be un-indented a space

);
} 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);
Copy link
Contributor

Choose a reason for hiding this comment

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

(wrong indent)

}
let mitem = cx.meta_word(titem.span, name);
return Some(cx.attribute(mitem.span, mitem));
}
}
None
Copy link
Contributor

Choose a reason for hiding this comment

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

Using an else to avoid the above return Some is more idiomatic

}).or_else(|| {
add_derived_markers(cx, attrs);
get_derive_attr(cx, attrs, DeriveType::Builtin)
}).or_else(|| {
get_derive_attr(cx, attrs, DeriveType::ProcMacro)
})
}
Loading