about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAustin Bonander <austin.bonander@gmail.com>2018-03-10 18:16:26 -0800
committerAustin Bonander <austin.bonander@gmail.com>2018-04-03 13:16:11 -0700
commit5d74990cebb82b9573ea6a9d509bb8e05fd6681e (patch)
tree6a2ebca0f580f6dcf95055054d3c16d3f3a9b9d0
parent5ee891cfeabc0872624104611cc0a359f46447cc (diff)
downloadrust-5d74990cebb82b9573ea6a9d509bb8e05fd6681e.tar.gz
rust-5d74990cebb82b9573ea6a9d509bb8e05fd6681e.zip
expand macro invocations in `extern {}` blocks
-rw-r--r--src/librustc/hir/lowering.rs1
-rw-r--r--src/librustc/hir/map/def_collector.rs4
-rw-r--r--src/librustc_passes/ast_validation.rs18
-rw-r--r--src/librustc_resolve/build_reduced_graph.rs6
-rw-r--r--src/librustc_resolve/lib.rs1
-rw-r--r--src/librustc_save_analysis/dump_visitor.rs1
-rw-r--r--src/librustc_save_analysis/lib.rs1
-rw-r--r--src/librustc_save_analysis/sig.rs1
-rw-r--r--src/libsyntax/ast.rs3
-rw-r--r--src/libsyntax/ext/base.rs28
-rw-r--r--src/libsyntax/ext/expand.rs58
-rw-r--r--src/libsyntax/ext/placeholders.rs11
-rw-r--r--src/libsyntax/feature_gate.rs11
-rw-r--r--src/libsyntax/fold.rs20
-rw-r--r--src/libsyntax/parse/parser.rs124
-rw-r--r--src/libsyntax/parse/token.rs2
-rw-r--r--src/libsyntax/print/pprust.rs9
-rw-r--r--src/libsyntax/visit.rs1
-rw-r--r--src/libsyntax_ext/deriving/custom.rs1
-rw-r--r--src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs6
-rw-r--r--src/test/compile-fail-fulldeps/proc-macro/auxiliary/test-macros.rs36
-rw-r--r--src/test/compile-fail-fulldeps/proc-macro/macros-in-extern.rs38
-rw-r--r--src/test/compile-fail/macros-in-extern.rs42
-rw-r--r--src/test/parse-fail/duplicate-visibility.rs2
-rw-r--r--src/test/parse-fail/extern-no-fn.rs4
-rw-r--r--src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs8
-rw-r--r--src/test/run-pass-fulldeps/proc-macro/auxiliary/test-macros.rs36
-rw-r--r--src/test/run-pass-fulldeps/proc-macro/macros-in-extern.rs35
-rw-r--r--src/test/run-pass/macros-in-extern.rs39
-rw-r--r--src/test/ui/feature-gate-macros_in_extern.rs35
-rw-r--r--src/test/ui/feature-gate-macros_in_extern.stderr27
31 files changed, 544 insertions, 65 deletions
diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs
index 536d682566a..bbc1b921188 100644
--- a/src/librustc/hir/lowering.rs
+++ b/src/librustc/hir/lowering.rs
@@ -2724,6 +2724,7 @@ impl<'a> LoweringContext<'a> {
                         hir::ForeignItemStatic(this.lower_ty(t, ImplTraitContext::Disallowed), m)
                     }
                     ForeignItemKind::Ty => hir::ForeignItemType,
+                    ForeignItemKind::Macro(_) => panic!("shouldn't exist here"),
                 },
                 vis: this.lower_visibility(&i.vis, None),
                 span: i.span,
diff --git a/src/librustc/hir/map/def_collector.rs b/src/librustc/hir/map/def_collector.rs
index 3619a7fb0c6..20f46cb348d 100644
--- a/src/librustc/hir/map/def_collector.rs
+++ b/src/librustc/hir/map/def_collector.rs
@@ -181,6 +181,10 @@ impl<'a> visit::Visitor<'a> for DefCollector<'a> {
     }
 
     fn visit_foreign_item(&mut self, foreign_item: &'a ForeignItem) {
+        if let ForeignItemKind::Macro(_) = foreign_item.node {
+            return self.visit_macro_invoc(foreign_item.id, false);
+        }
+
         let def = self.create_def(foreign_item.id,
                                   DefPathData::ValueNs(foreign_item.ident.name.as_str()),
                                   REGULAR_SPACE,
diff --git a/src/librustc_passes/ast_validation.rs b/src/librustc_passes/ast_validation.rs
index 37274d1fc44..b34decc1c69 100644
--- a/src/librustc_passes/ast_validation.rs
+++ b/src/librustc_passes/ast_validation.rs
@@ -381,7 +381,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
                         .span_label(span, "pattern not allowed in foreign function").emit();
                 });
             }
-            ForeignItemKind::Static(..) | ForeignItemKind::Ty => {}
+            ForeignItemKind::Static(..) | ForeignItemKind::Ty | ForeignItemKind::Macro(..) => {}
         }
 
         visit::walk_foreign_item(self, fi)
@@ -460,6 +460,14 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
         self.check_late_bound_lifetime_defs(&t.bound_generic_params);
         visit::walk_poly_trait_ref(self, t, m);
     }
+
+    fn visit_mac(&mut self, mac: &Spanned<Mac_>) {
+        // when a new macro kind is added but the author forgets to set it up for expansion
+        // because that's the only part that won't cause a compiler error
+        self.session.diagnostic()
+            .span_bug(mac.span, "macro invocation missed in expansion; did you forget to override \
+                                 the relevant `fold_*()` method in `PlaceholderExpander`?");
+    }
 }
 
 // Bans nested `impl Trait`, e.g. `impl Into<impl Debug>`.
@@ -522,6 +530,10 @@ impl<'a> Visitor<'a> for NestedImplTraitVisitor<'a> {
             }
         }
     }
+
+    fn visit_mac(&mut self, _mac: &Spanned<Mac_>) {
+        // covered in AstValidator
+    }
 }
 
 // Bans `impl Trait` in path projections like `<impl Iterator>::Item` or `Foo::Bar<impl Trait>`.
@@ -583,6 +595,10 @@ impl<'a> Visitor<'a> for ImplTraitProjectionVisitor<'a> {
             _ => visit::walk_ty(self, t),
         }
     }
