Skip to content

Commit

Permalink
rustc: Disallow modules and macros in expansions
Browse files Browse the repository at this point in the history
This commit feature gates generating modules and macro definitions in procedural
macro expansions. Custom derive is exempt from this check as it would be a large
retroactive breaking change (#50587). It's hoped that we can hopefully stem the
bleeding to figure out a better solution here before opening up the floodgates.

The restriction here is specifically targeted at surprising hygiene results [1]
that result in non-"copy/paste" behavior. Hygiene and procedural macros is
intended to be avoided as much as possible for Macros 1.2 by saying everything
is "as if you copy/pasted the code", but modules and macros are sort of weird
exceptions to this rule that aren't fully fleshed out.

[1]: #50504 (comment)

cc #50504
  • Loading branch information
alexcrichton committed May 18, 2018
1 parent 4208bd5 commit 5e4bac3
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 6 deletions.
60 changes: 55 additions & 5 deletions src/libsyntax/ext/expand.rs
Expand Up @@ -21,7 +21,7 @@ use ext::placeholders::{placeholder, PlaceholderExpander};
use feature_gate::{self, Features, GateIssue, is_builtin_attr, emit_feature_err};
use fold;
use fold::*;
use parse::{DirectoryOwnership, PResult};
use parse::{DirectoryOwnership, PResult, ParseSess};
use parse::token::{self, Token};
use parse::parser::Parser;
use ptr::P;
Expand All @@ -31,7 +31,7 @@ use syntax_pos::{Span, DUMMY_SP, FileName};
use syntax_pos::hygiene::ExpnFormat;
use tokenstream::{TokenStream, TokenTree};
use util::small_vector::SmallVector;
use visit::Visitor;
use visit::{self, Visitor};

use std::collections::HashMap;
use std::fs::File;
Expand Down Expand Up @@ -532,7 +532,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
})).into();
let input = self.extract_proc_macro_attr_input(attr.tokens, attr.span);
let tok_result = mac.expand(self.cx, attr.span, input, item_tok);
self.parse_expansion(tok_result, kind, &attr.path, attr.span)
let res = self.parse_expansion(tok_result, kind, &attr.path, attr.span);
self.gate_proc_macro_expansion(attr.span, &res);
res
}
ProcMacroDerive(..) | BuiltinDerive(..) => {
self.cx.span_err(attr.span, &format!("`{}` is a derive mode", attr.path));
Expand Down Expand Up @@ -591,6 +593,50 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
);
}

fn gate_proc_macro_expansion(&self, span: Span, expansion: &Option<Expansion>) {
if self.cx.ecfg.proc_macro_gen() {
return
}
let expansion = match expansion {
Some(expansion) => expansion,
None => return,
};

expansion.visit_with(&mut DisallowModules {
span,
parse_sess: self.cx.parse_sess,
});

struct DisallowModules<'a> {
span: Span,
parse_sess: &'a ParseSess,
}

impl<'ast, 'a> Visitor<'ast> for DisallowModules<'a> {
fn visit_item(&mut self, i: &'ast ast::Item) {
let name = match i.node {
ast::ItemKind::Mod(_) => Some("modules"),
ast::ItemKind::MacroDef(_) => Some("macro definitions"),
_ => None,
};
if let Some(name) = name {
emit_feature_err(
self.parse_sess,
"proc_macro_gen",
self.span,
GateIssue::Language,
&format!("procedural macros cannot expand to {}", name),
);
}
visit::walk_item(self, i);
}

fn visit_mac(&mut self, _mac: &'ast ast::Mac) {
// ...
}
}
}

/// Expand a macro invocation. Returns the result of expansion.
fn expand_bang_invoc(&mut self,
invoc: Invocation,
Expand Down Expand Up @@ -732,7 +778,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
});

