Merge pull request #19533 from Veykril/push-zxlpwkvzxzws

chore: Set up a proper job matrix for rust-cross
diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs
index 83857cf..9fbeace 100644
--- a/crates/base-db/src/lib.rs
+++ b/crates/base-db/src/lib.rs
@@ -303,6 +303,19 @@
     pub toolchain: Option<Version>,
 }
 
+impl CrateWorkspaceData {
+    pub fn is_atleast_187(&self) -> bool {
+        const VERSION_187: Version = Version {
+            major: 1,
+            minor: 87,
+            patch: 0,
+            pre: Prerelease::EMPTY,
+            build: BuildMetadata::EMPTY,
+        };
+        self.toolchain.as_ref().map_or(false, |v| *v >= VERSION_187)
+    }
+}
+
 fn toolchain_channel(db: &dyn RootQueryDb, krate: Crate) -> Option<ReleaseChannel> {
     krate.workspace_data(db).toolchain.as_ref().and_then(|v| ReleaseChannel::from_str(&v.pre))
 }
diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs
index 2d6e8f0..1791a1c 100644
--- a/crates/hir-def/src/expr_store/lower.rs
+++ b/crates/hir-def/src/expr_store/lower.rs
@@ -2321,54 +2321,99 @@
             zero_pad,
             debug_hex,
         } = &placeholder.format_options;
-        let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' '))));
 
-        let align = {
-            let align = LangItem::FormatAlignment.ty_rel_path(
-                self.db,
-                self.krate,
-                match alignment {
-                    Some(FormatAlignment::Left) => Name::new_symbol_root(sym::Left.clone()),
-                    Some(FormatAlignment::Right) => Name::new_symbol_root(sym::Right.clone()),
-                    Some(FormatAlignment::Center) => Name::new_symbol_root(sym::Center.clone()),
-                    None => Name::new_symbol_root(sym::Unknown.clone()),
-                },
-            );
-            match align {
-                Some(path) => self.alloc_expr_desugared(Expr::Path(path)),
-                None => self.missing_expr(),
-            }
-        };
-        // This needs to match `Flag` in library/core/src/fmt/rt.rs.
-        let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32)
-            | (((sign == Some(FormatSign::Minus)) as u32) << 1)
-            | ((alternate as u32) << 2)
-            | ((zero_pad as u32) << 3)
-            | (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4)
-            | (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5);
-        let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
-            flags as u128,
-            Some(BuiltinUint::U32),
-        )));
-        let precision = self.make_count(precision, argmap);
-        let width = self.make_count(width, argmap);
+        let precision_expr = self.make_count(precision, argmap);
+        let width_expr = self.make_count(width, argmap);
 
-        let format_placeholder_new = {
-            let format_placeholder_new = LangItem::FormatPlaceholder.ty_rel_path(
-                self.db,
-                self.krate,
-                Name::new_symbol_root(sym::new.clone()),
-            );
-            match format_placeholder_new {
-                Some(path) => self.alloc_expr_desugared(Expr::Path(path)),
-                None => self.missing_expr(),
-            }
-        };
+        if self.krate.workspace_data(self.db).is_atleast_187() {
+            // These need to match the constants in library/core/src/fmt/rt.rs.
+            let align = match alignment {
+                Some(FormatAlignment::Left) => 0,
+                Some(FormatAlignment::Right) => 1,
+                Some(FormatAlignment::Center) => 2,
+                None => 3,
+            };
+            // This needs to match `Flag` in library/core/src/fmt/rt.rs.
+            let flags = fill.unwrap_or(' ') as u32
+                | ((sign == Some(FormatSign::Plus)) as u32) << 21
+                | ((sign == Some(FormatSign::Minus)) as u32) << 22
+                | (alternate as u32) << 23
+                | (zero_pad as u32) << 24
+                | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25
+                | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26
+                | (width.is_some() as u32) << 27
+                | (precision.is_some() as u32) << 28
+                | align << 29
+                | 1 << 31; // Highest bit always set.
+            let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
+                flags as u128,
+                Some(BuiltinUint::U32),
+            )));
 