+
+    fn visit_mac(&mut self, _mac: &Spanned<Mac_>) {
+        // covered in AstValidator
+    }
 }
 
 pub fn check_crate(session: &Session, krate: &Crate) {
diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs
index c192f349c20..397590012fd 100644
--- a/src/librustc_resolve/build_reduced_graph.rs
+++ b/src/librustc_resolve/build_reduced_graph.rs
@@ -456,6 +456,7 @@ impl<'a> Resolver<'a> {
             ForeignItemKind::Ty => {
                 (Def::TyForeign(self.definitions.local_def_id(item.id)), TypeNS)
             }
+            ForeignItemKind::Macro(_) => unreachable!(),
         };
         let parent = self.current_module;
         let vis = self.resolve_visibility(&item.vis);
@@ -816,6 +817,11 @@ impl<'a, 'b> Visitor<'a> for BuildReducedGraphVisitor<'a, 'b> {
     }
 
     fn visit_foreign_item(&mut self, foreign_item: &'a ForeignItem) {
+        if let ForeignItemKind::Macro(_) = foreign_item.node {
+            self.visit_invoc(foreign_item.id);
+            return;
+        }
+
         self.resolver.build_reduced_graph_for_foreign_item(foreign_item, self.expansion);
         visit::walk_foreign_item(self, foreign_item);
     }
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index 97dcf081f8c..74cfe576652 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -863,6 +863,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Resolver<'a> {
             }
             ForeignItemKind::Static(..) => NoTypeParameters,
             ForeignItemKind::Ty => NoTypeParameters,
+            ForeignItemKind::Macro(..) => NoTypeParameters,
         };
         self.with_type_parameter_rib(type_parameters, |this| {
             visit::walk_foreign_item(this, foreign_item);
diff --git a/src/librustc_save_analysis/dump_visitor.rs b/src/librustc_save_analysis/dump_visitor.rs
index 3d4d8571c6e..5e51797d720 100644
--- a/src/librustc_save_analysis/dump_visitor.rs
+++ b/src/librustc_save_analysis/dump_visitor.rs
@@ -1812,6 +1812,7 @@ impl<'l, 'tcx: 'l, 'll, O: DumpOutput + 'll> Visitor<'l> for DumpVisitor<'l, 'tc
                     self.dumper.dump_def(&access, var_data);
                 }
             }
+            ast::ForeignItemKind::Macro(..) => {}
         }
     }
 }
diff --git a/src/librustc_save_analysis/lib.rs b/src/librustc_save_analysis/lib.rs
index 95374775651..fb4cb2afe5a 100644
--- a/src/librustc_save_analysis/lib.rs
+++ b/src/librustc_save_analysis/lib.rs
@@ -182,6 +182,7 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
             }
             // FIXME(plietar): needs a new DefKind in rls-data
             ast::ForeignItemKind::Ty => None,
+            ast::ForeignItemKind::Macro(..) => None,
         }
     }
 
diff --git a/src/librustc_save_analysis/sig.rs b/src/librustc_save_analysis/sig.rs
index 0c890ce19d0..fd4d3e76386 100644
--- a/src/librustc_save_analysis/sig.rs
+++ b/src/librustc_save_analysis/sig.rs
@@ -822,6 +822,7 @@ impl Sig for ast::ForeignItem {
                     refs: vec![],
                 })
             }
+            ast::ForeignItemKind::Macro(..) => Err("macro"),
         }
     }
 }
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index c90b0aecfc0..b55a502d118 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -2195,6 +2195,8 @@ pub enum ForeignItemKind {
     Static(P<Ty>, bool),
     /// A foreign type
     Ty,
+    /// A macro invocation
+    Macro(Mac),
 }
 
 impl ForeignItemKind {
@@ -2203,6 +2205,7 @@ impl ForeignItemKind {
             ForeignItemKind::Fn(..) => "foreign function",
             ForeignItemKind::Static(..) => "foreign static item",
             ForeignItemKind::Ty => "foreign type",
+            ForeignItemKind::Macro(..) => "macro in foreign module",
         }
     }
 }
diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs
index d3157af984e..5a735be55c0 100644
--- a/src/libsyntax/ext/base.rs
+++ b/src/libsyntax/ext/base.rs
@@ -38,6 +38,7 @@ pub enum Annotatable {
     Item(P<ast::Item>),
     TraitItem(P<ast::TraitItem>),
     ImplItem(P<ast::ImplItem>),
+    ForeignItem(P<ast::ForeignItem>),
     Stmt(P<ast::Stmt>),
     Expr(P<ast::Expr>),
 }
@@ -48,6 +49,7 @@ impl HasAttrs for Annotatable {
             Annotatable::Item(ref item) => &item.attrs,
             Annotatable::TraitItem(ref trait_item) => &trait_item.attrs,
             Annotatable::ImplItem(ref impl_item) => &impl_item.attrs,
+            Annotatable::ForeignItem(ref foreign_item) => &foreign_item.attrs,
             Annotatable::Stmt(ref stmt) => stmt.attrs(),
             Annotatable::Expr(ref expr) => &expr.attrs,
         }
@@ -58,6 +60,8 @@ impl HasAttrs for Annotatable {
             Annotatable::Item(item) => Annotatable::Item(item.map_attrs(f)),
             Annotatable::TraitItem(trait_item) => Annotatable::TraitItem(trait_item.map_attrs(f)),
             Annotatable::ImplItem(impl_item) => Annotatable::ImplItem(impl_item.map_attrs(f)),
+            Annotatable::ForeignItem(foreign_item) =>
+                Annotatable::ForeignItem(foreign_item.map_attrs(f)),
             Annotatable::Stmt(stmt) => Annotatable::Stmt(stmt.map_attrs(f)),
             Annotatable::Expr(expr) => Annotatable::Expr(expr.map_attrs(f)),
         }
@@ -70,6 +74,7 @@ impl Annotatable {
             Annotatable::Item(ref item) => item.span,
             Annotatable::TraitItem(ref trait_item) => trait_item.span,
             Annotatable::ImplItem(ref impl_item) => impl_item.span,
+            Annotatable::ForeignItem(ref foreign_item) => foreign_item.span,
             Annotatable::Stmt(ref stmt) => stmt.span,
             Annotatable::Expr(ref expr) => expr.span,
         }
@@ -106,6 +111,13 @@ impl Annotatable {
         }
     }
 
