Introduce InvisibleOrigin on invisible delimiters.

It's not used meaningfully yet, but will be needed to get rid of
interpolated tokens.
This commit is contained in:
Nicholas Nethercote 2024-04-17 09:59:27 +10:00
parent a1f2999536
commit afe238f66f
10 changed files with 133 additions and 31 deletions

View File

@ -457,7 +457,7 @@ impl MetaItemKind {
tokens: &mut impl Iterator<Item = &'a TokenTree>, tokens: &mut impl Iterator<Item = &'a TokenTree>,
) -> Option<MetaItemKind> { ) -> Option<MetaItemKind> {
match tokens.next() { match tokens.next() {
Some(TokenTree::Delimited(.., Delimiter::Invisible, inner_tokens)) => { Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => {
MetaItemKind::name_value_from_tokens(&mut inner_tokens.trees()) MetaItemKind::name_value_from_tokens(&mut inner_tokens.trees())
} }
Some(TokenTree::Token(token, _)) => { Some(TokenTree::Token(token, _)) => {
@ -605,7 +605,7 @@ impl MetaItemInner {
tokens.next(); tokens.next();
return Some(MetaItemInner::Lit(lit)); return Some(MetaItemInner::Lit(lit));
} }
Some(TokenTree::Delimited(.., Delimiter::Invisible, inner_tokens)) => { Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => {
tokens.next(); tokens.next();
return MetaItemInner::from_tokens(&mut inner_tokens.trees().peekable()); return MetaItemInner::from_tokens(&mut inner_tokens.trees().peekable());
} }

View File

@ -42,11 +42,86 @@ pub enum BinOpToken {
Shr, Shr,
} }
// This type must not implement `Hash` due to the unusual `PartialEq` impl below.
#[derive(Copy, Clone, Debug, Encodable, Decodable, HashStable_Generic)]
pub enum InvisibleOrigin {
// From the expansion of a metavariable in a declarative macro.
MetaVar(MetaVarKind),
// Converted from `proc_macro::Delimiter` in
// `proc_macro::Delimiter::to_internal`, i.e. returned by a proc macro.
ProcMacro,
// Converted from `TokenKind::Interpolated` in
// `TokenStream::flatten_token`. Treated similarly to `ProcMacro`.
FlattenToken,
}
impl PartialEq for InvisibleOrigin {
#[inline]
fn eq(&self, _other: &InvisibleOrigin) -> bool {
// When we had AST-based nonterminals we couldn't compare them, and the
// old `Nonterminal` type had an `eq` that always returned false,
// resulting in this restriction:
// https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment
// This `eq` emulates that behaviour. We could consider lifting this
// restriction now but there are still cases involving invisible
// delimiters that make it harder than it first appears.
false
}
}
/// Annoyingly similar to `NonterminalKind`, but the slight differences are important.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Encodable, Decodable, Hash, HashStable_Generic)]
pub enum MetaVarKind {
Item,
Block,
Stmt,
Pat(NtPatKind),
Expr {
kind: NtExprKind,
// This field is needed for `Token::can_begin_literal_maybe_minus`.
can_begin_literal_maybe_minus: bool,
// This field is needed for `Token::can_begin_string_literal`.
can_begin_string_literal: bool,
},
Ty,
Ident,
Lifetime,
Literal,
Meta,
Path,
Vis,
TT,
}
impl fmt::Display for MetaVarKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let sym = match self {
MetaVarKind::Item => sym::item,
MetaVarKind::Block => sym::block,
MetaVarKind::Stmt => sym::stmt,
MetaVarKind::Pat(PatParam { inferred: true } | PatWithOr) => sym::pat,
MetaVarKind::Pat(PatParam { inferred: false }) => sym::pat_param,
MetaVarKind::Expr { kind: Expr2021 { inferred: true } | Expr, .. } => sym::expr,
MetaVarKind::Expr { kind: Expr2021 { inferred: false }, .. } => sym::expr_2021,
MetaVarKind::Ty => sym::ty,
MetaVarKind::Ident => sym::ident,
MetaVarKind::Lifetime => sym::lifetime,
MetaVarKind::Literal => sym::literal,
MetaVarKind::Meta => sym::meta,
MetaVarKind::Path => sym::path,
MetaVarKind::Vis => sym::vis,
MetaVarKind::TT => sym::tt,
};
write!(f, "{sym}")
}
}
/// Describes how a sequence of token trees is delimited. /// Describes how a sequence of token trees is delimited.
/// Cannot use `proc_macro::Delimiter` directly because this /// Cannot use `proc_macro::Delimiter` directly because this
/// structure should implement some additional traits. /// structure should implement some additional traits.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Encodable, Decodable, HashStable_Generic)]
#[derive(Encodable, Decodable, Hash, HashStable_Generic)]
pub enum Delimiter { pub enum Delimiter {
/// `( ... )` /// `( ... )`
Parenthesis, Parenthesis,
@ -59,7 +134,34 @@ pub enum Delimiter {
/// "macro variable" `$var`. It is important to preserve operator priorities in cases like /// "macro variable" `$var`. It is important to preserve operator priorities in cases like
/// `$var * 3` where `$var` is `1 + 2`. /// `$var * 3` where `$var` is `1 + 2`.
/// Invisible delimiters might not survive roundtrip of a token stream through a string. /// Invisible delimiters might not survive roundtrip of a token stream through a string.
Invisible, Invisible(InvisibleOrigin),
}
impl Delimiter {
// Should the parser skip these delimiters? Only happens for certain kinds
// of invisible delimiters. Ideally this function will eventually disappear
// and no invisible delimiters will be skipped.
#[inline]
pub fn skip(&self) -> bool {
match self {
Delimiter::Parenthesis | Delimiter::Bracket | Delimiter::Brace => false,
Delimiter::Invisible(InvisibleOrigin::MetaVar(_)) => false,
Delimiter::Invisible(InvisibleOrigin::FlattenToken | InvisibleOrigin::ProcMacro) => {
true
}
}
}
// This exists because `InvisibleOrigin`s should be compared. It is only used for assertions.
pub fn eq_ignoring_invisible_origin(&self, other: &Delimiter) -> bool {
match (self, other) {
(Delimiter::Parenthesis, Delimiter::Parenthesis) => true,
(Delimiter::Brace, Delimiter::Brace) => true,
(Delimiter::Bracket, Delimiter::Bracket) => true,
(Delimiter::Invisible(_), Delimiter::Invisible(_)) => true,
_ => false,
}
}
} }
// Note that the suffix is *not* considered when deciding the `LitKind` in this // Note that the suffix is *not* considered when deciding the `LitKind` in this
@ -896,7 +998,7 @@ impl PartialEq<TokenKind> for Token {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Encodable, Decodable)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Encodable, Decodable, Hash, HashStable_Generic)]
pub enum NtPatKind { pub enum NtPatKind {
// Matches or-patterns. Was written using `pat` in edition 2021 or later. // Matches or-patterns. Was written using `pat` in edition 2021 or later.
PatWithOr, PatWithOr,
@ -906,7 +1008,7 @@ pub enum NtPatKind {
PatParam { inferred: bool }, PatParam { inferred: bool },
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Encodable, Decodable)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Encodable, Decodable, Hash, HashStable_Generic)]
pub enum NtExprKind { pub enum NtExprKind {
// Matches expressions using the post-edition 2024. Was written using // Matches expressions using the post-edition 2024. Was written using
// `expr` in edition 2024 or later. // `expr` in edition 2024 or later.
@ -933,7 +1035,7 @@ pub enum Nonterminal {
NtVis(P<ast::Visibility>), NtVis(P<ast::Visibility>),
} }
#[derive(Debug, Copy, Clone, PartialEq, Encodable, Decodable)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Encodable, Decodable, Hash, HashStable_Generic)]
pub enum NonterminalKind { pub enum NonterminalKind {
Item, Item,
Block, Block,

View File

@ -24,7 +24,7 @@ use rustc_span::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, Symbol, sym};
use crate::ast::{AttrStyle, StmtKind}; use crate::ast::{AttrStyle, StmtKind};
use crate::ast_traits::{HasAttrs, HasTokens}; use crate::ast_traits::{HasAttrs, HasTokens};
use crate::token::{self, Delimiter, Nonterminal, Token, TokenKind}; use crate::token::{self, Delimiter, InvisibleOrigin, Nonterminal, Token, TokenKind};
use crate::{AttrVec, Attribute}; use crate::{AttrVec, Attribute};
/// Part of a `TokenStream`. /// Part of a `TokenStream`.
@ -484,13 +484,13 @@ impl TokenStream {
token::NtLifetime(ident, is_raw) => TokenTree::Delimited( token::NtLifetime(ident, is_raw) => TokenTree::Delimited(
DelimSpan::from_single(token.span), DelimSpan::from_single(token.span),
DelimSpacing::new(Spacing::JointHidden, spacing), DelimSpacing::new(Spacing::JointHidden, spacing),
Delimiter::Invisible, Delimiter::Invisible(InvisibleOrigin::FlattenToken),
TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span), TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span),
), ),
token::Interpolated(ref nt) => TokenTree::Delimited( token::Interpolated(ref nt) => TokenTree::Delimited(
DelimSpan::from_single(token.span), DelimSpan::from_single(token.span),
DelimSpacing::new(Spacing::JointHidden, spacing), DelimSpacing::new(Spacing::JointHidden, spacing),
Delimiter::Invisible, Delimiter::Invisible(InvisibleOrigin::FlattenToken),
TokenStream::from_nonterminal_ast(&nt).flattened(), TokenStream::from_nonterminal_ast(&nt).flattened(),
), ),
_ => TokenTree::Token(token.clone(), spacing), _ => TokenTree::Token(token.clone(), spacing),

View File

@ -942,9 +942,8 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
token::CloseDelim(Delimiter::Bracket) => "]".into(), token::CloseDelim(Delimiter::Bracket) => "]".into(),
token::OpenDelim(Delimiter::Brace) => "{".into(), token::OpenDelim(Delimiter::Brace) => "{".into(),
token::CloseDelim(Delimiter::Brace) => "}".into(), token::CloseDelim(Delimiter::Brace) => "}".into(),
token::OpenDelim(Delimiter::Invisible) | token::CloseDelim(Delimiter::Invisible) => { token::OpenDelim(Delimiter::Invisible(_))
"".into() | token::CloseDelim(Delimiter::Invisible(_)) => "".into(),
}
token::Pound => "#".into(), token::Pound => "#".into(),
token::Dollar => "$".into(), token::Dollar => "$".into(),
token::Question => "?".into(), token::Question => "?".into(),

View File

@ -693,7 +693,7 @@ fn has_compile_error_macro(rhs: &mbe::TokenTree) -> bool {
&& let mbe::TokenTree::Token(bang) = bang && let mbe::TokenTree::Token(bang) = bang
&& let TokenKind::Not = bang.kind && let TokenKind::Not = bang.kind
&& let mbe::TokenTree::Delimited(.., del) = args && let mbe::TokenTree::Delimited(.., del) = args
&& del.delim != Delimiter::Invisible && !del.delim.skip()
{ {
true true
} else { } else {

View File

@ -165,11 +165,12 @@ fn parse_tree<'a>(
// during parsing. // during parsing.
let mut next = outer_trees.next(); let mut next = outer_trees.next();
let mut trees: Box<dyn Iterator<Item = &tokenstream::TokenTree>>; let mut trees: Box<dyn Iterator<Item = &tokenstream::TokenTree>>;
if let Some(tokenstream::TokenTree::Delimited(.., Delimiter::Invisible, tts)) = next { match next {
trees = Box::new(tts.trees()); Some(tokenstream::TokenTree::Delimited(.., delim, tts)) if delim.skip() => {
next = trees.next(); trees = Box::new(tts.trees());
} else { next = trees.next();
trees = Box::new(outer_trees); }
_ => trees = Box::new(outer_trees),
} }
match next { match next {

View File

@ -38,7 +38,7 @@ impl FromInternal<token::Delimiter> for Delimiter {
token::Delimiter::Parenthesis => Delimiter::Parenthesis, token::Delimiter::Parenthesis => Delimiter::Parenthesis,
token::Delimiter::Brace => Delimiter::Brace, token::Delimiter::Brace => Delimiter::Brace,
token::Delimiter::Bracket => Delimiter::Bracket, token::Delimiter::Bracket => Delimiter::Bracket,
token::Delimiter::Invisible => Delimiter::None, token::Delimiter::Invisible(_) => Delimiter::None,
} }
} }
} }
@ -49,7 +49,7 @@ impl ToInternal<token::Delimiter> for Delimiter {
Delimiter::Parenthesis => token::Delimiter::Parenthesis, Delimiter::Parenthesis => token::Delimiter::Parenthesis,
Delimiter::Brace => token::Delimiter::Brace, Delimiter::Brace => token::Delimiter::Brace,
Delimiter::Bracket => token::Delimiter::Bracket, Delimiter::Bracket => token::Delimiter::Bracket,
Delimiter::None => token::Delimiter::Invisible, Delimiter::None => token::Delimiter::Invisible(token::InvisibleOrigin::ProcMacro),
} }
} }
} }

