Skip to content

Commit

Permalink
Auto merge of #40847 - jseyfried:decl_macro, r=nrc
Browse files Browse the repository at this point in the history
Initial implementation of declarative macros 2.0

Implement declarative macros 2.0 (rust-lang/rfcs#1584) behind `#![feature(decl_macro)]`.
Differences from `macro_rules!` include:
 - new syntax: `macro m(..) { .. }` instead of `macro_rules! m { (..) => { .. } }`
 - declarative macros are items:
```rust
// crate A:
pub mod foo {
    m!(); // use before definition; declaration order is irrelevant
    pub macro m() {} // `pub`, `pub(super)`, etc. work
}
fn main() {
    foo::m!(); // named like other items
    { use foo::m as n; n!(); } // imported like other items
}
pub use foo::m; // re-exported like other items

// crate B:
extern crate A; // no need for `#[macro_use]`
A::foo::m!(); A::m!();
```
 - Racket-like hygiene for items, imports, methods, fields, type parameters, privacy, etc.
   - Intuitively, names in a macro definition are resolved in the macro definition's scope, not the scope in which the macro is used.
   - This [explaination](http://beautifulracket.com/explainer/hygiene.html) of hygiene for Racket applies here (except for the "Breaking Hygiene" section). I wrote a similar [explanation](/~https://github.com/jseyfried/rfcs/blob/hygiene/text/0000-hygiene.md) for Rust.
   - Generally speaking, if `fn f() { <body> }` resolves, `pub macro m() { <body> } ... m!()` also resolves, even if `m!()` is in a separate crate.
   - `::foo::bar` in a `macro` behaves like `$crate::foo::bar` in a `macro_rules!`, except it can access everything visible from the `macro` (thus more permissive).
   - See [`src/test/{run-pass, compile-fail}/hygiene`](afe7d89) for examples. Small example:
```rust
mod foo {
    fn f() { println!("hello world"); }
    pub macro m() { f(); }
}
fn main() { foo::m!(); }
```

Limitations:
 - This does not address planned changes to matchers (`expr`,`ty`, etc.), c.f. #26361.
 - Lints (including stability and deprecation) and `unsafe` are not hygienic.
   - adding hygiene here will be mostly or entirely backwards compatible
 - Nested macro definitions (a `macro` inside another `macro`) don't always work correctly when invoked from external crates.
   - pending improvements in how we encode macro definitions in crate metadata
 - There is no way to "escape" hygiene without using a procedural macro.

r? @nrc
  • Loading branch information
bors committed May 25, 2017
2 parents 5f39668 + dc34ea0 commit 4f9c9ed
Show file tree
Hide file tree
Showing 67 changed files with 1,574 additions and 479 deletions.
10 changes: 10 additions & 0 deletions src/doc/unstable-book/src/language-features/decl-macro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `decl_macro`

The tracking issue for this feature is: [#39412]

[#39412]: /~https://github.com/rust-lang/rust/issues/39412

------------------------



2 changes: 1 addition & 1 deletion src/librustc/hir/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ pub type ExportMap = NodeMap<Vec<Export>>;

#[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable)]
pub struct Export {
pub name: ast::Name, // The name of the target.
pub ident: ast::Ident, // The name of the target.
pub def: Def, // The definition of the target.
pub span: Span, // The span of the target definition.
}
Expand Down
67 changes: 43 additions & 24 deletions src/librustc/hir/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use hir::def_id::{DefIndex, DefId, CRATE_DEF_INDEX};
use hir::def::{Def, PathResolution};
use rustc_data_structures::indexed_vec::IndexVec;
use session::Session;
use util::nodemap::{DefIdMap, NodeMap};
use util::nodemap::{DefIdMap, FxHashMap, NodeMap};

use std::collections::BTreeMap;
use std::fmt::Debug;
Expand Down Expand Up @@ -77,6 +77,7 @@ pub struct LoweringContext<'a> {
// a definition, then we can properly create the def id.
parent_def: Option<DefIndex>,
resolver: &'a mut Resolver,
name_map: FxHashMap<Ident, Name>,

/// The items being lowered are collected here.
items: BTreeMap<NodeId, hir::Item>,
Expand Down Expand Up @@ -126,6 +127,7 @@ pub fn lower_crate(sess: &Session,
sess: sess,
parent_def: None,
resolver: resolver,
name_map: FxHashMap(),
items: BTreeMap::new(),
trait_items: BTreeMap::new(),
impl_items: BTreeMap::new(),
Expand Down Expand Up @@ -393,7 +395,7 @@ impl<'a> LoweringContext<'a> {
}

fn allow_internal_unstable(&self, reason: &'static str, mut span: Span) -> Span {
let mark = Mark::fresh();
let mark = Mark::fresh(Mark::root());
mark.set_expn_info(codemap::ExpnInfo {
call_site: span,
callee: codemap::NameAndSpan {
Expand Down Expand Up @@ -495,6 +497,14 @@ impl<'a> LoweringContext<'a> {
}
}

fn lower_ident(&mut self, ident: Ident) -> Name {
let ident = ident.modern();
if ident.ctxt == SyntaxContext::empty() {
return ident.name;
}
*self.name_map.entry(ident).or_insert_with(|| Symbol::from_ident(ident))
}

fn lower_opt_sp_ident(&mut self, o_id: Option<Spanned<Ident>>) -> Option<Spanned<Name>> {
o_id.map(|sp_ident| respan(sp_ident.span, sp_ident.node.name))
}
Expand Down Expand Up @@ -546,7 +556,7 @@ impl<'a> LoweringContext<'a> {
fn lower_ty_binding(&mut self, b: &TypeBinding) -> hir::TypeBinding {
hir::TypeBinding {
id: self.lower_node_id(b.id),
name: b.ident.name,
name: self.lower_ident(b.ident),
ty: self.lower_ty(&b.ty),
span: b.span,
}
Expand Down Expand Up @@ -844,7 +854,7 @@ impl<'a> LoweringContext<'a> {
}

hir::PathSegment {
name: segment.identifier.name,
name: self.lower_ident(segment.identifier),
parameters: parameters,
}
}
Expand Down Expand Up @@ -941,7 +951,7 @@ impl<'a> LoweringContext<'a> {
}

fn lower_ty_param(&mut self, tp: &TyParam, add_bounds: &[TyParamBound]) -> hir::TyParam {
let mut name = tp.ident.name;
let mut name = self.lower_ident(tp.ident);

// Don't expose `Self` (recovered "keyword used as ident" parse error).
// `rustc::ty` expects `Self` to be only used for a trait's `Self`.
Expand Down Expand Up @@ -975,7 +985,7 @@ impl<'a> LoweringContext<'a> {
fn lower_lifetime(&mut self, l: &Lifetime) -> hir::Lifetime {
hir::Lifetime {
id: self.lower_node_id(l.id),
name: l.name,
name: self.lower_ident(l.ident),
span: l.span,
}
}
Expand Down Expand Up @@ -1137,7 +1147,11 @@ impl<'a> LoweringContext<'a> {
hir::StructField {
span: f.span,
id: self.lower_node_id(f.id),
name: f.ident.map(|ident| ident.name).unwrap_or(Symbol::intern(&index.to_string())),
name: self.lower_ident(match f.ident {
Some(ident) => ident,
// FIXME(jseyfried) positional field hygiene
None => Ident { name: Symbol::intern(&index.to_string()), ctxt: f.span.ctxt },
}),
vis: self.lower_visibility(&f.vis, None),
ty: self.lower_ty(&f.ty),
attrs: self.lower_attrs(&f.attrs),
Expand All @@ -1146,7 +1160,7 @@ impl<'a> LoweringContext<'a> {

fn lower_field(&mut self, f: &Field) -> hir::Field {
hir::Field {
name: respan(f.ident.span, f.ident.node.name),
name: respan(f.ident.span, self.lower_ident(f.ident.node)),
expr: P(self.lower_expr(&f.expr)),
span: f.span,
is_shorthand: f.is_shorthand,
Expand Down Expand Up @@ -1371,7 +1385,7 @@ impl<'a> LoweringContext<'a> {
self.with_parent_def(i.id, |this| {
hir::TraitItem {
id: this.lower_node_id(i.id),
name: i.ident.name,
name: this.lower_ident(i.ident),
attrs: this.lower_attrs(&i.attrs),
node: match i.node {
TraitItemKind::Const(ref ty, ref default) => {
Expand Down Expand Up @@ -1421,7 +1435,7 @@ impl<'a> LoweringContext<'a> {
};
hir::TraitItemRef {
id: hir::TraitItemId { node_id: i.id },
name: i.ident.name,
name: self.lower_ident(i.ident),
span: i.span,
defaultness: self.lower_defaultness(Defaultness::Default, has_default),
kind: kind,
Expand All @@ -1432,7 +1446,7 @@ impl<'a> LoweringContext<'a> {
self.with_parent_def(i.id, |this| {
hir::ImplItem {
id: this.lower_node_id(i.id),
name: i.ident.name,
name: this.lower_ident(i.ident),
attrs: this.lower_attrs(&i.attrs),
vis: this.lower_visibility(&i.vis, None),
defaultness: this.lower_defaultness(i.defaultness, true /* [1] */),
Expand Down Expand Up @@ -1461,7 +1475,7 @@ impl<'a> LoweringContext<'a> {
fn lower_impl_item_ref(&mut self, i: &ImplItem) -> hir::ImplItemRef {
hir::ImplItemRef {
id: hir::ImplItemId { node_id: i.id },
name: i.ident.name,
name: self.lower_ident(i.ident),
span: i.span,
vis: self.lower_visibility(&i.vis, Some(i.id)),
defaultness: self.lower_defaultness(i.defaultness, true /* [1] */),
Expand Down Expand Up @@ -1501,17 +1515,23 @@ impl<'a> LoweringContext<'a> {

pub fn lower_item(&mut self, i: &Item) -> Option<hir::Item> {
let mut name = i.ident.name;
let mut vis = self.lower_visibility(&i.vis, None);
let attrs = self.lower_attrs(&i.attrs);
if let ItemKind::MacroDef(ref tts) = i.node {
if i.attrs.iter().any(|attr| attr.path == "macro_export") {
if let ItemKind::MacroDef(ref def) = i.node {
if !def.legacy || i.attrs.iter().any(|attr| attr.path == "macro_export") {
self.exported_macros.push(hir::MacroDef {
name: name, attrs: attrs, id: i.id, span: i.span, body: tts.clone().into(),
name: name,
vis: vis,
attrs: attrs,
id: i.id,
span: i.span,
body: def.stream(),
legacy: def.legacy,
});
}
return None;
}

let mut vis = self.lower_visibility(&i.vis, None);
let node = self.with_parent_def(i.id, |this| {
this.lower_item_kind(i.id, &mut name, &attrs, &mut vis, &i.node)
});
Expand Down Expand Up @@ -1654,7 +1674,7 @@ impl<'a> LoweringContext<'a> {
Spanned {
span: f.span,
node: hir::FieldPat {
name: f.node.ident.name,
name: self.lower_ident(f.node.ident),
pat: self.lower_pat(&f.node.pat),
is_shorthand: f.node.is_shorthand,
},
Expand Down Expand Up @@ -1824,7 +1844,7 @@ impl<'a> LoweringContext<'a> {
ExprKind::MethodCall(i, ref tps, ref args) => {
let tps = tps.iter().map(|x| self.lower_ty(x)).collect();
let args = args.iter().map(|x| self.lower_expr(x)).collect();
hir::ExprMethodCall(respan(i.span, i.node.name), tps, args)
hir::ExprMethodCall(respan(i.span, self.lower_ident(i.node)), tps, args)
}
ExprKind::Binary(binop, ref lhs, ref rhs) => {
let binop = self.lower_binop(binop);
Expand Down Expand Up @@ -1923,7 +1943,8 @@ impl<'a> LoweringContext<'a> {
P(self.lower_expr(er)))
}
ExprKind::Field(ref el, ident) => {
hir::ExprField(P(self.lower_expr(el)), respan(ident.span, ident.node.name))
hir::ExprField(P(self.lower_expr(el)),
respan(ident.span, self.lower_ident(ident.node)))
}
ExprKind::TupField(ref el, ident) => {
hir::ExprTupField(P(self.lower_expr(el)), ident)
Expand Down Expand Up @@ -2641,11 +2662,9 @@ impl<'a> LoweringContext<'a> {
let parent_def = self.parent_def.unwrap();
let def_id = {
let defs = self.resolver.definitions();
let def_path_data = DefPathData::Binding(name.as_str());
let def_index = defs.create_def_with_parent(parent_def,
id,
def_path_data,
REGULAR_SPACE);
let def_path_data = DefPathData::Binding(Ident::with_empty_ctxt(name));
let def_index = defs
.create_def_with_parent(parent_def, id, def_path_data, REGULAR_SPACE, Mark::root());
DefId::local(def_index)
};

Expand Down
49 changes: 25 additions & 24 deletions src/librustc/hir/map/def_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ use hir::def_id::{CRATE_DEF_INDEX, DefIndex, DefIndexAddressSpace};
use syntax::ast::*;
use syntax::ext::hygiene::Mark;
use syntax::visit;
use syntax::symbol::{Symbol, keywords};
use syntax::symbol::keywords;

use hir::map::{ITEM_LIKE_SPACE, REGULAR_SPACE};

/// Creates def ids for nodes in the AST.
pub struct DefCollector<'a> {
definitions: &'a mut Definitions,
parent_def: Option<DefIndex>,
expansion: Mark,
pub visit_macro_invoc: Option<&'a mut FnMut(MacroInvocationData)>,
}

Expand All @@ -32,9 +33,10 @@ pub struct MacroInvocationData {
}

impl<'a> DefCollector<'a> {
pub fn new(definitions: &'a mut Definitions) -> Self {
pub fn new(definitions: &'a mut Definitions, expansion: Mark) -> Self {
DefCollector {
definitions: definitions,
expansion: expansion,
parent_def: None,
visit_macro_invoc: None,
}
Expand All @@ -54,7 +56,8 @@ impl<'a> DefCollector<'a> {
-> DefIndex {
let parent_def = self.parent_def.unwrap();
debug!("create_def(node_id={:?}, data={:?}, parent_def={:?})", node_id, data, parent_def);
self.definitions.create_def_with_parent(parent_def, node_id, data, address_space)
self.definitions
.create_def_with_parent(parent_def, node_id, data, address_space, self.expansion)
}

pub fn with_parent<F: FnOnce(&mut Self)>(&mut self, parent_def: DefIndex, f: F) {
Expand Down Expand Up @@ -100,14 +103,14 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {
DefPathData::Impl,
ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Union(..) | ItemKind::Trait(..) |
ItemKind::ExternCrate(..) | ItemKind::ForeignMod(..) | ItemKind::Ty(..) =>
DefPathData::TypeNs(i.ident.name.as_str()),
DefPathData::TypeNs(i.ident.modern()),
ItemKind::Mod(..) if i.ident == keywords::Invalid.ident() => {
return visit::walk_item(self, i);
}
ItemKind::Mod(..) => DefPathData::Module(i.ident.name.as_str()),
ItemKind::Mod(..) => DefPathData::Module(i.ident.modern()),
ItemKind::Static(..) | ItemKind::Const(..) | ItemKind::Fn(..) =>
DefPathData::ValueNs(i.ident.name.as_str()),
ItemKind::MacroDef(..) => DefPathData::MacroDef(i.ident.name.as_str()),
DefPathData::ValueNs(i.ident.modern()),
ItemKind::MacroDef(..) => DefPathData::MacroDef(i.ident.modern()),
ItemKind::Mac(..) => return self.visit_macro_invoc(i.id, false),
ItemKind::GlobalAsm(..) => DefPathData::Misc,
ItemKind::Use(ref view_path) => {
Expand Down Expand Up @@ -135,15 +138,13 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {
for v in &enum_definition.variants {
let variant_def_index =
this.create_def(v.node.data.id(),
DefPathData::EnumVariant(v.node.name.name.as_str()),
DefPathData::EnumVariant(v.node.name.modern()),
REGULAR_SPACE);
this.with_parent(variant_def_index, |this| {
for (index, field) in v.node.data.fields().iter().enumerate() {
let name = field.ident.map(|ident| ident.name)
.unwrap_or_else(|| Symbol::intern(&index.to_string()));
this.create_def(field.id,
DefPathData::Field(name.as_str()),
REGULAR_SPACE);
let ident = field.ident.map(Ident::modern)
.unwrap_or_else(|| Ident::from_str(&index.to_string()));
this.create_def(field.id, DefPathData::Field(ident), REGULAR_SPACE);
}

if let Some(ref expr) = v.node.disr_expr {
Expand All @@ -161,9 +162,9 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {
}

for (index, field) in struct_def.fields().iter().enumerate() {
let name = field.ident.map(|ident| ident.name.as_str())
.unwrap_or(Symbol::intern(&index.to_string()).as_str());
this.create_def(field.id, DefPathData::Field(name), REGULAR_SPACE);
let ident = field.ident.map(Ident::modern)
.unwrap_or_else(|| Ident::from_str(&index.to_string()));
this.create_def(field.id, DefPathData::Field(ident), REGULAR_SPACE);
}
}
_ => {}
Expand All @@ -174,7 +175,7 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {

fn visit_foreign_item(&mut self, foreign_item: &'a ForeignItem) {
let def = self.create_def(foreign_item.id,
DefPathData::ValueNs(foreign_item.ident.name.as_str()),
DefPathData::ValueNs(foreign_item.ident.modern()),
REGULAR_SPACE);

self.with_parent(def, |this| {
Expand All @@ -185,7 +186,7 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {
fn visit_generics(&mut self, generics: &'a Generics) {
for ty_param in generics.ty_params.iter() {
self.create_def(ty_param.id,
DefPathData::TypeParam(ty_param.ident.name.as_str()),
DefPathData::TypeParam(ty_param.ident.modern()),
REGULAR_SPACE);
}

Expand All @@ -195,8 +196,8 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {
fn visit_trait_item(&mut self, ti: &'a TraitItem) {
let def_data = match ti.node {
TraitItemKind::Method(..) | TraitItemKind::Const(..) =>
DefPathData::ValueNs(ti.ident.name.as_str()),
TraitItemKind::Type(..) => DefPathData::TypeNs(ti.ident.name.as_str()),
DefPathData::ValueNs(ti.ident.modern()),
TraitItemKind::Type(..) => DefPathData::TypeNs(ti.ident.modern()),
TraitItemKind::Macro(..) => return self.visit_macro_invoc(ti.id, false),
};

Expand All @@ -213,8 +214,8 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {
fn visit_impl_item(&mut self, ii: &'a ImplItem) {
let def_data = match ii.node {
ImplItemKind::Method(..) | ImplItemKind::Const(..) =>
DefPathData::ValueNs(ii.ident.name.as_str()),
ImplItemKind::Type(..) => DefPathData::TypeNs(ii.ident.name.as_str()),
DefPathData::ValueNs(ii.ident.modern()),
ImplItemKind::Type(..) => DefPathData::TypeNs(ii.ident.modern()),
ImplItemKind::Macro(..) => return self.visit_macro_invoc(ii.id, false),
};

Expand All @@ -235,7 +236,7 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {
PatKind::Mac(..) => return self.visit_macro_invoc(pat.id, false),
PatKind::Ident(_, id, _) => {
let def = self.create_def(pat.id,
DefPathData::Binding(id.node.name.as_str()),
DefPathData::Binding(id.node.modern()),
REGULAR_SPACE);
self.parent_def = Some(def);
}
Expand Down Expand Up @@ -280,7 +281,7 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {

fn visit_lifetime_def(&mut self, def: &'a LifetimeDef) {
self.create_def(def.lifetime.id,
DefPathData::LifetimeDef(def.lifetime.name.as_str()),
DefPathData::LifetimeDef(def.lifetime.ident.modern()),
REGULAR_SPACE);
}

Expand Down
Loading

0 comments on commit 4f9c9ed

Please sign in to comment.