From 5e4bac31b82241db4ca8e9234a4f368c30e8966a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 9 May 2018 15:03:02 -0700 Subject: [PATCH] rustc: Disallow modules and macros in expansions 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]: https://github.com/rust-lang/rust/issues/50504#issuecomment-387734625 cc #50504 --- src/libsyntax/ext/expand.rs | 60 +++++++++++++++++-- src/libsyntax/feature_gate.rs | 1 + .../proc-macro/auxiliary/more-gates.rs | 56 +++++++++++++++++ .../proc-macro/more-gates.rs | 37 ++++++++++++ .../proc-macro/proc-macro-gates.rs | 3 + .../run-pass-fulldeps/macro-quote-test.rs | 2 +- 6 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 src/test/compile-fail-fulldeps/proc-macro/auxiliary/more-gates.rs create mode 100644 src/test/compile-fail-fulldeps/proc-macro/more-gates.rs diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 584b9455a93ad..eda75c4c53c92 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -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; @@ -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; @@ -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)); @@ -591,6 +593,50 @@ impl<'a, 'b> MacroExpander<'a, 'b> { ); } + fn gate_proc_macro_expansion(&self, span: Span, expansion: &Option) { + 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, @@ -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 } } }; @@ -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; @@ -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, } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index f1229520c7743..93bc4f3f3e644 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -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), diff --git a/src/test/compile-fail-fulldeps/proc-macro/auxiliary/more-gates.rs b/src/test/compile-fail-fulldeps/proc-macro/auxiliary/more-gates.rs new file mode 100644 index 0000000000000..def12f8e4ec85 --- /dev/null +++ b/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 or the MIT license +// , 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() +} diff --git a/src/test/compile-fail-fulldeps/proc-macro/more-gates.rs b/src/test/compile-fail-fulldeps/proc-macro/more-gates.rs new file mode 100644 index 0000000000000..a799f79ef70f3 --- /dev/null +++ b/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 or the MIT license +// , 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() {} diff --git a/src/test/compile-fail-fulldeps/proc-macro/proc-macro-gates.rs b/src/test/compile-fail-fulldeps/proc-macro/proc-macro-gates.rs index fff433b90ce69..70b2b5fdd336d 100644 --- a/src/test/compile-fail-fulldeps/proc-macro/proc-macro-gates.rs +++ b/src/test/compile-fail-fulldeps/proc-macro/proc-macro-gates.rs @@ -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)] @@ -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 diff --git a/src/test/run-pass-fulldeps/macro-quote-test.rs b/src/test/run-pass-fulldeps/macro-quote-test.rs index 1f6a340c7e88b..2349fa68c65f0 100644 --- a/src/test/run-pass-fulldeps/macro-quote-test.rs +++ b/src/test/run-pass-fulldeps/macro-quote-test.rs @@ -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;