diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index be97d4f17dba..a72ed842b8e3 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -329,6 +329,19 @@ fn print_crate_inner<'a>( /// - #63896: `#[allow(unused,` must be printed rather than `#[allow(unused ,` /// - #73345: `#[allow(unused)]` must be printed rather than `# [allow(unused)]` /// +/// Returns `true` if both token trees are identifier-like tokens that would +/// merge into a single token if printed without a space between them. +/// E.g. `ident` + `where` would merge into `identwhere`. +fn idents_would_merge(tt1: &TokenTree, tt2: &TokenTree) -> bool { + fn is_ident_like(tt: &TokenTree) -> bool { + matches!( + tt, + TokenTree::Token(Token { kind: token::Ident(..) | token::NtIdent(..), .. }, _,) + ) + } + is_ident_like(tt1) && is_ident_like(tt2) +} + fn space_between(tt1: &TokenTree, tt2: &TokenTree) -> bool { use Delimiter::*; use TokenTree::{Delimited as Del, Token as Tok}; @@ -811,6 +824,13 @@ fn print_tts(&mut self, tts: &TokenStream, convert_dollar_crate: bool) { if let Some(next) = iter.peek() { if spacing == Spacing::Alone && space_between(tt, next) { self.space(); + } else if spacing != Spacing::Alone && idents_would_merge(tt, next) { + // When tokens from macro `tt` captures preserve their + // original `Joint`/`JointHidden` spacing, adjacent + // identifier-like tokens can be concatenated without a + // space (e.g. `$x:identwhere`). Insert a space to + // prevent this. + self.space(); } } } diff --git a/tests/pretty/macro-fragment-specifier-whitespace.pp b/tests/pretty/macro-fragment-specifier-whitespace.pp new file mode 100644 index 000000000000..ee5a0f7a7c05 --- /dev/null +++ b/tests/pretty/macro-fragment-specifier-whitespace.pp @@ -0,0 +1,24 @@ +#![feature(prelude_import)] +#![no_std] +extern crate std; +#[prelude_import] +use ::std::prelude::rust_2015::*; +//@ pretty-mode:expanded +//@ pp-exact:macro-fragment-specifier-whitespace.pp + +// Test that fragment specifier names in macro definitions are properly +// separated from the following keyword/identifier token when pretty-printed. +// This is a regression test for a bug where `$x:ident` followed by `where` +// was pretty-printed as `$x:identwhere` (an invalid fragment specifier). + +macro_rules! outer { + ($d:tt $($params:tt)*) => + { + #[macro_export] macro_rules! inner + { ($($params)* where $d($rest:tt)*) => {}; } + }; +} +#[macro_export] +macro_rules! inner { ($x:ident where $ ($rest : tt)*) => {}; } + +fn main() {} diff --git a/tests/pretty/macro-fragment-specifier-whitespace.rs b/tests/pretty/macro-fragment-specifier-whitespace.rs new file mode 100644 index 000000000000..54c6debd9a27 --- /dev/null +++ b/tests/pretty/macro-fragment-specifier-whitespace.rs @@ -0,0 +1,19 @@ +//@ pretty-mode:expanded +//@ pp-exact:macro-fragment-specifier-whitespace.pp + +// Test that fragment specifier names in macro definitions are properly +// separated from the following keyword/identifier token when pretty-printed. +// This is a regression test for a bug where `$x:ident` followed by `where` +// was pretty-printed as `$x:identwhere` (an invalid fragment specifier). + +macro_rules! outer { + ($d:tt $($params:tt)*) => { + #[macro_export] + macro_rules! inner { + ($($params)* where $d($rest:tt)*) => {}; + } + }; +} +outer!($ $x:ident); + +fn main() {}