-        self.alloc_expr_desugared(Expr::Call {
-            callee: format_placeholder_new,
-            args: Box::new([position, fill, align, flags, precision, width]),
-        })
+            let position = RecordLitField {
+                name: Name::new_symbol_root(sym::position.clone()),
+                expr: position,
+            };
+            let flags =
+                RecordLitField { name: Name::new_symbol_root(sym::flags.clone()), expr: flags };
+            let precision = RecordLitField {
+                name: Name::new_symbol_root(sym::precision.clone()),
+                expr: precision_expr,
+            };
+            let width = RecordLitField {
+                name: Name::new_symbol_root(sym::width.clone()),
+                expr: width_expr,
+            };
+            self.alloc_expr_desugared(Expr::RecordLit {
+                path: LangItem::FormatPlaceholder.path(self.db, self.krate).map(Box::new),
+                fields: Box::new([position, flags, precision, width]),
+                spread: None,
+            })
+        } else {
+            let format_placeholder_new = {
+                let format_placeholder_new = LangItem::FormatPlaceholder.ty_rel_path(
+                    self.db,
+                    self.krate,
+                    Name::new_symbol_root(sym::new.clone()),
+                );
+                match format_placeholder_new {
+                    Some(path) => self.alloc_expr_desugared(Expr::Path(path)),
+                    None => self.missing_expr(),
+                }
+            };
+            // This needs to match `Flag` in library/core/src/fmt/rt.rs.
+            let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32)
+                | (((sign == Some(FormatSign::Minus)) as u32) << 1)
+                | ((alternate as u32) << 2)
+                | ((zero_pad as u32) << 3)
+                | (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4)
+                | (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5);
+            let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
+                flags as u128,
+                Some(BuiltinUint::U32),
+            )));
+            let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' '))));
+            let align = {
+                let align = LangItem::FormatAlignment.ty_rel_path(
+                    self.db,
+                    self.krate,
+                    match alignment {
+                        Some(FormatAlignment::Left) => Name::new_symbol_root(sym::Left.clone()),
+                        Some(FormatAlignment::Right) => Name::new_symbol_root(sym::Right.clone()),
+                        Some(FormatAlignment::Center) => Name::new_symbol_root(sym::Center.clone()),
+                        None => Name::new_symbol_root(sym::Unknown.clone()),
+                    },
+                );
+                match align {
+                    Some(path) => self.alloc_expr_desugared(Expr::Path(path)),
+                    None => self.missing_expr(),
+                }
+            };
+            self.alloc_expr_desugared(Expr::Call {
+                callee: format_placeholder_new,
+                args: Box::new([position, fill, align, flags, precision_expr, width_expr]),
+            })
+        }
     }
 
     /// Generate a hir expression for a format_args Count.
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs
index ddf1a21..f990309 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs
@@ -1979,3 +1979,51 @@
 "#]],
     );
 }
