Skip to content

Commit

Permalink
Implement #[proc_macro_attribute]
Browse files Browse the repository at this point in the history
* Add support for `#[proc_macro]`

* Reactivate `proc_macro` feature and gate `#[proc_macro_attribute]` under it

* Have `#![feature(proc_macro)]` imply `#![feature(use_extern_macros)]`,
error on legacy import of proc macros via `#[macro_use]`
  • Loading branch information
abonander committed Jan 17, 2017
1 parent f6c0c48 commit 375cbd2
Show file tree
Hide file tree
Showing 16 changed files with 526 additions and 79 deletions.
1 change: 1 addition & 0 deletions src/librustc_driver/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ pub fn phase_2_configure_and_expand<F>(sess: &Session,
should_test: sess.opts.test,
..syntax::ext::expand::ExpansionConfig::default(crate_name.to_string())
};

let mut ecx = ExtCtxt::new(&sess.parse_sess, cfg, &mut resolver);
let err_count = ecx.parse_sess.span_diagnostic.err_count();

Expand Down
10 changes: 10 additions & 0 deletions src/librustc_metadata/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ impl<'a> CrateLoader<'a> {
use proc_macro::__internal::Registry;
use rustc_back::dynamic_lib::DynamicLibrary;
use syntax_ext::deriving::custom::CustomDerive;
use syntax_ext::proc_macro_impl::AttrProcMacro;

let path = match dylib {
Some(dylib) => dylib,
Expand Down Expand Up @@ -613,6 +614,15 @@ impl<'a> CrateLoader<'a> {
);
self.0.push((Symbol::intern(trait_name), Rc::new(derive)));
}

fn register_attr_proc_macro(&mut self,
name: &str,
expand: fn(TokenStream, TokenStream) -> TokenStream) {
let expand = SyntaxExtension::AttrProcMacro(
Box::new(AttrProcMacro { inner: expand })
);
self.0.push((Symbol::intern(name), Rc::new(expand)));
}
}

let mut my_registrar = MyRegistrar(Vec::new());
Expand Down
46 changes: 44 additions & 2 deletions src/librustc_resolve/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ use syntax::ast::{FnDecl, ForeignItem, ForeignItemKind, Generics};
use syntax::ast::{Item, ItemKind, ImplItem, ImplItemKind};
use syntax::ast::{Local, Mutability, Pat, PatKind, Path};
use syntax::ast::{QSelf, TraitItemKind, TraitRef, Ty, TyKind};
use syntax::feature_gate::{emit_feature_err, GateIssue};
use syntax::feature_gate::{feature_err, emit_feature_err, GateIssue};

use syntax_pos::{Span, DUMMY_SP, MultiSpan};
use errors::DiagnosticBuilder;
Expand Down Expand Up @@ -1123,6 +1123,12 @@ pub struct Resolver<'a> {

// Avoid duplicated errors for "name already defined".
name_already_seen: FxHashMap<Name, Span>,

// If `#![feature(proc_macro)]` is set
proc_macro_enabled: bool,

// A set of procedural macros imported by `#[macro_use]` that have already been warned about
warned_proc_macros: FxHashSet<Name>,
}

pub struct ResolverArenas<'a> {
Expand Down Expand Up @@ -1227,6 +1233,8 @@ impl<'a> Resolver<'a> {
invocations.insert(Mark::root(),
arenas.alloc_invocation_data(InvocationData::root(graph_root)));

let features = session.features.borrow();

Resolver {
session: session,

Expand Down Expand Up @@ -1284,7 +1292,9 @@ impl<'a> Resolver<'a> {
span: DUMMY_SP,
vis: ty::Visibility::Public,
}),
use_extern_macros: session.features.borrow().use_extern_macros,

// `#![feature(proc_macro)]` implies `#[feature(extern_macros)]`
use_extern_macros: features.use_extern_macros || features.proc_macro,

exported_macros: Vec::new(),
crate_loader: crate_loader,
Expand All @@ -1296,6 +1306,8 @@ impl<'a> Resolver<'a> {
invocations: invocations,
name_already_seen: FxHashMap(),
whitelisted_legacy_custom_derives: Vec::new(),
proc_macro_enabled: features.proc_macro,
warned_proc_macros: FxHashSet(),
}
}

