about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMazdak Farrokhzad <twingoow@gmail.com>2020-02-14 14:21:02 +0100
committerMazdak Farrokhzad <twingoow@gmail.com>2020-02-15 20:57:12 +0100
commitf3e9763543e5828fe9eee7f5e78c88193f5b8ba5 (patch)
tree4d61cb1d454b4951c452ce9008832147992e57d7 /src
parent95dc9b9a73353a786e3c934c5074fb793ff7a735 (diff)
downloadrust-f3e9763543e5828fe9eee7f5e78c88193f5b8ba5.tar.gz
rust-f3e9763543e5828fe9eee7f5e78c88193f5b8ba5.zip
ast: make `= <expr>;` optional in free statics/consts.
Diffstat (limited to 'src')
-rw-r--r--src/librustc_ast_lowering/item.rs37
-rw-r--r--src/librustc_ast_passes/ast_validation.rs8
-rw-r--r--src/librustc_ast_pretty/pprust.rs14
-rw-r--r--src/librustc_builtin_macros/test.rs148
-rw-r--r--src/librustc_expand/build.rs4
-rw-r--r--src/librustc_lint/unused.rs2
-rw-r--r--src/librustc_parse/parser/item.rs19
-rw-r--r--src/librustc_resolve/late.rs6
-rw-r--r--src/librustc_save_analysis/dump_visitor.rs8
-rw-r--r--src/librustc_save_analysis/sig.rs18
-rw-r--r--src/libsyntax/ast.rs4
-rw-r--r--src/libsyntax/mut_visit.rs8
-rw-r--r--src/libsyntax/visit.rs2
-rw-r--r--src/test/ui/parser/item-free-const-no-body-semantic-fail.rs7
-rw-r--r--src/test/ui/parser/item-free-const-no-body-semantic-fail.stderr24
-rw-r--r--src/test/ui/parser/item-free-const-no-body-syntactic-pass.rs8
-rw-r--r--src/test/ui/parser/item-free-static-no-body-semantic-fail.rs11
-rw-r--r--src/test/ui/parser/item-free-static-no-body-semantic-fail.stderr46
-rw-r--r--src/test/ui/parser/item-free-static-no-body-syntactic-pass.rs8
19 files changed, 250 insertions, 132 deletions
diff --git a/src/librustc_ast_lowering/item.rs b/src/librustc_ast_lowering/item.rs
index 6c4026408ed..f19481d890d 100644
--- a/src/librustc_ast_lowering/item.rs
+++ b/src/librustc_ast_lowering/item.rs
@@ -269,26 +269,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
                 self.lower_use_tree(use_tree, &prefix, id, vis, ident, attrs)
             }
             ItemKind::Static(ref t, m, ref e) => {
-                let ty = self.lower_ty(
-                    t,
-                    if self.sess.features_untracked().impl_trait_in_bindings {
-                        ImplTraitContext::OpaqueTy(None, hir::OpaqueTyOrigin::Misc)
-                    } else {
-                        ImplTraitContext::Disallowed(ImplTraitPosition::Binding)
-                    },
-                );
-                hir::ItemKind::Static(ty, m, self.lower_const_body(span, Some(e)))
+                let (ty, body_id) = self.lower_const_item(t, span, e.as_deref());
+                hir::ItemKind::Static(ty, m, body_id)
             }
             ItemKind::Const(ref t, ref e) => {
-                let ty = self.lower_ty(
-                    t,
-                    if self.sess.features_untracked().impl_trait_in_bindings {
-                        ImplTraitContext::OpaqueTy(None, hir::OpaqueTyOrigin::Misc)
-                    } else {
-                        ImplTraitContext::Disallowed(ImplTraitPosition::Binding)
-                    },
-                );
-                hir::ItemKind::Const(ty, self.lower_const_body(span, Some(e)))
+                let (ty, body_id) = self.lower_const_item(t, span, e.as_deref());
+                hir::ItemKind::Const(ty, body_id)
             }
             ItemKind::Fn(FnSig { ref decl, header }, ref generics, ref body) => {
                 let fn_def_id = self.resolver.definitions().local_def_id(id);
@@ -457,6 +443,21 @@ impl<'hir> LoweringContext<'_, 'hir> {
         //     not cause an assertion failure inside the `lower_defaultness` function.
     }
 
