Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macros: Add a 'literal' fragment specifier #49835

Merged
merged 1 commit into from
May 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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

------------------------
2 changes: 1 addition & 1 deletion src/librustc_passes/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
2 changes: 2 additions & 0 deletions src/libsyntax/ext/tt/macro_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
21 changes: 19 additions & 2 deletions src/libsyntax/ext/tt/macro_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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`"))
}
}
}
Expand Down Expand Up @@ -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") {
Expand Down
6 changes: 6 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! (
Expand Down Expand Up @@ -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";

Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
18 changes: 9 additions & 9 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down Expand Up @@ -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); }
},
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -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()?;
Expand Down
21 changes: 20 additions & 1 deletion src/libsyntax/parse/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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(..)"),
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/print/pprust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
143 changes: 143 additions & 0 deletions src/test/run-pass/macro-literal.rs
Original file line number Diff line number Diff line change
@@ -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) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to attributes, the range should rather be produced by a macro (1833a1f#r184891034), this test with ... on the left side of the macro is testing something entirely different and probably not useful.

&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) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant literal inside the attribute produced by the macro, e.g. something like

macro_rules! match_attr {
    ($e: literal) => {
        // Struct with doc comment passed via $literal
        #[doc = $literal]
        struct S;
    }
}

"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) => {
Copy link
Contributor

@petrochenkov petrochenkov Apr 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a case checking that $lit1 ... $lit2 is parsed as a range pattern in patterns?

{
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");
}
Loading