View File

@ -510,8 +510,8 @@ fn make_attr_token_stream(
FlatToken::Token((Token { kind: TokenKind::CloseDelim(delim), span }, spacing)) => { FlatToken::Token((Token { kind: TokenKind::CloseDelim(delim), span }, spacing)) => {
let frame_data = mem::replace(&mut stack_top, stack_rest.pop().unwrap()); let frame_data = mem::replace(&mut stack_top, stack_rest.pop().unwrap());
let (open_delim, open_sp, open_spacing) = frame_data.open_delim_sp.unwrap(); let (open_delim, open_sp, open_spacing) = frame_data.open_delim_sp.unwrap();
assert_eq!( assert!(
open_delim, delim, open_delim.eq_ignoring_invisible_origin(&delim),
"Mismatched open/close delims: open={open_delim:?} close={span:?}" "Mismatched open/close delims: open={open_delim:?} close={span:?}"
); );
let dspan = DelimSpan::from_pair(open_sp, span); let dspan = DelimSpan::from_pair(open_sp, span);

View File

@ -317,7 +317,7 @@ impl TokenCursor {
spacing, spacing,
delim, delim,
)); ));
if delim != Delimiter::Invisible { if !delim.skip() {
return (Token::new(token::OpenDelim(delim), sp.open), spacing.open); return (Token::new(token::OpenDelim(delim), sp.open), spacing.open);
} }
// No open delimiter to return; continue on to the next iteration. // No open delimiter to return; continue on to the next iteration.
@ -326,7 +326,7 @@ impl TokenCursor {
} else if let Some((tree_cursor, span, spacing, delim)) = self.stack.pop() { } else if let Some((tree_cursor, span, spacing, delim)) = self.stack.pop() {
// We have exhausted this token stream. Move back to its parent token stream. // We have exhausted this token stream. Move back to its parent token stream.
self.tree_cursor = tree_cursor; self.tree_cursor = tree_cursor;
if delim != Delimiter::Invisible { if !delim.skip() {
return (Token::new(token::CloseDelim(delim), span.close), spacing.close); return (Token::new(token::CloseDelim(delim), span.close), spacing.close);
} }
// No close delimiter to return; continue on to the next iteration. // No close delimiter to return; continue on to the next iteration.
@ -1163,7 +1163,7 @@ impl<'a> Parser<'a> {
} }
debug_assert!(!matches!( debug_assert!(!matches!(
next.0.kind, next.0.kind,
token::OpenDelim(Delimiter::Invisible) | token::CloseDelim(Delimiter::Invisible) token::OpenDelim(delim) | token::CloseDelim(delim) if delim.skip()
)); ));
self.inlined_bump_with(next) self.inlined_bump_with(next)
} }
@ -1187,7 +1187,7 @@ impl<'a> Parser<'a> {
match tree { match tree {
TokenTree::Token(token, _) => return looker(token), TokenTree::Token(token, _) => return looker(token),
&TokenTree::Delimited(dspan, _, delim, _) => { &TokenTree::Delimited(dspan, _, delim, _) => {
if delim != Delimiter::Invisible { if !delim.skip() {
return looker(&Token::new(token::OpenDelim(delim), dspan.open)); return looker(&Token::new(token::OpenDelim(delim), dspan.open));
} }
} }
@ -1197,7 +1197,7 @@ impl<'a> Parser<'a> {
// The tree cursor lookahead went (one) past the end of the // The tree cursor lookahead went (one) past the end of the
// current token tree. Try to return a close delimiter. // current token tree. Try to return a close delimiter.
if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last() if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last()
&& delim != Delimiter::Invisible && !delim.skip()
{ {
// We are not in the outermost token stream, so we have // We are not in the outermost token stream, so we have
// delimiters. Also, those delimiters are not skipped. // delimiters. Also, those delimiters are not skipped.
@ -1216,7 +1216,7 @@ impl<'a> Parser<'a> {
token = cursor.next().0; token = cursor.next().0;
if matches!( if matches!(
token.kind, token.kind,
token::OpenDelim(Delimiter::Invisible) | token::CloseDelim(Delimiter::Invisible) token::OpenDelim(delim) | token::CloseDelim(delim) if delim.skip()
) { ) {
continue; continue;
} }

View File

@ -620,7 +620,7 @@ fn delim_token_to_str(
("{ ", " }") ("{ ", " }")
} }
} }
Delimiter::Invisible => unreachable!(), Delimiter::Invisible(_) => unreachable!(),
}; };
if use_multiple_lines { if use_multiple_lines {
let indent_str = shape.indent.to_string_with_newline(context.config); let indent_str = shape.indent.to_string_with_newline(context.config);