+    pub fn expect_foreign_item(self) -> ast::ForeignItem {
+        match self {
+            Annotatable::ForeignItem(i) => i.into_inner(),
+            _ => panic!("expected foreign item")
+        }
+    }
+
     pub fn derive_allowed(&self) -> bool {
         match *self {
             Annotatable::Item(ref item) => match item.node {
@@ -317,6 +329,9 @@ pub trait MacResult {
         None
     }
 
+    /// Create zero or more items in an `extern {}` block
+    fn make_foreign_items(self: Box<Self>) -> Option<SmallVector<ast::ForeignItem>> { None }
+
     /// Create a pattern.
     fn make_pat(self: Box<Self>) -> Option<P<ast::Pat>> {
         None
@@ -365,6 +380,7 @@ make_MacEager! {
     items: SmallVector<P<ast::Item>>,
     impl_items: SmallVector<ast::ImplItem>,
     trait_items: SmallVector<ast::TraitItem>,
+    foreign_items: SmallVector<ast::ForeignItem>,
     stmts: SmallVector<ast::Stmt>,
     ty: P<ast::Ty>,
 }
@@ -386,6 +402,10 @@ impl MacResult for MacEager {
         self.trait_items
     }
 
+    fn make_foreign_items(self: Box<Self>) -> Option<SmallVector<ast::ForeignItem>> {
+        self.foreign_items
+    }
+
     fn make_stmts(self: Box<Self>) -> Option<SmallVector<ast::Stmt>> {
         match self.stmts.as_ref().map_or(0, |s| s.len()) {
             0 => make_stmts_default!(self),
@@ -502,6 +522,14 @@ impl MacResult for DummyResult {
         }
     }
 
+    fn make_foreign_items(self: Box<Self>) -> Option<SmallVector<ast::ForeignItem>> {
+        if self.expr_only {
+            None
+        } else {
+            Some(SmallVector::new())
+        }
+    }
+
     fn make_stmts(self: Box<DummyResult>) -> Option<SmallVector<ast::Stmt>> {
         Some(SmallVector::one(ast::Stmt {
             id: ast::DUMMY_NODE_ID,
diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs
index 864969c4075..105de13b976 100644
--- a/src/libsyntax/ext/expand.rs
+++ b/src/libsyntax/ext/expand.rs
@@ -133,6 +133,8 @@ expansions! {
         "trait item", .make_trait_items, lift .fold_trait_item, lift .visit_trait_item;
     ImplItems: SmallVector<ast::ImplItem> [SmallVector, ast::ImplItem],
         "impl item",  .make_impl_items,  lift .fold_impl_item,  lift .visit_impl_item;
+    ForeignItems: SmallVector<ast::ForeignItem> [SmallVector, ast::ForeignItem],
+        "foreign item", .make_foreign_items, lift .fold_foreign_item, lift .visit_foreign_item;
 }
 
 impl ExpansionKind {
@@ -149,6 +151,8 @@ impl ExpansionKind {
                 Expansion::ImplItems(items.map(Annotatable::expect_impl_item).collect()),
             ExpansionKind::TraitItems =>
                 Expansion::TraitItems(items.map(Annotatable::expect_trait_item).collect()),
+            ExpansionKind::ForeignItems =>
+                Expansion::ForeignItems(items.map(Annotatable::expect_foreign_item).collect()),
             _ => unreachable!(),
         }
     }
@@ -435,6 +439,11 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
             Annotatable::ImplItem(item) => {
                 Annotatable::ImplItem(item.map(|item| cfg.fold_impl_item(item).pop().unwrap()))
             }
+            Annotatable::ForeignItem(item) => {
+                Annotatable::ForeignItem(
+                    item.map(|item| cfg.fold_foreign_item(item).pop().unwrap())
+                )
+            }
             Annotatable::Stmt(stmt) => {
                 Annotatable::Stmt(stmt.map(|stmt| cfg.fold_stmt(stmt).pop().unwrap()))
             }
@@ -509,6 +518,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                     Annotatable::Item(item) => token::NtItem(item),
                     Annotatable::TraitItem(item) => token::NtTraitItem(item.into_inner()),
                     Annotatable::ImplItem(item) => token::NtImplItem(item.into_inner()),
+                    Annotatable::ForeignItem(item) => token::NtForeignItem(item.into_inner()),
                     Annotatable::Stmt(stmt) => token::NtStmt(stmt.into_inner()),
                     Annotatable::Expr(expr) => token::NtExpr(expr),
                 })).into();
@@ -793,6 +803,15 @@ impl<'a> Parser<'a> {
                 }
                 Expansion::ImplItems(items)
             }
+            ExpansionKind::ForeignItems => {
+                let mut items = SmallVector::new();
+                while self.token != token::Eof {
+                    if let Some(item) = self.parse_foreign_item()? {
+                        items.push(item);
+                    }
+                }
+                Expansion::ForeignItems(items)
+            }
             ExpansionKind::Stmts => {
                 let mut stmts = SmallVector::new();
                 while self.token != token::Eof &&
@@ -1166,6 +1185,44 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
         noop_fold_foreign_mod(self.cfg.configure_foreign_mod(foreign_mod), self)
     }
 
+    fn fold_foreign_item(&mut self,
+                         foreign_item: ast::ForeignItem) -> SmallVector<ast::ForeignItem> {
+        let (attr, traits, foreign_item) = self.classify_item(foreign_item);
+
+        let explain = if self.cx.ecfg.proc_macro_enabled() {
+            feature_gate::EXPLAIN_PROC_MACROS_IN_EXTERN
+        } else {
+            feature_gate::EXPLAIN_MACROS_IN_EXTERN
+        };
+
+        if attr.is_some() || !traits.is_empty()  {
+            if !self.cx.ecfg.macros_in_extern_enabled() {
+                if let Some(ref attr) = attr {
+                    emit_feature_err(&self.cx.parse_sess, "macros_in_extern", attr.span,
+                                     GateIssue::Language, explain);
+                }
+            }
+
+            let item = Annotatable::ForeignItem(P(foreign_item));
+            return self.collect_attr(attr, traits, item, ExpansionKind::ForeignItems)
+                .make_foreign_items();
+        }
+
+        if let ast::ForeignItemKind::Macro(mac) = foreign_item.node {
+            self.check_attributes(&foreign_item.attrs);
+
+            if !self.cx.ecfg.macros_in_extern_enabled() {
+                emit_feature_err(&self.cx.parse_sess, "macros_in_extern", foreign_item.span,
+                                 GateIssue::Language, explain);
+            }
+
+            return self.collect_bang(mac, foreign_item.span, ExpansionKind::ForeignItems)
+                .make_foreign_items();
+        }
+
+        noop_fold_foreign_item(foreign_item, self)
+    }
+
     fn fold_item_kind(&mut self, item: ast::ItemKind) -> ast::ItemKind {
         match item {
             ast::ItemKind::MacroDef(..) => item,
@@ -1311,6 +1368,7 @@ impl<'feat> ExpansionConfig<'feat> {
         fn enable_allow_internal_unstable = allow_internal_unstable,
         fn enable_custom_derive = custom_derive,
         fn proc_macro_enabled = proc_macro,
+        fn macros_in_extern_enabled = macros_in_extern,
     }
 }
 
diff --git a/src/libsyntax/ext/placeholders.rs b/src/libsyntax/ext/placeholders.rs
index 9c2c22476e9..9f60882ca29 100644
--- a/src/libsyntax/ext/placeholders.rs
+++ b/src/libsyntax/ext/placeholders.rs
@@ -60,6 +60,10 @@ pub fn placeholder(kind: ExpansionKind, id: ast::NodeId) -> Expansion {
             defaultness: ast::Defaultness::Final,
             tokens: None,
         })),
+        ExpansionKind::ForeignItems => Expansion::ForeignItems(SmallVector::one(ast::ForeignItem {
+            id, span, ident, vis, attrs,
+            node: ast::ForeignItemKind::Macro(mac_placeholder()),
+        })),
         ExpansionKind::Pat => Expansion::Pat(P(ast::Pat {
             id, span, node: ast::PatKind::Mac(mac_placeholder()),
         })),
@@ -132,6 +136,13 @@ impl<'a, 'b> Folder for PlaceholderExpander<'a, 'b> {
         }
     }
 
+    fn fold_foreign_item(&mut self, item: ast::ForeignItem) -> SmallVector<ast::ForeignItem> {
+        match item.node {
+            ast::ForeignItemKind::Macro(_) => self.remove(item.id).make_foreign_items(),
+            _ => noop_fold_foreign_item(item, self),
+        }
+    }
+
     fn fold_expr(&mut self, expr: P<ast::Expr>) -> P<ast::Expr> {
         match expr.node {
             ast::ExprKind::Mac(_) => self.remove(expr.id).make_expr(),
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index 463e76e1461..d88305529ed 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -447,6 +447,9 @@ declare_features! (
 
     // Allows keywords to be escaped for use as identifiers
     (active, raw_identifiers, "1.26.0", Some(48589), None),
+
+    // Allows macro invocations in `extern {}` blocks
+    (active, macros_in_extern, "1.27.0", Some(49476), None),
 );
 
 declare_features! (
@@ -1296,6 +1299,13 @@ pub const EXPLAIN_UNSIZED_TUPLE_COERCION: &'static str =
 pub const EXPLAIN_MACRO_AT_MOST_ONCE_REP: &'static str =
     "Using the `?` macro Kleene operator for \"at most one\" repetition is unstable";
 
+pub const EXPLAIN_MACROS_IN_EXTERN: &'static str =
+    "Macro invocations in `extern {}` blocks are experimental.";
+
+// mention proc-macros when enabled
+pub const EXPLAIN_PROC_MACROS_IN_EXTERN: &'static str =
+    "Macro and proc-macro invocations in `extern {}` blocks are experimental.";
+
 struct PostExpansionVisitor<'a> {
     context: &'a Context<'a>,
 }
@@ -1600,6 +1610,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
                     gate_feature_post!(&self, extern_types, i.span,
                                        "extern types are experimental");
             }
+            ast::ForeignItemKind::Macro(..) => {}
         }
 
         visit::walk_foreign_item(self, i)
diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs
index 05a3150c139..72f695b4fdb 100644
--- a/src/libsyntax/fold.rs
+++ b/src/libsyntax/fold.rs
@@ -60,10 +60,14 @@ pub trait Folder : Sized {
         noop_fold_use_tree(use_tree, self)
     }
 
-    fn fold_foreign_item(&mut self, ni: ForeignItem) -> ForeignItem {
+    fn fold_foreign_item(&mut self, ni: ForeignItem) -> SmallVector<ForeignItem> {
         noop_fold_foreign_item(ni, self)
     }
 
+    fn fold_foreign_item_simple(&mut self, ni: ForeignItem) -> ForeignItem {
+        noop_fold_foreign_item_simple(ni, self)
+    }
+
     fn fold_item(&mut self, i: P<Item>) -> SmallVector<P<Item>> {
         noop_fold_item(i, self)
     }
@@ -414,7 +418,7 @@ pub fn noop_fold_foreign_mod<T: Folder>(ForeignMod {abi, items}: ForeignMod,
                                         fld: &mut T) -> ForeignMod {
     ForeignMod {
         abi,
-        items: items.move_map(|x| fld.fold_foreign_item(x)),
+        items: items.move_flat_map(|x| fld.fold_foreign_item(x)),
     }
 }
 
@@ -648,6 +652,10 @@ pub fn noop_fold_interpolated<T: Folder>(nt: token::Nonterminal, fld: &mut T)
         token::NtArg(arg) => token::NtArg(fld.fold_arg(arg)),
         token::NtVis(vis) => token::NtVis(fld.fold_vis(vis)),
         token::NtLifetime(lifetime) => token::NtLifetime(fld.fold_lifetime(lifetime)),
+        token::NtForeignItem(ni) =>
+            token::NtForeignItem(fld.fold_foreign_item(ni)
+                                 // see reasoning above
+                                 .expect_one("expected fold to produce exactly one item")),
     }
 }
 
@@ -1072,7 +1080,12 @@ pub fn noop_fold_item_simple<T: Folder>(Item {id, ident, attrs, node, vis, span,
     }
 }
 
-pub fn noop_fold_foreign_item<T: Folder>(ni: ForeignItem, folder: &mut T) -> ForeignItem {
+pub fn noop_fold_foreign_item<T: Folder>(ni: ForeignItem, folder: &mut T)
+-> SmallVector<ForeignItem> {
+    SmallVector::one(folder.fold_foreign_item_simple(ni))
+}
+
+pub fn noop_fold_foreign_item_simple<T: Folder>(ni: ForeignItem, folder: &mut T) -> ForeignItem {
     ForeignItem {
         id: folder.new_id(ni.id),
         vis: folder.fold_vis(ni.vis),
@@ -1086,6 +1099,7 @@ pub fn noop_fold_foreign_item<T: Folder>(ni: ForeignItem, folder: &mut T) -> For
                 ForeignItemKind::Static(folder.fold_ty(t), m)
             }
             ForeignItemKind::Ty => ForeignItemKind::Ty,
+            ForeignItemKind::Macro(mac) => ForeignItemKind::Macro(folder.fold_mac(mac)),
         },
         span: folder.new_span(ni.span)
     }
diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs
index f7cdd4ba2b4..6e44fe7344f 100644
--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -26,7 +26,7 @@ use ast::{Ident, ImplItem, IsAuto, Item, ItemKind};
 use ast::{Label, Lifetime, LifetimeDef, Lit, LitKind, UintTy};
 use ast::Local;
 use ast::MacStmtStyle;
-use ast::Mac_;
+use ast::{Mac, Mac_};
 use ast::{MutTy, Mutability};
 use ast::{Pat, PatKind, PathSegment};
 use ast::{PolyTraitRef, QSelf};
@@ -1417,28 +1417,8 @@ impl<'a> Parser<'a> {
                 None
             };
             (ident, TraitItemKind::Const(ty, default), ast::Generics::default())
-        } else if self.token.is_path_start() && !self.is_extern_non_path() {
+        } else if let Some(mac) = self.parse_assoc_macro_invoc("trait", None, &mut false)? {
             // trait item macro.
-            // code copied from parse_macro_use_or_failure... abstraction!
-            let prev_span = self.prev_span;
-            let lo = self.span;
-            let pth = self.parse_path(PathStyle::Mod)?;
-
-            if pth.segments.len() == 1 {
-                if !self.eat(&token::Not) {
-                    return Err(self.missing_assoc_item_kind_err("trait", prev_span));
-                }
-            } else {
-                self.expect(&token::Not)?;
-            }
-
-            // eat a matched-delimiter token tree:
-            let (delim, tts) = self.expect_delimited_token_tree()?;
-            if delim != token::Brace {
-                self.expect(&token::Semi)?
-            }
-
-            let mac = respan(lo.to(self.prev_span), Mac_ { path: pth, tts: tts });
             (keywords::Invalid.ident(), ast::TraitItemKind::Macro(mac), ast::Generics::default())
         } else {
             let (constness, unsafety, abi) = self.parse_fn_front_matter()?;
@@ -5406,6 +5386,12 @@ impl<'a> Parser<'a> {
     fn missing_assoc_item_kind_err(&mut self, item_type: &str, prev_span: Span)
                                    -> DiagnosticBuilder<'a>
     {
+        let expected_kinds = if item_type == "extern" {
+            "missing `fn`, `type`, or `static`"
+        } else {
+            "missing `fn`, `type`, or `const`"
+        };
+
         // Given this code `path(`, it seems like this is not
         // setting the visibility of a macro invocation, but rather
         // a mistyped method declaration.
@@ -5418,9 +5404,9 @@ impl<'a> Parser<'a> {
         let sp = prev_span.between(self.prev_span);
         let mut err = self.diagnostic().struct_span_err(
             sp,
-            &format!("missing `fn`, `type`, or `const` for {}-item declaration",
-                     item_type));
-        err.span_label(sp, "missing `fn`, `type`, or `const`");
+            &format!("{} for {}-item declaration",
+                     expected_kinds, item_type));
+        err.span_label(sp, expected_kinds);
         err
     }
 
@@ -5429,31 +5415,8 @@ impl<'a> Parser<'a> {
                          -> PResult<'a, (Ident, Vec<Attribute>, ast::Generics,
                              ast::ImplItemKind)> {
         // code copied from parse_macro_use_or_failure... abstraction!
-        if self.token.is_path_start() && !self.is_extern_non_path() {
+        if let Some(mac) = self.parse_assoc_macro_invoc("impl", Some(vis), at_end)? {
             // Method macro.
-
-            let prev_span = self.prev_span;
-
-            let lo = self.span;
-            let pth = self.parse_path(PathStyle::Mod)?;
-            if pth.segments.len() == 1 {
-                if !self.eat(&token::Not) {
-                    return Err(self.missing_assoc_item_kind_err("impl", prev_span));
-                }
-            } else {
-                self.expect(&token::Not)?;
-            }
-
-            self.complain_if_pub_macro(&vis.node, prev_span);
-
-            // eat a matched-delimiter token tree:
-            *at_end = true;
-            let (delim, tts) = self.expect_delimited_token_tree()?;
-            if delim != token::Brace {
-                self.expect(&token::Semi)?
-            }
-
-            let mac = respan(lo.to(self.prev_span), Mac_ { path: pth, tts: tts });
             Ok((keywords::Invalid.ident(), vec![], ast::Generics::default(),
                 ast::ImplItemKind::Macro(mac)))
         } else {
@@ -6799,7 +6762,9 @@ impl<'a> Parser<'a> {
     }
 
     /// Parse a foreign item.
-    fn parse_foreign_item(&mut self) -> PResult<'a, Option<ForeignItem>> {
+    pub fn parse_foreign_item(&mut self) -> PResult<'a, Option<ForeignItem>> {
+        maybe_whole!(self, NtForeignItem, |ni| Some(ni));
+
         let attrs = self.parse_outer_attributes()?;
         let lo = self.span;
         let visibility = self.parse_visibility(false)?;
@@ -6825,12 +6790,26 @@ impl<'a> Parser<'a> {
             return Ok(Some(self.parse_item_foreign_type(visibility, lo, attrs)?));
         }
 
-        // FIXME #5668: this will occur for a macro invocation:
-        match self.parse_macro_use_or_failure(attrs, true, false, lo, visibility)? {
-            Some(item) => {
-                return Err(self.span_fatal(item.span, "macros cannot expand to foreign items"));
+        match self.parse_assoc_macro_invoc("extern", Some(&visibility), &mut false)? {
+            Some(mac) => {
+                Ok(Some(
+                    ForeignItem {
+                        ident: keywords::Invalid.ident(),
+                        span: lo.to(self.prev_span),
+                        id: ast::DUMMY_NODE_ID,
+                        attrs,
+                        vis: visibility,
+                        node: ForeignItemKind::Macro(mac),
+                    }
+                ))
+            }
+            None => {
+                if !attrs.is_empty() {
+                    self.expected_item_err(&attrs);
+                }
+
+                Ok(None)
             }
-            None => Ok(None)
         }
     }
 
@@ -6894,6 +6873,41 @@ impl<'a> Parser<'a> {
         Ok(None)
     }
 
+    /// Parse a macro invocation inside a `trait`, `impl` or `extern` block
+    fn parse_assoc_macro_invoc(&mut self, item_kind: &str, vis: Option<&Visibility>,
+                               at_end: &mut bool) -> PResult<'a, Option<Mac>>
+    {
+        if self.token.is_path_start() && !self.is_extern_non_path() {
+            let prev_span = self.prev_span;
+            let lo = self.span;
+            let pth = self.parse_path(PathStyle::Mod)?;
+
+            if pth.segments.len() == 1 {
+                if !self.eat(&token::Not) {
+                    return Err(self.missing_assoc_item_kind_err(item_kind, prev_span));
+                }
+            } else {
+                self.expect(&token::Not)?;
+            }
+
+            if let Some(vis) = vis {
+                self.complain_if_pub_macro(&vis.node, prev_span);
+            }
+
+            *at_end = true;
+
+            // eat a matched-delimiter token tree:
+            let (delim, tts) = self.expect_delimited_token_tree()?;
+            if delim != token::Brace {
+                self.expect(&token::Semi)?
+            }
+
+            Ok(Some(respan(lo.to(self.prev_span), Mac_ { path: pth, tts: tts })))
+        } else {
+            Ok(None)
+        }
+    }
+
     fn collect_tokens<F, R>(&mut self, f: F) -> PResult<'a, (R, TokenStream)>
         where F: FnOnce(&mut Self) -> PResult<'a, R>
     {
diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs
index e2dfca5d10a..5fe2b081566 100644
--- a/src/libsyntax/parse/token.rs
+++ b/src/libsyntax/parse/token.rs
@@ -581,6 +581,7 @@ pub enum Nonterminal {
     NtArm(ast::Arm),
     NtImplItem(ast::ImplItem),
     NtTraitItem(ast::TraitItem),
+    NtForeignItem(ast::ForeignItem),
     NtGenerics(ast::Generics),
     NtWhereClause(ast::WhereClause),
     NtArg(ast::Arg),
@@ -603,6 +604,7 @@ impl fmt::Debug for Nonterminal {
             NtArm(..) => f.pad("NtArm(..)"),
             NtImplItem(..) => f.pad("NtImplItem(..)"),
             NtTraitItem(..) => f.pad("NtTraitItem(..)"),
+            NtForeignItem(..) => f.pad("NtForeignItem(..)"),
             NtGenerics(..) => f.pad("NtGenerics(..)"),
             NtWhereClause(..) => f.pad("NtWhereClause(..)"),
             NtArg(..) => f.pad("NtArg(..)"),
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index ae045fc095a..fbcd251f108 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -281,6 +281,7 @@ pub fn token_to_string(tok: &Token) -> String {
             token::NtArg(ref e)          => arg_to_string(e),
             token::NtVis(ref e)          => vis_to_string(e),
             token::NtLifetime(ref e)     => lifetime_to_string(e),
+            token::NtForeignItem(ref ni) => foreign_item_to_string(ni),
         }
     }
 }
@@ -422,6 +423,10 @@ pub fn mac_to_string(arg: &ast::Mac) -> String {
     to_string(|s| s.print_mac(arg, ::parse::token::Paren))
 }
 
+pub fn foreign_item_to_string(arg: &ast::ForeignItem) -> String {
+    to_string(|s| s.print_foreign_item(arg))
+}
+
 pub fn visibility_qualified(vis: &ast::Visibility, s: &str) -> String {
     format!("{}{}", to_string(|s| s.print_visibility(vis)), s)
 }
@@ -1127,6 +1132,10 @@ impl<'a> State<'a> {
                 self.end()?; // end the head-ibox
                 self.end() // end the outer cbox
             }
+            ast::ForeignItemKind::Macro(ref m) => {
+                self.print_mac(m, token::Paren)?;
+                self.s.word(";")
+            }
         }
     }
 
diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs
index bbf1fe124f1..9004ad022f7 100644
--- a/src/libsyntax/visit.rs
+++ b/src/libsyntax/visit.rs
@@ -460,6 +460,7 @@ pub fn walk_foreign_item<'a, V: Visitor<'a>>(visitor: &mut V, foreign_item: &'a
         }
         ForeignItemKind::Static(ref typ, _) => visitor.visit_ty(typ),
         ForeignItemKind::Ty => (),
+        ForeignItemKind::Macro(ref mac) => visitor.visit_mac(mac),
     }
 
     walk_list!(visitor, visit_attribute, &foreign_item.attrs);
diff --git a/src/libsyntax_ext/deriving/custom.rs b/src/libsyntax_ext/deriving/custom.rs
index 80557078d54..5fd5e299488 100644
--- a/src/libsyntax_ext/deriving/custom.rs
+++ b/src/libsyntax_ext/deriving/custom.rs
@@ -55,6 +55,7 @@ impl MultiItemModifier for ProcMacroDerive {
             Annotatable::Item(item) => item,
             Annotatable::ImplItem(_) |
             Annotatable::TraitItem(_) |
+            Annotatable::ForeignItem(_) |
             Annotatable::Stmt(_) |
             Annotatable::Expr(_) => {
                 ecx.span_err(span, "proc-macro derives may only be \
diff --git a/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs b/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs
index 77ea3019419..bc51b4061ed 100644
--- a/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs
+++ b/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs
@@ -93,7 +93,9 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt,
                 }
             })
         }
-        // these are covered in proc_macro/attr-stmt-expr.rs
+        // covered in proc_macro/macros-in-extern.rs
+        Annotatable::ForeignItem(_) => unimplemented!(),
+        // covered in proc_macro/attr-stmt-expr.rs
         Annotatable::Stmt(_) | Annotatable::Expr(_) => panic!("expected item")
     }
 }
@@ -147,6 +149,8 @@ fn expand_duplicate(cx: &mut ExtCtxt,
             new_it.ident = copy_name;
             push(Annotatable::TraitItem(P(new_it)));
         }
+        // covered in proc_macro/macros-in-extern.rs
+        Annotatable::ForeignItem(_) => unimplemented!(),
         // covered in proc_macro/attr-stmt-expr.rs
         Annotatable::Stmt(_) | Annotatable::Expr(_) => panic!("expected item")
     }
diff --git a/src/test/compile-fail-fulldeps/proc-macro/auxiliary/test-macros.rs b/src/test/compile-fail-fulldeps/proc-macro/auxiliary/test-macros.rs
new file mode 100644
index 00000000000..d1c5b9050aa
--- /dev/null
+++ b/src/test/compile-fail-fulldeps/proc-macro/auxiliary/test-macros.rs
@@ -0,0 +1,36 @@
+// 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::TokenStream;
+
+#[proc_macro_attribute]
+pub fn nop_attr(_attr: TokenStream, input: TokenStream) -> TokenStream {
+    assert!(_attr.to_string().is_empty());
+    input
+}
+
+#[proc_macro_attribute]
+pub fn no_output(_attr: TokenStream, _input: TokenStream) -> TokenStream {
+    assert!(_attr.to_string().is_empty());
+    assert!(!_input.to_string().is_empty());
+    "".parse().unwrap()
+}
+
+#[proc_macro]
+pub fn emit_input(input: TokenStream) -> TokenStream {
+    input
+}
diff --git a/src/test/compile-fail-fulldeps/proc-macro/macros-in-extern.rs b/src/test/compile-fail-fulldeps/proc-macro/macros-in-extern.rs
new file mode 100644
index 00000000000..4c88df33246
--- /dev/null
+++ b/src/test/compile-fail-fulldeps/proc-macro/macros-in-extern.rs
@@ -0,0 +1,38 @@
+// 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:test-macros.rs
+// ignore-stage1
+// ignore-wasm32
+
+#![feature(proc_macro)]
+
+extern crate test_macros;
+
+use test_macros::{nop_attr, no_output, emit_input};
+
+fn main() {
+    assert_eq!(unsafe { rust_get_test_int() }, 0isize);
+    assert_eq!(unsafe { rust_dbg_extern_identity_u32(0xDEADBEEF) }, 0xDEADBEEF);
+}
+
+#[link(name = "rust_test_helpers", kind = "static")]
+extern {
+    #[no_output]
+    //~^ ERROR Macro and proc-macro invocations in `extern {}` blocks are experimental.
+    fn some_definitely_unknown_symbol_which_should_be_removed();
+
+    #[nop_attr]
+    //~^ ERROR Macro and proc-macro invocations in `extern {}` blocks are experimental.
+    fn rust_get_test_int() -> isize;
+
+    emit_input!(fn rust_dbg_extern_identity_u32(arg: u32) -> u32;);
+    //~^ ERROR Macro and proc-macro invocations in `extern {}` blocks are experimental.
+}
diff --git a/src/test/compile-fail/macros-in-extern.rs b/src/test/compile-fail/macros-in-extern.rs
new file mode 100644
index 00000000000..7d7f95cbbf5
--- /dev/null
+++ b/src/test/compile-fail/macros-in-extern.rs
@@ -0,0 +1,42 @@
+// 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.
+
+// ignore-wasm32
+
+#![feature(decl_macro)]
+
+macro_rules! returns_isize(
+    ($ident:ident) => (
+        fn $ident() -> isize;
+    )
+);
+
+macro takes_u32_returns_u32($ident:ident) {
+    fn $ident (arg: u32) -> u32;
+}
+
+macro_rules! emits_nothing(
+    () => ()
+);
+
+fn main() {
+    assert_eq!(unsafe { rust_get_test_int() }, 0isize);
+    assert_eq!(unsafe { rust_dbg_extern_identity_u32(0xDEADBEEF) }, 0xDEADBEEFu32);
+}
+
+#[link(name = "rust_test_helpers", kind = "static")]
+extern {
+    returns_isize!(rust_get_test_int);
+    //~^ ERROR Macro invocations in `extern {}` blocks are experimental.
+    takes_u32_returns_u32!(rust_dbg_extern_identity_u32);
+    //~^ ERROR Macro invocations in `extern {}` blocks are experimental.
+    emits_nothing!();
+    //~^ ERROR Macro invocations in `extern {}` blocks are experimental.
+}
diff --git a/src/test/parse-fail/duplicate-visibility.rs b/src/test/parse-fail/duplicate-visibility.rs
index 5ee84cd5543..6899caa7153 100644
--- a/src/test/parse-fail/duplicate-visibility.rs
+++ b/src/test/parse-fail/duplicate-visibility.rs
@@ -10,7 +10,7 @@
 
 // compile-flags: -Z parse-only
 
-// error-pattern:unmatched visibility `pub`
+// error-pattern:expected one of `(`, `fn`, `static`, `type`, or `}` here
 extern {
     pub pub fn foo();
 }
diff --git a/src/test/parse-fail/extern-no-fn.rs b/src/test/parse-fail/extern-no-fn.rs
index ff3fefde40e..aa0dbd4d4fc 100644
--- a/src/test/parse-fail/extern-no-fn.rs
+++ b/src/test/parse-fail/extern-no-fn.rs
@@ -10,8 +10,8 @@
 
 // compile-flags: -Z parse-only
 
-extern {
-    f(); //~ ERROR expected one of `!` or `::`, found `(`
+extern { //~ ERROR missing `fn`, `type`, or `static` for extern-item declaration
+    f();
 }
 
 fn main() {
diff --git a/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs b/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs
index 5ebd3292132..6612fe45b81 100644
--- a/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs
+++ b/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs
@@ -96,7 +96,9 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt,
                 }
             })
         ],
