about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDan Aloni <alonid@gmail.com>2018-04-10 02:08:47 +0300
committerDan Aloni <alonid@gmail.com>2018-05-13 19:17:02 +0300
commit37ed2ab91038567bafe3fd2e545c7d1631ff2ab0 (patch)
tree27b8125e37ecd34720f6cee3286e821c6a6df96d
parent3e955a058108fcadf0a8222de5868b0c905534d5 (diff)
downloadrust-37ed2ab91038567bafe3fd2e545c7d1631ff2ab0.tar.gz
rust-37ed2ab91038567bafe3fd2e545c7d1631ff2ab0.zip
Macros: Add a 'literal' fragment specifier
Implements RFC 1576.

See: https://github.com/rust-lang/rfcs/blob/master/text/1576-macros-literal-matcher.md

Changes are mostly in libsyntax, docs, and tests. Feature gate is
enabled for 1.27.0.

Many thanks to Vadim Petrochenkov for following through code reviews
and suggestions.

Example:

````rust

macro_rules! test_literal {
    ($l:literal) => {
        println!("literal: {}", $l);
    };
    ($e:expr) => {
        println!("expr: {}", $e);
    };
}

fn main() {
    let a = 1;
    test_literal!(a);
    test_literal!(2);
    test_literal!(-3);
}
```

Output:

```
expr: 1
literal: 2
literal: -3
```
-rw-r--r--src/doc/unstable-book/src/language-features/macro-literal-matcher.md17
-rw-r--r--src/librustc_passes/ast_validation.rs2
-rw-r--r--src/libsyntax/attr.rs2
-rw-r--r--src/libsyntax/ext/tt/macro_parser.rs2
-rw-r--r--src/libsyntax/ext/tt/macro_rules.rs21
-rw-r--r--src/libsyntax/feature_gate.rs6
-rw-r--r--src/libsyntax/fold.rs1
-rw-r--r--src/libsyntax/parse/parser.rs18
-rw-r--r--src/libsyntax/parse/token.rs21
-rw-r--r--src/libsyntax/print/pprust.rs1
-rw-r--r--src/test/run-pass/macro-literal.rs143
-rw-r--r--src/test/ui/feature-gate-macro-literal-matcher.rs19
-rw-r--r--src/test/ui/feature-gate-macro-literal-matcher.stderr11
-rw-r--r--src/test/ui/macro-invalid-fragment-spec.stderr2
14 files changed, 251 insertions, 15 deletions
diff --git a/src/doc/unstable-book/src/language-features/macro-literal-matcher.md b/src/doc/unstable-book/src/language-features/macro-literal-matcher.md
new file mode 100644
index 00000000000..7e3638fd1cf
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/macro-literal-matcher.md
@@ -0,0 +1,17 @@
+# `macro_literal_matcher`
+
+The tracking issue for this feature is: [#35625]
+
+The RFC is: [rfc#1576].
+
+With this feature gate enabled, the [list of fragment specifiers][frags] gains one more entry:
+
+* `literal`: a literal. Examples: 2, "string", 'c'
+
+A `literal` may be followed by anything, similarly to the `ident` specifier.
+
+[rfc#1576]: http://rust-lang.github.io/rfcs/1576-macros-literal-matcher.html
+[#35625]: https://github.com/rust-lang/rust/issues/35625
+[frags]: ../book/first-edition/macros.html#syntactic-requirements
+
+------------------------
diff --git a/src/librustc_passes/ast_validation.rs b/src/librustc_passes/ast_validation.rs
index 6708640379a..4789e2e50ca 100644
--- a/src/librustc_passes/ast_validation.rs
+++ b/src/librustc_passes/ast_validation.rs
@@ -114,7 +114,7 @@ impl<'a> AstValidator<'a> {
         }
     }
 
-    /// matches '-' lit | lit (cf. parser::Parser::parse_pat_literal_maybe_minus),
+    /// matches '-' lit | lit (cf. parser::Parser::parse_literal_maybe_minus),
     /// or path for ranges.
     ///
     /// FIXME: do we want to allow expr -> pattern conversion to create path expressions?
diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs
index ace9904e0c0..fcda6ce9b16 100644
--- a/src/libsyntax/attr.rs
+++ b/src/libsyntax/attr.rs
@@ -1348,7 +1348,7 @@ impl LitKind {
             Token::Ident(ident, false) if ident.name == "true" => Some(LitKind::Bool(true)),
             Token::Ident(ident, false) if ident.name == "false" => Some(LitKind::Bool(false)),
             Token::Interpolated(ref nt) => match nt.0 {
-                token::NtExpr(ref v) => match v.node {
+                token::NtExpr(ref v) | token::NtLiteral(ref v) => match v.node {
                     ExprKind::Lit(ref lit) => Some(lit.node.clone()),
                     _ => None,
                 },
diff --git a/src/libsyntax/ext/tt/macro_parser.rs b/src/libsyntax/ext/tt/macro_parser.rs
index 71634ada894..f0339b89839 100644
--- a/src/libsyntax/ext/tt/macro_parser.rs
+++ b/src/libsyntax/ext/tt/macro_parser.rs
@@ -735,6 +735,7 @@ fn may_begin_with(name: &str, token: &Token) -> bool {
         "expr" => token.can_begin_expr(),
         "ty" => token.can_begin_type(),
         "ident" => get_macro_ident(token).is_some(),
+        "literal" => token.can_begin_literal_or_bool(),
         "vis" => match *token {
             // The follow-set of :vis + "priv" keyword + interpolated
             Token::Comma | Token::Ident(..) | Token::Interpolated(_) => true,
@@ -821,6 +822,7 @@ fn parse_nt<'a>(p: &mut Parser<'a>, sp: Span, name: &str) -> Nonterminal {
         },
         "pat" => token::NtPat(panictry!(p.parse_pat())),
         "expr" => token::NtExpr(panictry!(p.parse_expr())),
+        "literal" => token::NtLiteral(panictry!(p.parse_literal_maybe_minus())),
         "ty" => token::NtTy(panictry!(p.parse_ty())),
         // this could be handled like a token, since it is one
         "ident" => if let Some((ident, is_raw)) = get_macro_ident(&p.token) {
diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs
index ffe68289d52..1fc5aed7e7a 100644
--- a/src/libsyntax/ext/tt/macro_rules.rs
+++ b/src/libsyntax/ext/tt/macro_rules.rs
@@ -647,7 +647,7 @@ fn check_matcher_core(sess: &ParseSess,
                     let msg = format!("invalid fragment specifier `{}`", bad_frag);
                     sess.span_diagnostic.struct_span_err(token.span(), &msg)
                         .help("valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, \
-                              `pat`, `ty`, `path`, `meta`, `tt`, `item` and `vis`")
+                              `pat`, `ty`, `literal`, `path`, `meta`, `tt`, `item` and `vis`")
                         .emit();
                     // (This eliminates false positives and duplicates
                     // from error messages.)
@@ -784,6 +784,7 @@ fn frag_can_be_followed_by_any(frag: &str) -> bool {
         "item"     | // always terminated by `}` or `;`
         "block"    | // exactly one token tree
         "ident"    | // exactly one token tree
+        "literal"  | // exactly one token tree
         "meta"     | // exactly one token tree
         "lifetime" | // exactly one token tree
         "tt" =>   // exactly one token tree
@@ -850,6 +851,10 @@ fn is_in_follow(tok: &quoted::TokenTree, frag: &str) -> Result<bool, (String, &'
                 // being a single token, idents and lifetimes are harmless
                 Ok(true)
             },
+            "literal" => {
+                // literals may be of a single token, or two tokens (negative numbers)
+                Ok(true)
+            },
             "meta" | "tt" => {
                 // being either a single token or a delimited sequence, tt is
                 // harmless
@@ -873,7 +878,7 @@ fn is_in_follow(tok: &quoted::TokenTree, frag: &str) -> Result<bool, (String, &'
             _ => Err((format!("invalid fragment specifier `{}`", frag),
                      "valid fragment specifiers are `ident`, `block`, \
                       `stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt`, \
-                      `item` and `vis`"))
+                      `literal`, `item` and `vis`"))
         }
     }
 }
@@ -913,6 +918,18 @@ fn is_legal_fragment_specifier(sess: &ParseSess,
             }
             true
         },
+        "literal" => {
+            if !features.macro_literal_matcher &&
+               !attr::contains_name(attrs, "allow_internal_unstable") {
+                let explain = feature_gate::EXPLAIN_LITERAL_MATCHER;
+                emit_feature_err(sess,
+                                 "macro_literal_matcher",
+                                 frag_span,
+                                 GateIssue::Language,
+                                 explain);
+            }
+            true
+        },
         "vis" => {
             if !features.macro_vis_matcher &&
                !attr::contains_name(attrs, "allow_internal_unstable") {
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index b27568a61f8..562705462e2 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -463,6 +463,9 @@ declare_features! (
 
     // Scoped attributes
     (active, tool_attributes, "1.25.0", Some(44690), None),
+
+    // Allows use of the :literal macro fragment specifier (RFC 1576)
+    (active, macro_literal_matcher, "1.27.0", Some(35625), None),
 );
 
 declare_features! (
@@ -1331,6 +1334,9 @@ pub const EXPLAIN_VIS_MATCHER: &'static str =
 pub const EXPLAIN_LIFETIME_MATCHER: &'static str =
     ":lifetime fragment specifier is experimental and subject to change";
 
+pub const EXPLAIN_LITERAL_MATCHER: &'static str =
+    ":literal fragment specifier is experimental and subject to change";
+
 pub const EXPLAIN_UNSIZED_TUPLE_COERCION: &'static str =
     "Unsized tuple coercion is not stable enough for use and is subject to change";
 
diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs
index a0cd831a9ba..d67995761f6 100644
--- a/src/libsyntax/fold.rs
+++ b/src/libsyntax/fold.rs
@@ -635,6 +635,7 @@ pub fn noop_fold_interpolated<T: Folder>(nt: token::Nonterminal, fld: &mut T)
         token::NtTy(ty) => token::NtTy(fld.fold_ty(ty)),
         token::NtIdent(ident, is_raw) => token::NtIdent(fld.fold_ident(ident), is_raw),
         token::NtLifetime(ident) => token::NtLifetime(fld.fold_ident(ident)),
+        token::NtLiteral(expr) => token::NtLiteral(fld.fold_expr(expr)),
         token::NtMeta(meta) => token::NtMeta(fld.fold_meta_item(meta)),
         token::NtPath(path) => token::NtPath(fld.fold_path(path)),
         token::NtTT(tt) => token::NtTT(fld.fold_tt(tt)),
diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs
index 49b30c6f460..3f0df6d055b 100644
--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -115,7 +115,7 @@ macro_rules! maybe_whole_expr {
     ($p:expr) => {
         if let token::Interpolated(nt) = $p.token.clone() {
             match nt.0 {
-                token::NtExpr(ref e) => {
+                token::NtExpr(ref e) | token::NtLiteral(ref e) => {
                     $p.bump();
                     return Ok((*e).clone());
                 }
@@ -1823,7 +1823,7 @@ impl<'a> Parser<'a> {
     pub fn parse_lit_token(&mut self) -> PResult<'a, LitKind> {
         let out = match self.token {
             token::Interpolated(ref nt) => match nt.0 {
-                token::NtExpr(ref v) => match v.node {
+                token::NtExpr(ref v) | token::NtLiteral(ref v) => match v.node {
                     ExprKind::Lit(ref lit) => { lit.node.clone() }
                     _ => { return self.unexpected_last(&self.token); }
                 },
@@ -1862,7 +1862,7 @@ impl<'a> Parser<'a> {
     }
 
     /// matches '-' lit | lit (cf. ast_validation::AstValidator::check_expr_within_pat)
-    pub fn parse_pat_literal_maybe_minus(&mut self) -> PResult<'a, P<Expr>> {
+    pub fn parse_literal_maybe_minus(&mut self) -> PResult<'a, P<Expr>> {
         maybe_whole_expr!(self);
 
         let minus_lo = self.span;
@@ -2407,10 +2407,10 @@ impl<'a> Parser<'a> {
                     hi = pth.span;
                     ex = ExprKind::Path(None, pth);
                 } else {
-                    match self.parse_lit() {
-                        Ok(lit) => {
-                            hi = lit.span;
-                            ex = ExprKind::Lit(P(lit));
+                    match self.parse_literal_maybe_minus() {
+                        Ok(expr) => {
+                            hi = expr.span;
+                            ex = expr.node.clone();
                         }
                         Err(mut err) => {
                             self.cancel(&mut err);
@@ -3724,7 +3724,7 @@ impl<'a> Parser<'a> {
             let hi = self.prev_span;
             Ok(self.mk_expr(lo.to(hi), ExprKind::Path(qself, path), ThinVec::new()))
         } else {
-            self.parse_pat_literal_maybe_minus()
+            self.parse_literal_maybe_minus()
         }
     }
 
@@ -3914,7 +3914,7 @@ impl<'a> Parser<'a> {
                 }
             } else {
                 // Try to parse everything else as literal with optional minus
-                match self.parse_pat_literal_maybe_minus() {
+                match self.parse_literal_maybe_minus() {
                     Ok(begin) => {
                         if self.eat(&token::DotDotDot) {
                             let end = self.parse_pat_range_end()?;
diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs
index 938711ca1d4..6bcc1b0f026 100644
--- a/src/libsyntax/parse/token.rs
+++ b/src/libsyntax/parse/token.rs
@@ -280,7 +280,12 @@ impl Token {
             Lifetime(..)                      | // labeled loop
             Pound                             => true, // expression attributes
             Interpolated(ref nt) => match nt.0 {
-                NtIdent(..) | NtExpr(..) | NtBlock(..) | NtPath(..) | NtLifetime(..) => true,
+                NtLiteral(..) |
+                NtIdent(..)   |
+                NtExpr(..)    |
+                NtBlock(..)   |
+                NtPath(..)    |
+                NtLifetime(..) => true,
                 _ => false,
             },
             _ => false,
@@ -324,6 +329,18 @@ impl Token {
         }
     }
 
+    /// Returns `true` if the token is any literal, a minus (which can follow a literal,
+    /// for example a '-42', or one of the boolean idents).
+    pub fn can_begin_literal_or_bool(&self) -> bool {
+        match *self {
+            Literal(..)  => true,
+            BinOp(Minus) => true,
+            Ident(ident, false) if ident.name == keywords::True.name() => true,
+            Ident(ident, false) if ident.name == keywords::False.name() => true,
+            _            => false,
+        }
+    }
+
     /// Returns an identifier if this token is an identifier.
     pub fn ident(&self) -> Option<(ast::Ident, /* is_raw */ bool)> {
         match *self {
@@ -672,6 +689,7 @@ pub enum Nonterminal {
     NtTy(P<ast::Ty>),
     NtIdent(ast::Ident, /* is_raw */ bool),
     NtLifetime(ast::Ident),
+    NtLiteral(P<ast::Expr>),
     /// Stuff inside brackets for attributes
     NtMeta(ast::MetaItem),
     NtPath(ast::Path),
@@ -713,6 +731,7 @@ impl fmt::Debug for Nonterminal {
             NtExpr(..) => f.pad("NtExpr(..)"),
             NtTy(..) => f.pad("NtTy(..)"),
             NtIdent(..) => f.pad("NtIdent(..)"),
+            NtLiteral(..) => f.pad("NtLiteral(..)"),
             NtMeta(..) => f.pad("NtMeta(..)"),
             NtPath(..) => f.pad("NtPath(..)"),
             NtTT(..) => f.pad("NtTT(..)"),
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index b8ddb063d98..99a6fcf170d 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -273,6 +273,7 @@ pub fn token_to_string(tok: &Token) -> String {
             token::NtIdent(e, false)    => ident_to_string(e),
             token::NtIdent(e, true)     => format!("r#{}", ident_to_string(e)),
             token::NtLifetime(e)        => ident_to_string(e),
+            token::NtLiteral(ref e)     => expr_to_string(e),
             token::NtTT(ref tree)       => tt_to_string(tree.clone()),
             token::NtArm(ref e)         => arm_to_string(e),
             token::NtImplItem(ref e)    => impl_item_to_string(e),
diff --git a/src/test/run-pass/macro-literal.rs b/src/test/run-pass/macro-literal.rs
new file mode 100644
index 00000000000..0bcda7bc144
--- /dev/null
+++ b/src/test/run-pass/macro-literal.rs
@@ -0,0 +1,143 @@
+// 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(macro_literal_matcher)]
+
+macro_rules! mtester {
+    ($l:literal) => {
+        &format!("macro caught literal: {}", $l)
+    };
+    ($e:expr) => {
+        &format!("macro caught expr: {}", $e)
+    };
+}
+
+macro_rules! two_negative_literals {
+    ($l1:literal $l2:literal) => {
+        &format!("macro caught literals: {}, {}", $l1, $l2)
+    };
+}
+
+macro_rules! only_expr {
+    ($e:expr) => {
+        &format!("macro caught expr: {}", $e)
+    };
+}
+
+macro_rules! mtester_dbg {
+    ($l:literal) => {
+        &format!("macro caught literal: {:?}", $l)
+    };
+    ($e:expr) => {
+        &format!("macro caught expr: {:?}", $e)
+    };
+}
+
+macro_rules! catch_range {
+    ($s:literal ... $e:literal) => {
+        &format!("macro caught literal: {} ... {}", $s, $e)
+    };
+    (($s:expr) ... ($e:expr)) => { // Must use ')' before '...'
+        &format!("macro caught expr: {} ... {}", $s, $e)
+    };
+}
+
+macro_rules! pat_match {
+    ($s:literal ... $e:literal) => {
+        match 3 {
+            $s ... $e => "literal, in range",
+            _ => "literal, other",
+        }
+    };
+    ($s:pat) => {
+        match 3 {
+            $s => "pat, single",
+            _ => "pat, other",
+        }
+    };
+}
+
+macro_rules! match_attr {
+    (#[$attr:meta] $e:literal) => {
+        "attr matched literal"
+    };
+    (#[$attr:meta] $e:expr) => {
+        "attr matched expr"
+    };
+}
+
+macro_rules! match_produced_attr {
+    ($lit: literal) => {
+        // Struct with doc comment passed via $literal
+        #[doc = $lit]
+        struct LiteralProduced;
+    };
+    ($expr: expr) => {
+        struct ExprProduced;
+    };
+}
+
+macro_rules! test_user {
+    ($s:literal, $e:literal) => {
+        {
+            let mut v = Vec::new();
+            for i in $s .. $e {
+                v.push(i);
+            }
+            "literal"
+        }
+    };
+    ($s:expr, $e: expr) => {
+        {
+            let mut v = Vec::new();
+            for i in $s .. $e {
+                v.push(i);
+            }
+            "expr"
+        }
+    };
+}
+
+pub fn main() {
+    // Cases where 'literal' catches
+    assert_eq!(mtester!("str"), "macro caught literal: str");
+    assert_eq!(mtester!(2), "macro caught literal: 2");
+    assert_eq!(mtester!(2.2), "macro caught literal: 2.2");
+    assert_eq!(mtester!(1u32), "macro caught literal: 1");
+    assert_eq!(mtester!(0x32), "macro caught literal: 50");
+    assert_eq!(mtester!('c'), "macro caught literal: c");
+    assert_eq!(mtester!(-1.2), "macro caught literal: -1.2");
+    assert_eq!(two_negative_literals!(-2 -3), "macro caught literals: -2, -3");
+    assert_eq!(catch_range!(2 ... 3), "macro caught literal: 2 ... 3");
+    assert_eq!(match_attr!(#[attr] 1), "attr matched literal");
+    assert_eq!(test_user!(10, 20), "literal");
+    assert_eq!(mtester!(false), "macro caught literal: false");
+    assert_eq!(mtester!(true), "macro caught literal: true");
+    match_produced_attr!("a");
+    let _a = LiteralProduced;
+    assert_eq!(pat_match!(1 ... 3), "literal, in range");
+    assert_eq!(pat_match!(4 ... 6), "literal, other");
+
+    // Cases where 'expr' catches
+    assert_eq!(mtester!((-1.2)), "macro caught expr: -1.2");
+    assert_eq!(only_expr!(-1.2), "macro caught expr: -1.2");
+    assert_eq!(mtester!((1 + 3)), "macro caught expr: 4");
+    assert_eq!(mtester_dbg!(()), "macro caught expr: ()");
+    assert_eq!(catch_range!((1 + 1) ... (2 + 2)), "macro caught expr: 2 ... 4");
+    assert_eq!(match_attr!(#[attr] (1 + 2)), "attr matched expr");
+    assert_eq!(test_user!(10, (20 + 2)), "expr");
+
+    match_produced_attr!((3 + 2));
+    let _b = ExprProduced;
+
+    // Cases where 'pat' matched
+    assert_eq!(pat_match!(3), "pat, single");
+    assert_eq!(pat_match!(6), "pat, other");
+}
diff --git a/src/test/ui/feature-gate-macro-literal-matcher.rs b/src/test/ui/feature-gate-macro-literal-matcher.rs
new file mode 100644
index 00000000000..db5cca193ab
--- /dev/null
+++ b/src/test/ui/feature-gate-macro-literal-matcher.rs
@@ -0,0 +1,19 @@
+// 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.
+
+// Test that the :lifetime macro fragment cannot be used when macro_lifetime_matcher
+// feature gate is not used.
+
+macro_rules! m { ($lt:literal) => {} }
+//~^ ERROR :literal fragment specifier is experimental and subject to change
+
+fn main() {
+    m!("some string literal");
+}
diff --git a/src/test/ui/feature-gate-macro-literal-matcher.stderr b/src/test/ui/feature-gate-macro-literal-matcher.stderr
new file mode 100644
index 00000000000..f714b916966
--- /dev/null
+++ b/src/test/ui/feature-gate-macro-literal-matcher.stderr
@@ -0,0 +1,11 @@
+error[E0658]: :literal fragment specifier is experimental and subject to change (see issue #35625)
+  --> $DIR/feature-gate-macro-literal-matcher.rs:14:19
+   |
+LL | macro_rules! m { ($lt:literal) => {} }
+   |                   ^^^^^^^^^^^
+   |
+   = help: add #![feature(macro_literal_matcher)] to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/macro-invalid-fragment-spec.stderr b/src/test/ui/macro-invalid-fragment-spec.stderr
index bdb0e4a5c40..765621f51d4 100644
--- a/src/test/ui/macro-invalid-fragment-spec.stderr
+++ b/src/test/ui/macro-invalid-fragment-spec.stderr
@@ -4,7 +4,7 @@ error: invalid fragment specifier `foo`
 LL |     ($x:foo) => ()
    |      ^^^^^^
    |
-   = help: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt`, `item` and `vis`
+   = help: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `literal`, `path`, `meta`, `tt`, `item` and `vis`
 
 error: aborting due to previous error