let tok_result = expandfun.expand(self.cx, span, mac.node.stream());
self.parse_expansion(tok_result, kind, path, span)
let result = self.parse_expansion(tok_result, kind, path, span);
self.gate_proc_macro_expansion(span, &result);
result
}
}
};
Expand Down Expand Up @@ -814,7 +862,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
span: DUMMY_SP,
node: ast::MetaItemKind::Word,
};
Some(kind.expect_from_annotatables(ext.expand(self.cx, span, &dummy, item)))
let items = ext.expand(self.cx, span, &dummy, item);
Some(kind.expect_from_annotatables(items))
}
BuiltinDerive(func) => {
expn_info.callee.allow_internal_unstable = true;
Expand Down Expand Up @@ -1491,6 +1540,7 @@ impl<'feat> ExpansionConfig<'feat> {
fn proc_macro_enabled = proc_macro,
fn macros_in_extern_enabled = macros_in_extern,
fn proc_macro_mod = proc_macro_mod,
fn proc_macro_gen = proc_macro_gen,
fn proc_macro_expr = proc_macro_expr,
fn proc_macro_non_items = proc_macro_non_items,
}
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/feature_gate.rs
Expand Up @@ -451,6 +451,7 @@ declare_features! (
(active, proc_macro_mod, "1.27.0", None, None),
(active, proc_macro_expr, "1.27.0", None, None),
(active, proc_macro_non_items, "1.27.0", None, None),
(active, proc_macro_gen, "1.27.0", None, None),

// #[doc(alias = "...")]
(active, doc_alias, "1.27.0", Some(50146), None),
Expand Down
56 changes: 56 additions & 0 deletions src/test/compile-fail-fulldeps/proc-macro/auxiliary/more-gates.rs
@@ -0,0 +1,56 @@
// Copyright 2018 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)]

extern crate proc_macro;

use proc_macro::*;

#[proc_macro_attribute]
pub fn attr2mod(_: TokenStream, _: TokenStream) -> TokenStream {
"mod test {}".parse().unwrap()
}

#[proc_macro_attribute]
pub fn attr2mac1(_: TokenStream, _: TokenStream) -> TokenStream {
"macro_rules! foo1 { (a) => (a) }".parse().unwrap()
}

#[proc_macro_attribute]
pub fn attr2mac2(_: TokenStream, _: TokenStream) -> TokenStream {
"macro foo2(a) { a }".parse().unwrap()
}

#[proc_macro]
pub fn mac2mod(_: TokenStream) -> TokenStream {
"mod test2 {}".parse().unwrap()
}

#[proc_macro]
pub fn mac2mac1(_: TokenStream) -> TokenStream {
"macro_rules! foo3 { (a) => (a) }".parse().unwrap()
}

#[proc_macro]
pub fn mac2mac2(_: TokenStream) -> TokenStream {
"macro foo4(a) { a }".parse().unwrap()
}

#[proc_macro]
pub fn tricky(_: TokenStream) -> TokenStream {
"fn foo() {
mod test {}
macro_rules! foo { (a) => (a) }
}".parse().unwrap()
}
37 changes: 37 additions & 0 deletions src/test/compile-fail-fulldeps/proc-macro/more-gates.rs
@@ -0,0 +1,37 @@
// Copyright 2018 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:more-gates.rs

#![feature(proc_macro)]

extern crate more_gates as foo;

use foo::*;

#[attr2mod]
//~^ ERROR: cannot expand to modules
pub fn a() {}
#[attr2mac1]
//~^ ERROR: cannot expand to macro definitions
pub fn a() {}
#[attr2mac2]
//~^ ERROR: cannot expand to macro definitions
pub fn a() {}

mac2mod!(); //~ ERROR: cannot expand to modules
mac2mac1!(); //~ ERROR: cannot expand to macro definitions
mac2mac2!(); //~ ERROR: cannot expand to macro definitions

tricky!();
//~^ ERROR: cannot expand to modules
//~| ERROR: cannot expand to macro definitions

fn main() {}
3 changes: 3 additions & 0 deletions src/test/compile-fail-fulldeps/proc-macro/proc-macro-gates.rs
Expand Up @@ -14,6 +14,7 @@
// gate-test-proc_macro_mod line
// gate-test-proc_macro_expr
// gate-test-proc_macro_mod
// gate-test-proc_macro_gen

#![feature(proc_macro, stmt_expr_attributes)]

Expand All @@ -29,10 +30,12 @@ fn _test_inner() {
}

#[a] //~ ERROR: custom attributes cannot be applied to modules
//~| ERROR: procedural macros cannot expand to modules
mod _test2 {}

mod _test2_inner {
#![a] //~ ERROR: custom attributes cannot be applied to modules
//~| ERROR: procedural macros cannot expand to modules
}

#[a = y] //~ ERROR: must only be followed by a delimiter token
Expand Down
2 changes: 1 addition & 1 deletion src/test/run-pass-fulldeps/macro-quote-test.rs
Expand Up @@ -13,7 +13,7 @@
// aux-build:hello_macro.rs
// ignore-stage1

#![feature(use_extern_macros, proc_macro_non_items)]
#![feature(use_extern_macros, proc_macro_non_items, proc_macro_gen)]

extern crate hello_macro;

Expand Down

0 comments on commit 5e4bac3

Please sign in to comment.