+
+#[test]
+fn semicolon_does_not_glue() {
+    check(
+        r#"
+macro_rules! bug {
+    ($id: expr) => {
+        true
+    };
+    ($id: expr; $($attr: ident),*) => {
+        true
+    };
+    ($id: expr; $($attr: ident),*; $norm: expr) => {
+        true
+    };
+    ($id: expr; $($attr: ident),*;; $print: expr) => {
+        true
+    };
+    ($id: expr; $($attr: ident),*; $norm: expr; $print: expr) => {
+        true
+    };
+}
+
+let _ = bug!(a;;;test);
+    "#,
+        expect![[r#"
+macro_rules! bug {
+    ($id: expr) => {
+        true
+    };
+    ($id: expr; $($attr: ident),*) => {
+        true
+    };
+    ($id: expr; $($attr: ident),*; $norm: expr) => {
+        true
+    };
+    ($id: expr; $($attr: ident),*;; $print: expr) => {
+        true
+    };
+    ($id: expr; $($attr: ident),*; $norm: expr; $print: expr) => {
+        true
+    };
+}
+
+let _ = true;
+    "#]],
+    );
+}
diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
index 1bbed01..cb4fcd8 100644
--- a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
@@ -582,8 +582,8 @@
 }
 
 impl <A: Arbitrary> $crate::arbitrary::Arbitrary for Vec<A> {
-    type Parameters = RangedParams1<A::Parameters>;
-    type Strategy = VecStrategy<A::Strategy>;
+    type Parameters = RangedParams1<A::Parameters> ;
+    type Strategy = VecStrategy<A::Strategy> ;
     fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { {
             let product_unpack![range, a] = args;
             vec(any_with::<A>(a), range)
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 0bd605c..0448ecd 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -1534,10 +1534,6 @@
                 None => return (self.err_ty(), None),
             }
         };
-        let Some(mod_path) = path.mod_path() else {
-            never!("resolver should always resolve lang item paths");
-            return (self.err_ty(), None);
-        };
         return match resolution {
             TypeNs::AdtId(AdtId::StructId(strukt)) => {
                 let substs = path_ctx.substs_from_path(strukt.into(), true);
@@ -1567,6 +1563,10 @@
 
                 let Some(remaining_idx) = unresolved else {
                     drop(ctx);
+                    let Some(mod_path) = path.mod_path() else {
+                        never!("resolver should always resolve lang item paths");
+                        return (self.err_ty(), None);
+                    };
                     return self.resolve_variant_on_alias(ty, None, mod_path);
                 };
 
@@ -1630,6 +1630,10 @@
                 (ty, variant)
             }
             TypeNs::TypeAliasId(it) => {
+                let Some(mod_path) = path.mod_path() else {
+                    never!("resolver should always resolve lang item paths");
+                    return (self.err_ty(), None);
+                };
                 let substs = path_ctx.substs_from_path_segment(it.into(), true, None);
                 drop(ctx);
                 let ty = self.db.ty(it.into());
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index afa1f6d..651ec15 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -570,10 +570,17 @@
         source_map: &hir_def::expr_store::BodySourceMap,
     ) -> Option<AnyDiagnostic> {
         let expr_syntax = |expr| {
-            source_map.expr_syntax(expr).inspect_err(|_| stdx::never!("synthetic syntax")).ok()
+            source_map
+                .expr_syntax(expr)
+                .inspect_err(|_| stdx::never!("inference diagnostic in desugared expr"))
+                .ok()
         };
-        let pat_syntax =
-            |pat| source_map.pat_syntax(pat).inspect_err(|_| stdx::never!("synthetic syntax")).ok();
+        let pat_syntax = |pat| {
+            source_map
+                .pat_syntax(pat)
+                .inspect_err(|_| stdx::never!("inference diagnostic in desugared pattern"))
+                .ok()
+        };
         let expr_or_pat_syntax = |id| match id {
             ExprOrPatId::ExprId(expr) => expr_syntax(expr),
             ExprOrPatId::PatId(pat) => pat_syntax(pat),
diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs
index 4ae00cc..58e7f58 100644
--- a/crates/ide-completion/src/completions/item_list.rs
+++ b/crates/ide-completion/src/completions/item_list.rs
@@ -114,6 +114,7 @@
             add_keyword("trait", "trait $1 {\n    $0\n}");
             if no_vis_qualifiers {
                 add_keyword("impl", "impl $1 {\n    $0\n}");
+                add_keyword("impl for", "impl $1 for $2 {\n    $0\n}");
             }
         }
 
@@ -144,6 +145,7 @@
         add_keyword("use", "use $0");
         if no_vis_qualifiers {
             add_keyword("impl", "impl $1 {\n    $0\n}");
+            add_keyword("impl for", "impl $1 for $2 {\n    $0\n}");
         }
     }
 
diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs
index 14b0d54..0397424 100644
--- a/crates/ide-completion/src/completions/keyword.rs
+++ b/crates/ide-completion/src/completions/keyword.rs
@@ -56,6 +56,7 @@
                 kw extern
                 kw fn
                 kw impl
+                kw impl for
                 kw trait
             "#]],
         );
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index 98da2cb..b30ac43 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -171,6 +171,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -249,6 +250,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -300,6 +302,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -375,6 +378,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -961,6 +965,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1003,6 +1008,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1095,6 +1101,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1137,6 +1144,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1179,6 +1187,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1231,6 +1240,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1285,6 +1295,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1529,6 +1540,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -2001,6 +2013,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -2073,6 +2086,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
diff --git a/crates/ide-completion/src/tests/item.rs b/crates/ide-completion/src/tests/item.rs
index be2c37d..5568903 100644
--- a/crates/ide-completion/src/tests/item.rs
+++ b/crates/ide-completion/src/tests/item.rs
@@ -284,6 +284,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs
index 841c421..fcdf10c 100644
--- a/crates/ide-completion/src/tests/item_list.rs
+++ b/crates/ide-completion/src/tests/item_list.rs
@@ -16,6 +16,7 @@
             kw extern
             kw fn
             kw impl
+            kw impl for
             kw mod
             kw pub
             kw pub(crate)
@@ -50,6 +51,7 @@
             kw extern
             kw fn
             kw impl
+            kw impl for
             kw mod
             kw pub
             kw pub(crate)
@@ -83,6 +85,7 @@
             kw extern
             kw fn
             kw impl
+            kw impl for
             kw mod
             kw pub
             kw pub(crate)
@@ -122,6 +125,7 @@
             kw extern
             kw fn
             kw impl
+            kw impl for
             kw trait
         "#]],
     );
@@ -385,6 +389,7 @@
             kw extern
             kw fn
             kw impl
+            kw impl for
             kw mod
             kw pub
             kw pub(crate)
diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs
index 70caeac..15518e9 100644
--- a/crates/ide-completion/src/tests/special.rs
+++ b/crates/ide-completion/src/tests/special.rs
@@ -1008,6 +1008,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1059,6 +1060,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1184,6 +1186,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
@@ -1441,6 +1444,7 @@
             kw if
             kw if let
             kw impl
+            kw impl for
             kw let
             kw letm
             kw loop
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index a01afd2..258d80e 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -627,7 +627,7 @@
             return Some((def, file, Some(format!("variant.{}", ev.name(db).as_str()))));
         }
         Definition::Const(c) => {
-            format!("const.{}.html", c.name(db)?.as_str())
+            format!("constant.{}.html", c.name(db)?.as_str())
         }
         Definition::Static(s) => {
             format!("static.{}.html", s.name(db).as_str())
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 4811f1f..7766897 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -677,4 +677,26 @@
 crate::Foo;"#]],
         );
     }