+    fn lower_const_item(
+        &mut self,
+        ty: &Ty,
+        span: Span,
+        body: Option<&Expr>,
+    ) -> (&'hir hir::Ty<'hir>, hir::BodyId) {
+        let itctx = if self.sess.features_untracked().impl_trait_in_bindings {
+            ImplTraitContext::OpaqueTy(None, hir::OpaqueTyOrigin::Misc)
+        } else {
+            ImplTraitContext::Disallowed(ImplTraitPosition::Binding)
+        };
+        let ty = self.lower_ty(ty, itctx);
+        (ty, self.lower_const_body(span, body))
+    }
+
     fn lower_use_tree(
         &mut self,
         tree: &UseTree,
diff --git a/src/librustc_ast_passes/ast_validation.rs b/src/librustc_ast_passes/ast_validation.rs
index c539f98aecf..fdbf866cd9a 100644
--- a/src/librustc_ast_passes/ast_validation.rs
+++ b/src/librustc_ast_passes/ast_validation.rs
@@ -948,6 +948,14 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
                     self.err_handler().span_err(item.span, "unions cannot have zero fields");
                 }
             }
+            ItemKind::Const(.., None) => {
+                let msg = "free constant item without body";
+                self.error_item_without_body(item.span, "constant", msg, " = <expr>;");
+            }
+            ItemKind::Static(.., None) => {
+                let msg = "free static item without body";
+                self.error_item_without_body(item.span, "static", msg, " = <expr>;");
+            }
             _ => {}
         }
 
diff --git a/src/librustc_ast_pretty/pprust.rs b/src/librustc_ast_pretty/pprust.rs
index b0e1b5d4f42..356fdd1e71c 100644
--- a/src/librustc_ast_pretty/pprust.rs
+++ b/src/librustc_ast_pretty/pprust.rs
@@ -1124,9 +1124,10 @@ impl<'a> State<'a> {
                 self.print_type(ty);
                 self.s.space();
                 self.end(); // end the head-ibox
-
-                self.word_space("=");
-                self.print_expr(expr);
+                if let Some(expr) = expr {
+                    self.word_space("=");
+                    self.print_expr(expr);
+                }
                 self.s.word(";");
                 self.end(); // end the outer cbox
             }
@@ -1137,9 +1138,10 @@ impl<'a> State<'a> {
                 self.print_type(ty);
                 self.s.space();
                 self.end(); // end the head-ibox
-
-                self.word_space("=");
-                self.print_expr(expr);
+                if let Some(expr) = expr {
+                    self.word_space("=");
+                    self.print_expr(expr);
+                }
                 self.s.word(";");
                 self.end(); // end the outer cbox
             }