Expand Down Expand Up @@ -1525,6 +1537,8 @@ impl<'a> Resolver<'a> {

debug!("(resolving item) resolving {}", name);

self.check_proc_macro_attrs(&item.attrs);

match item.node {
ItemKind::Enum(_, ref generics) |
ItemKind::Ty(_, ref generics) |
Expand Down Expand Up @@ -1554,6 +1568,8 @@ impl<'a> Resolver<'a> {
walk_list!(this, visit_ty_param_bound, bounds);

for trait_item in trait_items {
this.check_proc_macro_attrs(&trait_item.attrs);

match trait_item.node {
TraitItemKind::Const(_, ref default) => {
// Only impose the restrictions of
Expand Down Expand Up @@ -1738,6 +1754,7 @@ impl<'a> Resolver<'a> {
this.with_self_rib(Def::SelfTy(trait_id, Some(item_def_id)), |this| {
this.with_current_self_type(self_type, |this| {
for impl_item in impl_items {
this.check_proc_macro_attrs(&impl_item.attrs);
this.resolve_visibility(&impl_item.vis);
match impl_item.node {
ImplItemKind::Const(..) => {
Expand Down Expand Up @@ -3184,6 +3201,31 @@ impl<'a> Resolver<'a> {
let msg = "`self` no longer imports values".to_string();
self.session.add_lint(lint::builtin::LEGACY_IMPORTS, id, span, msg);
}

fn check_proc_macro_attrs(&mut self, attrs: &[ast::Attribute]) {
if self.proc_macro_enabled { return; }

for attr in attrs {
let maybe_binding = self.builtin_macros.get(&attr.name()).cloned().or_else(|| {
let ident = Ident::with_empty_ctxt(attr.name());
self.resolve_lexical_macro_path_segment(ident, MacroNS, None).ok()
});

if let Some(binding) = maybe_binding {
if let SyntaxExtension::AttrProcMacro(..) = *binding.get_macro(self) {
attr::mark_known(attr);

let msg = "attribute procedural macros are experimental";
let feature = "proc_macro";

feature_err(&self.session.parse_sess, feature,
attr.span, GateIssue::Language, msg)
.span_note(binding.span, "procedural macro imported here")
.emit();
}
}
}
}
}

fn is_struct_like(def: Def) -> bool {
Expand Down
43 changes: 42 additions & 1 deletion src/librustc_resolve/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use syntax::ext::base::{NormalTT, Resolver as SyntaxResolver, SyntaxExtension};
use syntax::ext::expand::{Expansion, mark_tts};
use syntax::ext::hygiene::Mark;
use syntax::ext::tt::macro_rules;
use syntax::feature_gate::{emit_feature_err, GateIssue};
use syntax::feature_gate::{emit_feature_err, GateIssue, is_builtin_attr};
use syntax::fold::{self, Folder};
use syntax::ptr::P;
use syntax::symbol::keywords;
Expand Down Expand Up @@ -183,6 +183,10 @@ impl<'a> base::Resolver for Resolver<'a> {
},
None => {}
}

if self.proc_macro_enabled && !is_builtin_attr(&attrs[i]) {
return Some(attrs.remove(i));
}
}
None
}
Expand Down Expand Up @@ -373,6 +377,10 @@ impl<'a> Resolver<'a> {
let resolution = self.resolve_lexical_macro_path_segment(ident, MacroNS, Some(span));
let (legacy_resolution, resolution) = match (legacy_resolution, resolution) {
(Some(legacy_resolution), Ok(resolution)) => (legacy_resolution, resolution),
(Some(MacroBinding::Modern(binding)), Err(_)) => {
self.err_if_macro_use_proc_macro(ident.name, span, binding);
continue
},
_ => continue,
};
let (legacy_span, participle) = match legacy_resolution {
Expand Down Expand Up @@ -469,4 +477,37 @@ impl<'a> Resolver<'a> {
self.exported_macros.push(def);
}
}

/// Error if `ext` is a Macros 1.1 procedural macro being imported by `#[macro_use]`
fn err_if_macro_use_proc_macro(&mut self, name: Name, use_span: Span,
binding: &NameBinding<'a>) {
use self::SyntaxExtension::*;

let krate = binding.def().def_id().krate;

// Plugin-based syntax extensions are exempt from this check
if krate == BUILTIN_MACROS_CRATE { return; }

let ext = binding.get_macro(self);

match *ext {
// If `ext` is a procedural macro, check if we've already warned about it
AttrProcMacro(_) | ProcMacro(_) => if !self.warned_proc_macros.insert(name) { return; },
_ => return,
}

let warn_msg = match *ext {
AttrProcMacro(_) => "attribute procedural macros cannot be \
imported with `#[macro_use]`",
ProcMacro(_) => "procedural macros cannot be imported with `#[macro_use]`",
_ => return,
};

let crate_name = self.session.cstore.crate_name(krate);

self.session.struct_span_err(use_span, warn_msg)
.help(&format!("instead, import the procedural macro like any other item: \
`use {}::{};`", crate_name, name))
.emit();
}
}
30 changes: 27 additions & 3 deletions src/libsyntax/ext/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
kind.expect_from_annotatables(items)
}
SyntaxExtension::AttrProcMacro(ref mac) => {
let attr_toks = TokenStream::from_tts(tts_for_attr(&attr, &self.cx.parse_sess));
let attr_toks = TokenStream::from_tts(tts_for_attr_args(&attr,
&self.cx.parse_sess));

let item_toks = TokenStream::from_tts(tts_for_item(&item, &self.cx.parse_sess));

let tok_result = mac.expand(self.cx, attr.span, attr_toks, item_toks);
Expand Down Expand Up @@ -640,8 +642,30 @@ fn tts_for_item(item: &Annotatable, parse_sess: &ParseSess) -> Vec<TokenTree> {
string_to_tts(text, parse_sess)
}

fn tts_for_attr(attr: &ast::Attribute, parse_sess: &ParseSess) -> Vec<TokenTree> {
string_to_tts(pprust::attr_to_string(attr), parse_sess)
fn tts_for_attr_args(attr: &ast::Attribute, parse_sess: &ParseSess) -> Vec<TokenTree> {
use ast::MetaItemKind::*;
use print::pp::Breaks;
use print::pprust::PrintState;

let token_string = match attr.value.node {
// For `#[foo]`, an empty token
Word => return vec![],
// For `#[foo(bar, baz)]`, returns `(bar, baz)`
List(ref items) => pprust::to_string(|s| {
s.popen()?;
s.commasep(Breaks::Consistent,
&items[..],
|s, i| s.print_meta_list_item(&i))?;
s.pclose()
}),
// For `#[foo = "bar"]`, returns `= "bar"`
NameValue(ref lit) => pprust::to_string(|s| {
s.word_space("=")?;
s.print_literal(lit)
}),
};

string_to_tts(token_string, parse_sess)
}

fn string_to_tts(text: String, parse_sess: &ParseSess) -> Vec<TokenTree> {
Expand Down
60 changes: 57 additions & 3 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use ast::{self, NodeId, PatKind};
use attr;
use codemap::{CodeMap, Spanned};
use syntax_pos::Span;
use errors::{DiagnosticBuilder, Handler};
use errors::{DiagnosticBuilder, Handler, FatalError};
use visit::{self, FnKind, Visitor};
use parse::ParseSess;
use symbol::Symbol;
Expand Down Expand Up @@ -325,6 +325,9 @@ declare_features! (
// The `unadjusted` ABI. Perma unstable.
(active, abi_unadjusted, "1.16.0", None),

// Macros 1.1
(active, proc_macro, "1.16.0", Some(35900)),

// Allows attributes on struct literal fields.
(active, struct_field_attributes, "1.16.0", Some(38814)),
);
Expand Down Expand Up @@ -377,8 +380,6 @@ declare_features! (
// Allows `..` in tuple (struct) patterns
(accepted, dotdot_in_tuple_patterns, "1.14.0", Some(33627)),
(accepted, item_like_imports, "1.14.0", Some(35120)),
// Macros 1.1
(accepted, proc_macro, "1.15.0", Some(35900)),
);
// (changing above list without updating src/doc/reference.md makes @cmr sad)

Expand Down Expand Up @@ -446,6 +447,10 @@ pub fn deprecated_attributes() -> Vec<&'static (&'static str, AttributeType, Att
BUILTIN_ATTRIBUTES.iter().filter(|a| a.2.is_deprecated()).collect()
}

pub fn is_builtin_attr(attr: &ast::Attribute) -> bool {
BUILTIN_ATTRIBUTES.iter().any(|&(builtin_name, _, _)| attr.check_name(builtin_name))
}

// Attributes that have a special meaning to rustc or rustdoc
pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGate)] = &[
// Normal attributes
Expand Down Expand Up @@ -739,6 +744,16 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
is currently unstable",
cfg_fn!(windows_subsystem))),

("proc_macro_attribute", Normal, Gated(Stability::Unstable,
"proc_macro",
"attribute proc macros are currently unstable",
cfg_fn!(proc_macro))),

("rustc_derive_registrar", Normal, Gated(Stability::Unstable,
"rustc_derive_registrar",
"used internally by rustc",
cfg_fn!(rustc_attrs))),

// Crate level attributes
("crate_name", CrateLevel, Ungated),
("crate_type", CrateLevel, Ungated),
Expand Down Expand Up @@ -1380,6 +1395,8 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute]) -> Features {
let mut features = Features::new();

let mut feature_checker = MutexFeatureChecker::default();

for attr in krate_attrs {
if !attr.check_name("feature") {
continue
Expand All @@ -1403,6 +1420,7 @@ pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute]) -> F
if let Some(&(_, _, _, setter)) = ACTIVE_FEATURES.iter()
.find(|& &(n, _, _, _)| name == n) {
*(setter(&mut features)) = true;
feature_checker.collect(&features, mi.span);
}
else if let Some(&(_, _, _)) = REMOVED_FEATURES.iter()
.find(|& &(n, _, _)| name == n) {
Expand All @@ -1419,9 +1437,45 @@ pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute]) -> F
}
}

feature_checker.check(span_handler);

features
}

// A collector for mutually-exclusive features and their flag spans
#[derive(Default)]
struct MutexFeatureChecker {
proc_macro: Option<Span>,
custom_attribute: Option<Span>,
}

impl MutexFeatureChecker {
// If this method turns out to be a hotspot due to branching,
// the branching can be eliminated by modifying `setter!()` to set these spans
// only for the features that need to be checked for mutual exclusion.
fn collect(&mut self, features: &Features, span: Span) {
if features.proc_macro {
// If self.proc_macro is None, set to Some(span)
self.proc_macro = self.proc_macro.or(Some(span));
}

if features.custom_attribute {
self.custom_attribute = self.custom_attribute.or(Some(span));
}
}

fn check(self, handler: &Handler) {
if let (Some(pm_span), Some(ca_span)) = (self.proc_macro, self.custom_attribute) {
handler.struct_span_err(pm_span, "Cannot use `#![feature(proc_macro)]` and \
`#![feature(custom_attribute)] at the same time")
.span_note(ca_span, "`#![feature(custom_attribute)]` declared here")
.emit();

panic!(FatalError);
}
}
}

pub fn check_crate(krate: &ast::Crate,
sess: &ParseSess,
features: &Features,
Expand Down
2 changes: 2 additions & 0 deletions src/libsyntax_ext/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub mod proc_macro_registrar;
// for custom_derive
pub mod deriving;

pub mod proc_macro_impl;

use std::rc::Rc;
use syntax::ast;
use syntax::ext::base::{MacroExpanderFn, NormalTT, MultiModifier, NamedSyntaxExtension};
Expand Down
Loading

0 comments on commit 375cbd2

Please sign in to comment.