+
+    #[test]
+    fn semi_glueing() {
+        check(
+            r#"
+macro_rules! __log_value {
+    ($key:ident :$capture:tt =) => {};
+}
+
+macro_rules! __log {
+    ($key:tt $(:$capture:tt)? $(= $value:expr)?; $($arg:tt)+) => {
+        __log_value!($key $(:$capture)* = $($value)*);
+    };
+}
+
+__log!(written:%; "Test"$0);
+    "#,
+            expect![[r#"
+                __log!
+            "#]],
+        );
+    }
 }
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 7d855cf..13b161e 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -753,7 +753,7 @@
         frange: FileRange,
     ) -> Cancellable<Vec<Assist>> {
         let include_fixes = match &assist_config.allowed {
-            Some(it) => it.iter().any(|&it| it == AssistKind::QuickFix),
+            Some(it) => it.contains(&AssistKind::QuickFix),
             None => true,
         };
 
diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs
index 4841f48..a9ed185 100644
--- a/crates/intern/src/symbol/symbols.rs
+++ b/crates/intern/src/symbol/symbols.rs
@@ -161,6 +161,7 @@
     bitxor_assign,
     bitxor,
     bool,
+    bootstrap,
     box_free,
     Box,
     boxed,
@@ -525,4 +526,8 @@
     ignore_flyimport,
     ignore_flyimport_methods,
     ignore_methods,
+    position,
+    flags,
+    precision,
+    width,
 }