-        // these are covered in proc_macro/attr-stmt-expr.rs
+        // covered in proc_macro/macros-in-extern.rs
+        Annotatable::ForeignItem(..) => unimplemented!(),
+        // covered in proc_macro/attr-stmt-expr.rs
         Annotatable::Stmt(_) | Annotatable::Expr(_) => panic!("expected item"),
     }
 }
@@ -142,7 +144,9 @@ fn expand_duplicate(cx: &mut ExtCtxt,
             new_it.ident = copy_name;
             push(Annotatable::TraitItem(P(new_it)));
         }
-        // these are covered in proc_macro/attr-stmt-expr.rs
+        // covered in proc_macro/macros-in-extern.rs
+        Annotatable::ForeignItem(..) => unimplemented!(),
+        // covered in proc_macro/attr-stmt-expr.rs
         Annotatable::Stmt(_) | Annotatable::Expr(_) => panic!("expected item")
     }
 }
diff --git a/src/test/run-pass-fulldeps/proc-macro/auxiliary/test-macros.rs b/src/test/run-pass-fulldeps/proc-macro/auxiliary/test-macros.rs
new file mode 100644
index 00000000000..d1c5b9050aa
--- /dev/null
+++ b/src/test/run-pass-fulldeps/proc-macro/auxiliary/test-macros.rs
@@ -0,0 +1,36 @@
+// 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::TokenStream;
+
+#[proc_macro_attribute]
+pub fn nop_attr(_attr: TokenStream, input: TokenStream) -> TokenStream {
+    assert!(_attr.to_string().is_empty());
+    input
+}
+
+#[proc_macro_attribute]
+pub fn no_output(_attr: TokenStream, _input: TokenStream) -> TokenStream {
+    assert!(_attr.to_string().is_empty());
+    assert!(!_input.to_string().is_empty());
+    "".parse().unwrap()
+}
+
+#[proc_macro]
+pub fn emit_input(input: TokenStream) -> TokenStream {
+    input
+}
diff --git a/src/test/run-pass-fulldeps/proc-macro/macros-in-extern.rs b/src/test/run-pass-fulldeps/proc-macro/macros-in-extern.rs
new file mode 100644
index 00000000000..59b9b0baa8a
--- /dev/null
+++ b/src/test/run-pass-fulldeps/proc-macro/macros-in-extern.rs
@@ -0,0 +1,35 @@
+// 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:test-macros.rs
+// ignore-stage1
+// ignore-wasm32
+
+#![feature(proc_macro, macros_in_extern)]
+
+extern crate test_macros;
+
+use test_macros::{nop_attr, no_output, emit_input};
+
+fn main() {
+    assert_eq!(unsafe { rust_get_test_int() }, 1isize);
+    assert_eq!(unsafe { rust_dbg_extern_identity_u32(0xDEADBEEF) }, 0xDEADBEEF);
+}
+
+#[link(name = "rust_test_helpers", kind = "static")]
+extern {
+    #[no_output]
+    fn some_definitely_unknown_symbol_which_should_be_removed();
+
+    #[nop_attr]
+    fn rust_get_test_int() -> isize;
+
+    emit_input!(fn rust_dbg_extern_identity_u32(arg: u32) -> u32;);
+}
diff --git a/src/test/run-pass/macros-in-extern.rs b/src/test/run-pass/macros-in-extern.rs
new file mode 100644
index 00000000000..d9094934356
--- /dev/null
+++ b/src/test/run-pass/macros-in-extern.rs
@@ -0,0 +1,39 @@
+// 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.
+
+// ignore-wasm32
+
+#![feature(decl_macro, macros_in_extern)]
+
+macro_rules! returns_isize(
+    ($ident:ident) => (
+        fn $ident() -> isize;
+    )
+);
+
+macro takes_u32_returns_u32($ident:ident) {
+    fn $ident (arg: u32) -> u32;
+}
+
+macro_rules! emits_nothing(
+    () => ()
+);
+
+fn main() {
+    assert_eq!(unsafe { rust_get_test_int() }, 1isize);
+    assert_eq!(unsafe { rust_dbg_extern_identity_u32(0xDEADBEEF) }, 0xDEADBEEFu32);
+}
+
+#[link(name = "rust_test_helpers", kind = "static")]
+extern {
+    returns_isize!(rust_get_test_int);
+    takes_u32_returns_u32!(rust_dbg_extern_identity_u32);
+    emits_nothing!();
+}
diff --git a/src/test/ui/feature-gate-macros_in_extern.rs b/src/test/ui/feature-gate-macros_in_extern.rs
new file mode 100644
index 00000000000..9c758241ea1
--- /dev/null
+++ b/src/test/ui/feature-gate-macros_in_extern.rs
@@ -0,0 +1,35 @@
+// 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.
+
+#![feature(decl_macro)]
+
+macro_rules! returns_isize(
+    ($ident:ident) => (
+        fn $ident() -> isize;
+    )
+);
+
+macro takes_u32_returns_u32($ident:ident) {
+    fn $ident (arg: u32) -> u32;
+}
+
+macro_rules! emits_nothing(
+    () => ()
+);
+
+#[link(name = "rust_test_helpers", kind = "static")]
+extern {
+    returns_isize!(rust_get_test_int);
+    //~^ ERROR Macro invocations in `extern {}` blocks are experimental.
+    takes_u32_returns_u32!(rust_dbg_extern_identity_u32);
+    //~^ ERROR Macro invocations in `extern {}` blocks are experimental.
+    emits_nothing!();
+    //~^ ERROR Macro invocations in `extern {}` blocks are experimental.
+}
diff --git a/src/test/ui/feature-gate-macros_in_extern.stderr b/src/test/ui/feature-gate-macros_in_extern.stderr
new file mode 100644
index 00000000000..49aca0db2d4
--- /dev/null
+++ b/src/test/ui/feature-gate-macros_in_extern.stderr
@@ -0,0 +1,27 @@
+error[E0658]: Macro invocations in `extern {}` blocks are experimental. (see issue #49476)
+  --> $DIR/feature-gate-macros_in_extern.rs:29:5
+   |
+LL |     returns_isize!(rust_get_test_int);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: add #![feature(macros_in_extern)] to the crate attributes to enable
+
+error[E0658]: Macro invocations in `extern {}` blocks are experimental. (see issue #49476)
+  --> $DIR/feature-gate-macros_in_extern.rs:31:5
+   |
+LL |     takes_u32_returns_u32!(rust_dbg_extern_identity_u32);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: add #![feature(macros_in_extern)] to the crate attributes to enable
+
+error[E0658]: Macro invocations in `extern {}` blocks are experimental. (see issue #49476)
+  --> $DIR/feature-gate-macros_in_extern.rs:33:5
+   |
+LL |     emits_nothing!();
+   |     ^^^^^^^^^^^^^^^^^
+   |
+   = help: add #![feature(macros_in_extern)] to the crate attributes to enable
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0658`.