diff --git a/.worktrees/rust-1.90.0 b/.worktrees/rust-1.90.0 new file mode 160000 index 0000000000000..1159e78c4747b --- /dev/null +++ b/.worktrees/rust-1.90.0 @@ -0,0 +1 @@ +Subproject commit 1159e78c4747b02ef996e55082b704c09b970588 diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 6b220faa66ac4..62d5462cf0cd5 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -559,6 +559,8 @@ declare_features! ( (unstable, macro_metavar_expr, "1.61.0", Some(83527)), /// Provides a way to concatenate identifiers using metavariable expressions. (unstable, macro_metavar_expr_concat, "1.81.0", Some(124225)), + /// Allows map-like collection literals of the form `{ k1, v1, k2, v2, ... }`. + (unstable, map_literals, "1.90.0", None), /// Allows `#[marker]` on certain traits allowing overlapping implementations. (unstable, marker_trait_attr, "1.30.0", Some(29864)), /// Enables the generic const args MVP (only bare paths, not arbitrary computation). diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 35b987cf50fa2..f7fc0ec7ec069 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1454,7 +1454,11 @@ impl<'a> Parser<'a> { } else if this.check(exp!(OpenParen)) { this.parse_expr_tuple_parens(restrictions) } else if this.check(exp!(OpenBrace)) { - this.parse_expr_block(None, lo, BlockCheckMode::Default) + if let Some(expr) = this.maybe_parse_map_literal(lo)? { + Ok(expr) + } else { + this.parse_expr_block(None, lo, BlockCheckMode::Default) + } } else if this.check(exp!(Or)) || this.check(exp!(OrOr)) { this.parse_expr_closure().map_err(|mut err| { // If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }` @@ -1556,6 +1560,139 @@ impl<'a> Parser<'a> { }) } + /// Tries to parse a map-like collection literal: + /// + /// ```text + /// { k1, v1, k2, v2, ... } + /// ``` + /// + /// This is intentionally "opt-in" (feature-gated) syntax sugar aimed at + /// simplifying map initialization. To avoid breaking existing code, we only + /// commit to this parse if the first expression is immediately followed by + /// a top-level comma. Otherwise, we restore the snapshot and let normal + /// block parsing handle `{ ... }`. + fn maybe_parse_map_literal(&mut self, lo: Span) -> PResult<'a, Option
>> {
+ // We only attempt this from the `{` branch in `parse_expr_bottom`.
+ debug_assert!(self.check_noexpect(&token::OpenBrace));
+
+ // Probe in a snapshot first: don't emit errors for normal blocks like `{ let x = 1; ... }`.
+ // We only commit to this syntax if the first expression is followed by a top-level comma.
+ let snapshot = self.create_snapshot_for_diagnostic();
+ self.bump(); // `{`
+
+ // `{}` remains an empty block (unit), not an empty map literal.
+ if self.check(exp!(CloseBrace)) {
+ self.restore_snapshot(snapshot);
+ return Ok(None);
+ }
+
+ // Avoid spurious diagnostics for normal blocks that start with a `let` statement
+ // (e.g. `{ let x = 1; x }`), which `parse_expr()` would otherwise treat as a
+ // restricted `let`-expression and eagerly emit an error.
+ if self.token.is_keyword(kw::Let) {
+ self.restore_snapshot(snapshot);
+ return Ok(None);
+ }
+
+ // `use`/`static` at the start of a block are usually items/statements; probing with
+ // `parse_expr()` would misinterpret them as closure qualifiers and can emit spurious
+ // feature-gate diagnostics.
+ if self.token.is_keyword(kw::Use) || self.token.is_keyword(kw::Static) {
+ self.restore_snapshot(snapshot);
+ return Ok(None);
+ }
+
+ // Likewise, blocks can start with attributes (`#![...]` or `#[...]`).
+ // Those are valid in blocks/macros, but we intentionally do not support them as the
+ // first token inside a map literal.
+ if self.token == token::Pound {
+ self.restore_snapshot(snapshot);
+ return Ok(None);
+ }
+
+ let first_key = match self.parse_expr() {
+ Ok(e) => e,
+ Err(err) => {
+ err.cancel();
+ self.restore_snapshot(snapshot);
+ return Ok(None);
+ }
+ };
+ if !self.eat(exp!(Comma)) {
+ self.restore_snapshot(snapshot);
+ return Ok(None);
+ }
+
+ // It's a map literal; restore and parse for real.
+ drop(first_key);
+ self.restore_snapshot(snapshot);
+ self.parse_map_literal_expr(lo).map(Some)
+ }
+
+ fn parse_map_literal_expr(&mut self, lo: Span) -> PResult<'a, P > = pairs
+ .into_iter()
+ .map(|(k, v)| self.mk_expr(k.span.to(v.span), ExprKind::Tup(thin_vec![k, v])))
+ .collect();
+ let array_expr = self.mk_expr(span, ExprKind::Array(tuple_exprs));
+ Ok(self.mk_expr(span, ExprKind::Call(fun, thin_vec![array_expr])))
+ }
+
fn parse_expr_lit(&mut self) -> PResult<'a, P