diff --git a/crates/mbe/src/expander/transcriber.rs b/crates/mbe/src/expander/transcriber.rs
index b1f542e..f3f9f29 100644
--- a/crates/mbe/src/expander/transcriber.rs
+++ b/crates/mbe/src/expander/transcriber.rs
@@ -389,8 +389,13 @@
     match ctx.bindings.get_fragment(v, id, &mut ctx.nesting, marker) {
         Ok(fragment) => {
             match fragment {
-                Fragment::Tokens(tt) => builder.extend_with_tt(tt.strip_invisible()),
-                Fragment::TokensOwned(tt) => builder.extend_with_tt(tt.view().strip_invisible()),
+                // rustc spacing is not like ours. Ours is like proc macros', it dictates how puncts will actually be joined.
+                // rustc uses them mostly for pretty printing. So we have to deviate a bit from what rustc does here.
+                // Basically, a metavariable can never be joined with whatever after it.
+                Fragment::Tokens(tt) => builder.extend_with_tt_alone(tt.strip_invisible()),
+                Fragment::TokensOwned(tt) => {
+                    builder.extend_with_tt_alone(tt.view().strip_invisible())
+                }
                 Fragment::Expr(sub) => {
                     let sub = sub.strip_invisible();
                     let mut span = id;
@@ -402,7 +407,7 @@
                     if wrap_in_parens {
                         builder.open(tt::DelimiterKind::Parenthesis, span);
                     }
-                    builder.extend_with_tt(sub);
+                    builder.extend_with_tt_alone(sub);
                     if wrap_in_parens {
                         builder.close(span);
                     }
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index 7be49cb..8a2f124 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -6,7 +6,10 @@
 use arrayvec::ArrayVec;
 use intern::{Symbol, sym};
 use span::{Edition, Span, SyntaxContext};
-use tt::iter::{TtElement, TtIter};
+use tt::{
+    MAX_GLUED_PUNCT_LEN,
+    iter::{TtElement, TtIter},
+};
 
 use crate::ParseError;
 
@@ -96,7 +99,7 @@
         delimiter: tt::Delimiter<Span>,
     },
     Literal(tt::Literal<Span>),
-    Punct(Box<ArrayVec<tt::Punct<Span>, 3>>),
+    Punct(Box<ArrayVec<tt::Punct<Span>, MAX_GLUED_PUNCT_LEN>>),
     Ident(tt::Ident<Span>),
 }
 
@@ -151,7 +154,7 @@
 pub(crate) enum Separator {
     Literal(tt::Literal<Span>),
     Ident(tt::Ident<Span>),
-    Puncts(ArrayVec<tt::Punct<Span>, 3>),
+    Puncts(ArrayVec<tt::Punct<Span>, MAX_GLUED_PUNCT_LEN>),
 }
 
 // Note that when we compare a Separator, we just care about its textual value.
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index a0fb4a7..7a139ea 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -1664,6 +1664,7 @@
                         vec![
                             CfgAtom::Flag(sym::debug_assertions.clone()),
                             CfgAtom::Flag(sym::miri.clone()),
+                            CfgAtom::Flag(sym::bootstrap.clone()),
                         ],
                         vec![CfgAtom::Flag(sym::test.clone())],
                     ),
diff --git a/crates/tt/src/iter.rs b/crates/tt/src/iter.rs
index 1d88218..0418c00 100644
--- a/crates/tt/src/iter.rs
+++ b/crates/tt/src/iter.rs
@@ -6,7 +6,7 @@
 use arrayvec::ArrayVec;
 use intern::sym;
 
-use crate::{Ident, Leaf, Punct, Spacing, Subtree, TokenTree, TokenTreesView};
+use crate::{Ident, Leaf, MAX_GLUED_PUNCT_LEN, Punct, Spacing, Subtree, TokenTree, TokenTreesView};
 
 #[derive(Clone)]
 pub struct TtIter<'a, S> {
@@ -111,7 +111,7 @@
     ///
     /// This method currently may return a single quotation, which is part of lifetime ident and
     /// conceptually not a punct in the context of mbe. Callers should handle this.
-    pub fn expect_glued_punct(&mut self) -> Result<ArrayVec<Punct<S>, 3>, ()> {
+    pub fn expect_glued_punct(&mut self) -> Result<ArrayVec<Punct<S>, MAX_GLUED_PUNCT_LEN>, ()> {
         let TtElement::Leaf(&Leaf::Punct(first)) = self.next().ok_or(())? else {
             return Err(());
         };
@@ -145,7 +145,6 @@
             }
             ('-' | '!' | '*' | '/' | '&' | '%' | '^' | '+' | '<' | '=' | '>' | '|', '=', _)
             | ('-' | '=' | '>', '>', _)
-            | (_, _, Some(';'))
             | ('<', '-', _)
             | (':', ':', _)
             | ('.', '.', _)
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs
index 916e00b..36ccb67 100644
--- a/crates/tt/src/lib.rs
+++ b/crates/tt/src/lib.rs
@@ -22,6 +22,8 @@
 
 pub use text_size::{TextRange, TextSize};
 
+pub const MAX_GLUED_PUNCT_LEN: usize = 3;
+
 #[derive(Clone, PartialEq, Debug)]
 pub struct Lit {
     pub kind: LitKind,
@@ -243,6 +245,23 @@
         self.token_trees.extend(tt.0.iter().cloned());
     }
 
+    /// Like [`Self::extend_with_tt()`], but makes sure the new tokens will never be
+    /// joint with whatever comes after them.
+    pub fn extend_with_tt_alone(&mut self, tt: TokenTreesView<'_, S>) {
+        if let Some((last, before_last)) = tt.0.split_last() {
+            self.token_trees.reserve(tt.0.len());
+            self.token_trees.extend(before_last.iter().cloned());
+            let last = if let TokenTree::Leaf(Leaf::Punct(last)) = last {
+                let mut last = *last;
+                last.spacing = Spacing::Alone;
+                TokenTree::Leaf(Leaf::Punct(last))
+            } else {
+                last.clone()
+            };
+            self.token_trees.push(last);
+        }
+    }
+
     pub fn expected_delimiters(&self) -> impl Iterator<Item = &Delimiter<S>> {
         self.unclosed_subtree_indices.iter().rev().map(|&subtree_idx| {
             let TokenTree::Subtree(subtree) = &self.token_trees[subtree_idx] else {