diff --git a/src/librustc_builtin_macros/test.rs b/src/librustc_builtin_macros/test.rs
index 02a0bc00c11..b6837c0703a 100644
--- a/src/librustc_builtin_macros/test.rs
+++ b/src/librustc_builtin_macros/test.rs
@@ -186,81 +186,85 @@ pub fn expand_test_or_bench(
         ast::ItemKind::Const(
             cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
             // test::TestDescAndFn {
-            cx.expr_struct(
-                sp,
-                test_path("TestDescAndFn"),
-                vec![
-                    // desc: test::TestDesc {
-                    field(
-                        "desc",
-                        cx.expr_struct(
-                            sp,
-                            test_path("TestDesc"),
-                            vec![
-                                // name: "path::to::test"
-                                field(
-                                    "name",
-                                    cx.expr_call(
-                                        sp,
-                                        cx.expr_path(test_path("StaticTestName")),
-                                        vec![cx.expr_str(
-                                            sp,
-                                            Symbol::intern(&item_path(
-                                                // skip the name of the root module
-                                                &cx.current_expansion.module.mod_path[1..],
-                                                &item.ident,
-                                            )),
-                                        )],
-                                    ),
-                                ),
-                                // ignore: true | false
-                                field("ignore", cx.expr_bool(sp, should_ignore(&item))),
-                                // allow_fail: true | false
-                                field("allow_fail", cx.expr_bool(sp, should_fail(&item))),
-                                // should_panic: ...
-                                field(
-                                    "should_panic",
-                                    match should_panic(cx, &item) {
-                                        // test::ShouldPanic::No
-                                        ShouldPanic::No => cx.expr_path(should_panic_path("No")),
-                                        // test::ShouldPanic::Yes
-                                        ShouldPanic::Yes(None) => {
-                                            cx.expr_path(should_panic_path("Yes"))
-                                        }
-                                        // test::ShouldPanic::YesWithMessage("...")
-                                        ShouldPanic::Yes(Some(sym)) => cx.expr_call(
+            Some(
+                cx.expr_struct(
+                    sp,
+                    test_path("TestDescAndFn"),
+                    vec![
+                        // desc: test::TestDesc {
+                        field(
+                            "desc",
+                            cx.expr_struct(
+                                sp,
+                                test_path("TestDesc"),
+                                vec![
+                                    // name: "path::to::test"
+                                    field(
+                                        "name",
+                                        cx.expr_call(
                                             sp,
-                                            cx.expr_path(should_panic_path("YesWithMessage")),
-                                            vec![cx.expr_str(sp, sym)],
+                                            cx.expr_path(test_path("StaticTestName")),
+                                            vec![cx.expr_str(
+                                                sp,
+                                                Symbol::intern(&item_path(
+                                                    // skip the name of the root module
+                                                    &cx.current_expansion.module.mod_path[1..],
+                                                    &item.ident,
+                                                )),
+                                            )],
                                         ),
-                                    },
-                                ),
-                                // test_type: ...
-                                field(
-                                    "test_type",
-                                    match test_type(cx) {
-                                        // test::TestType::UnitTest
-                                        TestType::UnitTest => {
-                                            cx.expr_path(test_type_path("UnitTest"))
-                                        }
-                                        // test::TestType::IntegrationTest
-                                        TestType::IntegrationTest => {
-                                            cx.expr_path(test_type_path("IntegrationTest"))
-                                        }
-                                        // test::TestPath::Unknown
-                                        TestType::Unknown => {
-                                            cx.expr_path(test_type_path("Unknown"))
-                                        }
-                                    },
-                                ),
-                                // },
-                            ],
+                                    ),
+                                    // ignore: true | false
+                                    field("ignore", cx.expr_bool(sp, should_ignore(&item))),
+                                    // allow_fail: true | false
+                                    field("allow_fail", cx.expr_bool(sp, should_fail(&item))),
+                                    // should_panic: ...
+                                    field(
+                                        "should_panic",
+                                        match should_panic(cx, &item) {
+                                            // test::ShouldPanic::No
+                                            ShouldPanic::No => {
+                                                cx.expr_path(should_panic_path("No"))
+                                            }
+                                            // test::ShouldPanic::Yes
+                                            ShouldPanic::Yes(None) => {
+                                                cx.expr_path(should_panic_path("Yes"))
+                                            }
+                                            // test::ShouldPanic::YesWithMessage("...")
+                                            ShouldPanic::Yes(Some(sym)) => cx.expr_call(
+                                                sp,
+                                                cx.expr_path(should_panic_path("YesWithMessage")),
+                                                vec![cx.expr_str(sp, sym)],
+                                            ),
+                                        },
+                                    ),
+                                    // test_type: ...
+                                    field(
+                                        "test_type",
+                                        match test_type(cx) {
+                                            // test::TestType::UnitTest
+                                            TestType::UnitTest => {
+                                                cx.expr_path(test_type_path("UnitTest"))
+                                            }
+                                            // test::TestType::IntegrationTest
+                                            TestType::IntegrationTest => {
+                                                cx.expr_path(test_type_path("IntegrationTest"))
+                                            }
+                                            // test::TestPath::Unknown
+                                            TestType::Unknown => {
+                                                cx.expr_path(test_type_path("Unknown"))
+                                            }
+                                        },
+                                    ),
+                                    // },
+                                ],
+                            ),
                         ),
-                    ),
-                    // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
-                    field("testfn", test_fn), // }
-                ],
-            ), // }
+                        // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
+                        field("testfn", test_fn), // }
+                    ],
+                ), // }
+            ),
         ),
     );
     test_const = test_const.map(|mut tc| {
diff --git a/src/librustc_expand/build.rs b/src/librustc_expand/build.rs
index af22e46eb6a..08821510630 100644
--- a/src/librustc_expand/build.rs
+++ b/src/librustc_expand/build.rs
@@ -634,7 +634,7 @@ impl<'a> ExtCtxt<'a> {
         mutbl: ast::Mutability,
         expr: P<ast::Expr>,
     ) -> P<ast::Item> {
-        self.item(span, name, Vec::new(), ast::ItemKind::Static(ty, mutbl, expr))
+        self.item(span, name, Vec::new(), ast::ItemKind::Static(ty, mutbl, Some(expr)))
     }
 
     pub fn item_const(
@@ -644,7 +644,7 @@ impl<'a> ExtCtxt<'a> {
         ty: P<ast::Ty>,
         expr: P<ast::Expr>,
     ) -> P<ast::Item> {
-        self.item(span, name, Vec::new(), ast::ItemKind::Const(ty, expr))
+        self.item(span, name, Vec::new(), ast::ItemKind::Const(ty, Some(expr)))
     }
 
     pub fn attribute(&self, mi: ast::MetaItem) -> ast::Attribute {
diff --git a/src/librustc_lint/unused.rs b/src/librustc_lint/unused.rs
index 480df99a01e..7870b9da4cb 100644
--- a/src/librustc_lint/unused.rs
+++ b/src/librustc_lint/unused.rs
@@ -603,7 +603,7 @@ impl EarlyLintPass for UnusedParens {
     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
         use ast::ItemKind::*;
 
-        if let Const(.., ref expr) | Static(.., ref expr) = item.kind {
+        if let Const(.., Some(expr)) | Static(.., Some(expr)) = &item.kind {
             self.check_unused_parens_expr(cx, expr, "assigned value", false, None, None);
         }
     }
diff --git a/src/librustc_parse/parser/item.rs b/src/librustc_parse/parser/item.rs
index 4a5b4ff8e04..893cbf5adfa 100644
--- a/src/librustc_parse/parser/item.rs
+++ b/src/librustc_parse/parser/item.rs
@@ -966,7 +966,7 @@ impl<'a> Parser<'a> {
         }
     }
 
-    /// Parse `["const" | ("static" "mut"?)] $ident ":" $ty = $expr` with
+    /// Parse `["const" | ("static" "mut"?)] $ident ":" $ty (= $expr)?` with
     /// `["const" | ("static" "mut"?)]` already parsed and stored in `m`.
     ///
     /// When `m` is `"const"`, `$ident` may also be `"_"`.
@@ -975,25 +975,22 @@ impl<'a> Parser<'a> {
 
         // Parse the type of a `const` or `static mut?` item.
         // That is, the `":" $ty` fragment.
-        let ty = if self.token == token::Eq {
-            self.recover_missing_const_type(id, m)
-        } else {
-            // Not `=` so expect `":"" $ty` as usual.
-            self.expect(&token::Colon)?;
+        let ty = if self.eat(&token::Colon) {
             self.parse_ty()?
+        } else {
+            self.recover_missing_const_type(id, m)
         };
 
-        self.expect(&token::Eq)?;
-        let e = self.parse_expr()?;
+        let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None };
         self.expect_semi()?;
         let item = match m {
-            Some(m) => ItemKind::Static(ty, m, e),
-            None => ItemKind::Const(ty, e),
+            Some(m) => ItemKind::Static(ty, m, expr),
+            None => ItemKind::Const(ty, expr),
         };
         Ok((id, item))
     }
 
-    /// We were supposed to parse `:` but instead, we're already at `=`.
+    /// We were supposed to parse `:` but the `:` was missing.
     /// This means that the type is missing.
     fn recover_missing_const_type(&mut self, id: Ident, m: Option<Mutability>) -> P<Ty> {
         // Construct the error and stash it away with the hope
diff --git a/src/librustc_resolve/late.rs b/src/librustc_resolve/late.rs
index 1a1a9b1076e..36667e1d6ff 100644
--- a/src/librustc_resolve/late.rs
+++ b/src/librustc_resolve/late.rs
@@ -881,9 +881,9 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                 debug!("resolve_item ItemKind::Const");
                 self.with_item_rib(HasGenericParams::No, |this| {
                     this.visit_ty(ty);
-                    this.with_constant_rib(|this| {
-                        this.visit_expr(expr);
-                    });
+                    if let Some(expr) = expr {
+                        this.with_constant_rib(|this| this.visit_expr(expr));
+                    }
                 });
             }
 
diff --git a/src/librustc_save_analysis/dump_visitor.rs b/src/librustc_save_analysis/dump_visitor.rs
index 5668be40eef..fc5496eb182 100644
--- a/src/librustc_save_analysis/dump_visitor.rs
+++ b/src/librustc_save_analysis/dump_visitor.rs
@@ -400,7 +400,7 @@ impl<'l, 'tcx> DumpVisitor<'l, 'tcx> {
         &mut self,
         item: &'l ast::Item,
         typ: &'l ast::Ty,
-        expr: &'l ast::Expr,
+        expr: Option<&'l ast::Expr>,
     ) {
         let hir_id = self.tcx.hir().node_to_hir_id(item.id);
         self.nest_tables(item.id, |v| {
@@ -409,7 +409,7 @@ impl<'l, 'tcx> DumpVisitor<'l, 'tcx> {
                 v.dumper.dump_def(&access_from!(v.save_ctxt, item, hir_id), var_data);
             }
             v.visit_ty(&typ);
-            v.visit_expr(expr);
+            walk_list!(v, visit_expr, expr);
         });
     }
 
@@ -1293,8 +1293,8 @@ impl<'l, 'tcx> Visitor<'l> for DumpVisitor<'l, 'tcx> {
             Fn(ref sig, ref ty_params, ref body) => {
                 self.process_fn(item, &sig.decl, &sig.header, ty_params, body.as_deref())
             }
-            Static(ref typ, _, ref expr) => self.process_static_or_const_item(item, typ, expr),
-            Const(ref typ, ref expr) => self.process_static_or_const_item(item, &typ, &expr),
+            Static(ref typ, _, ref e) => self.process_static_or_const_item(item, typ, e.as_deref()),
+            Const(ref typ, ref e) => self.process_static_or_const_item(item, typ, e.as_deref()),
             Struct(ref def, ref ty_params) | Union(ref def, ref ty_params) => {
                 self.process_struct(item, def, ty_params)
             }
diff --git a/src/librustc_save_analysis/sig.rs b/src/librustc_save_analysis/sig.rs
index bd7bd9bc616..d678a8b067e 100644
--- a/src/librustc_save_analysis/sig.rs
+++ b/src/librustc_save_analysis/sig.rs
@@ -334,10 +334,13 @@ impl Sig for ast::Item {
 
                 let ty = ty.make(offset + text.len(), id, scx)?;
                 text.push_str(&ty.text);
-                text.push_str(" = ");
 
-                let expr = pprust::expr_to_string(expr).replace('\n', " ");
-                text.push_str(&expr);
+                if let Some(expr) = expr {
+                    text.push_str(" = ");
+                    let expr = pprust::expr_to_string(expr).replace('\n', " ");
+                    text.push_str(&expr);
+                }
+
                 text.push(';');
 
                 Ok(extend_sig(ty, text, defs, vec![]))
@@ -355,10 +358,13 @@ impl Sig for ast::Item {
 
                 let ty = ty.make(offset + text.len(), id, scx)?;
                 text.push_str(&ty.text);
-                text.push_str(" = ");
 
-                let expr = pprust::expr_to_string(expr).replace('\n', " ");
-                text.push_str(&expr);
+                if let Some(expr) = expr {
+                    text.push_str(" = ");
+                    let expr = pprust::expr_to_string(expr).replace('\n', " ");
+                    text.push_str(&expr);
+                }
+
                 text.push(';');
 
                 Ok(extend_sig(ty, text, defs, vec![]))
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index fe353bc994a..61ae14cae02 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -2496,11 +2496,11 @@ pub enum ItemKind {
     /// A static item (`static`).
     ///
     /// E.g., `static FOO: i32 = 42;` or `static FOO: &'static str = "bar";`.
-    Static(P<Ty>, Mutability, P<Expr>),
+    Static(P<Ty>, Mutability, Option<P<Expr>>),
     /// A constant item (`const`).
     ///
     /// E.g., `const FOO: i32 = 42;`.
-    Const(P<Ty>, P<Expr>),
+    Const(P<Ty>, Option<P<Expr>>),
     /// A function declaration (`fn`).
     ///
     /// E.g., `fn foo(bar: usize) -> usize { .. }`.
diff --git a/src/libsyntax/mut_visit.rs b/src/libsyntax/mut_visit.rs
index d36b0a28a8c..cd7a3becca7 100644
--- a/src/libsyntax/mut_visit.rs
+++ b/src/libsyntax/mut_visit.rs
@@ -890,13 +890,9 @@ pub fn noop_visit_item_kind<T: MutVisitor>(kind: &mut ItemKind, vis: &mut T) {
     match kind {
         ItemKind::ExternCrate(_orig_name) => {}
         ItemKind::Use(use_tree) => vis.visit_use_tree(use_tree),
-        ItemKind::Static(ty, _mut, expr) => {
+        ItemKind::Static(ty, _, expr) | ItemKind::Const(ty, expr) => {
             vis.visit_ty(ty);
-            vis.visit_expr(expr);
-        }
-        ItemKind::Const(ty, expr) => {
-            vis.visit_ty(ty);
-            vis.visit_expr(expr);
+            visit_opt(expr, |expr| vis.visit_expr(expr));
         }
         ItemKind::Fn(sig, generics, body) => {
             visit_fn_sig(sig, vis);
diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs
index ed57fc8abf3..4beb94e9f0c 100644
--- a/src/libsyntax/visit.rs
+++ b/src/libsyntax/visit.rs
@@ -300,7 +300,7 @@ pub fn walk_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a Item) {
         ItemKind::Use(ref use_tree) => visitor.visit_use_tree(use_tree, item.id, false),
         ItemKind::Static(ref typ, _, ref expr) | ItemKind::Const(ref typ, ref expr) => {
             visitor.visit_ty(typ);
-            visitor.visit_expr(expr);
+            walk_list!(visitor, visit_expr, expr);
         }
         ItemKind::Fn(ref sig, ref generics, ref body) => {
             visitor.visit_generics(generics);
diff --git a/src/test/ui/parser/item-free-const-no-body-semantic-fail.rs b/src/test/ui/parser/item-free-const-no-body-semantic-fail.rs
new file mode 100644
index 00000000000..613b3c98561
--- /dev/null
+++ b/src/test/ui/parser/item-free-const-no-body-semantic-fail.rs
@@ -0,0 +1,7 @@
+// Semantically, a free `const` item cannot omit its body.
+
+fn main() {}
+
+const A: u8; //~ ERROR free constant item without body
+const B; //~ ERROR free constant item without body
+//~^ ERROR missing type for `const` item
diff --git a/src/test/ui/parser/item-free-const-no-body-semantic-fail.stderr b/src/test/ui/parser/item-free-const-no-body-semantic-fail.stderr
new file mode 100644
index 00000000000..4e97229fa1a
--- /dev/null
+++ b/src/test/ui/parser/item-free-const-no-body-semantic-fail.stderr
@@ -0,0 +1,24 @@
+error: free constant item without body
+  --> $DIR/item-free-const-no-body-semantic-fail.rs:5:1
+   |
+LL | const A: u8;
+   | ^^^^^^^^^^^-
+   |            |
+   |            help: provide a definition for the constant: `= <expr>;`
+
+error: free constant item without body
+  --> $DIR/item-free-const-no-body-semantic-fail.rs:6:1
+   |
+LL | const B;
+   | ^^^^^^^-
+   |        |
+   |        help: provide a definition for the constant: `= <expr>;`
+
+error: missing type for `const` item
+  --> $DIR/item-free-const-no-body-semantic-fail.rs:6:7
+   |
+LL | const B;
+   |       ^ help: provide a type for the item: `B: [type error]`
+
+error: aborting due to 3 previous errors
+
diff --git a/src/test/ui/parser/item-free-const-no-body-syntactic-pass.rs b/src/test/ui/parser/item-free-const-no-body-syntactic-pass.rs
new file mode 100644
index 00000000000..acfdd3c363f
--- /dev/null
+++ b/src/test/ui/parser/item-free-const-no-body-syntactic-pass.rs
@@ -0,0 +1,8 @@
+// Syntactically, a free `const` item can omit its body.
+
+// check-pass
+
+fn main() {}
+
+#[cfg(FALSE)]
+const X: u8;
diff --git a/src/test/ui/parser/item-free-static-no-body-semantic-fail.rs b/src/test/ui/parser/item-free-static-no-body-semantic-fail.rs
new file mode 100644
index 00000000000..780479e3d26
--- /dev/null
+++ b/src/test/ui/parser/item-free-static-no-body-semantic-fail.rs
@@ -0,0 +1,11 @@
+// Semantically, a free `static` item cannot omit its body.
+
+fn main() {}
+
+static A: u8; //~ ERROR free static item without body
+static B; //~ ERROR free static item without body
+//~^ ERROR missing type for `static` item
+
+static mut C: u8; //~ ERROR free static item without body
+static mut D; //~ ERROR free static item without body
+//~^ ERROR missing type for `static mut` item
diff --git a/src/test/ui/parser/item-free-static-no-body-semantic-fail.stderr b/src/test/ui/parser/item-free-static-no-body-semantic-fail.stderr
new file mode 100644
index 00000000000..60b7bb34c69
--- /dev/null
+++ b/src/test/ui/parser/item-free-static-no-body-semantic-fail.stderr
@@ -0,0 +1,46 @@
+error: free static item without body
+  --> $DIR/item-free-static-no-body-semantic-fail.rs:5:1
+   |
+LL | static A: u8;
+   | ^^^^^^^^^^^^-
+   |             |
+   |             help: provide a definition for the static: `= <expr>;`
+
+error: free static item without body
+  --> $DIR/item-free-static-no-body-semantic-fail.rs:6:1
+   |
+LL | static B;
+   | ^^^^^^^^-
+   |         |
+   |         help: provide a definition for the static: `= <expr>;`
+
+error: free static item without body
+  --> $DIR/item-free-static-no-body-semantic-fail.rs:9:1
+   |
+LL | static mut C: u8;
+   | ^^^^^^^^^^^^^^^^-
+   |                 |
+   |                 help: provide a definition for the static: `= <expr>;`
+
+error: free static item without body
+  --> $DIR/item-free-static-no-body-semantic-fail.rs:10:1
+   |
+LL | static mut D;
+   | ^^^^^^^^^^^^-
+   |             |
+   |             help: provide a definition for the static: `= <expr>;`
+
+error: missing type for `static` item
+  --> $DIR/item-free-static-no-body-semantic-fail.rs:6:8
+   |
+LL | static B;
+   |        ^ help: provide a type for the item: `B: [type error]`
+
+error: missing type for `static mut` item
+  --> $DIR/item-free-static-no-body-semantic-fail.rs:10:12
+   |
+LL | static mut D;
+   |            ^ help: provide a type for the item: `D: [type error]`
+
+error: aborting due to 6 previous errors
+
diff --git a/src/test/ui/parser/item-free-static-no-body-syntactic-pass.rs b/src/test/ui/parser/item-free-static-no-body-syntactic-pass.rs
new file mode 100644
index 00000000000..db0039204d8
--- /dev/null
+++ b/src/test/ui/parser/item-free-static-no-body-syntactic-pass.rs
@@ -0,0 +1,8 @@
+// Syntactically, a free `const` item can omit its body.
+
+// check-pass
+
+fn main() {}
+
+#[cfg(FALSE)]
+static X: u8;