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> { + debug_assert!(self.check_noexpect(&token::OpenBrace)); + + self.bump(); // `{` + + let first_key = self.parse_expr()?; + self.expect(exp!(Comma))?; + + // We have `{ key,` so this is unambiguously the new syntax. + let mut pairs: ThinVec<(P, P)> = ThinVec::new(); + + let mut key = first_key; + loop { + let value = self.parse_expr()?; + pairs.push((key, value)); + + // End of literal: `{ k, v }` + if self.eat(exp!(CloseBrace)) { + break; + } + + // Separator between pairs. + self.expect(exp!(Comma))?; + + // Trailing comma: `{ k, v, }` + if self.eat(exp!(CloseBrace)) { + break; + } + + // Next key/value pair. + key = self.parse_expr()?; + self.expect(exp!(Comma))?; + } + + let span = lo.to(self.prev_token.span); + self.psess.gated_spans.gate(sym::map_literals, span); + + // Desugar `{ k1, v1, ... }` to: + // `::core::iter::FromIterator::from_iter([ (k1, v1), ... ])` + // + // This keeps evaluation order (left-to-right) and relies on `FromIterator` + // for the target type, similar to `.collect()`. + let seg = |s: &str| PathSegment::from_ident(Ident::new(Symbol::intern(s), span)); + let from_iter_path = Path { + span, + segments: thin_vec![ + PathSegment::path_root(span), + seg("core"), + seg("iter"), + seg("FromIterator"), + seg("from_iter"), + ], + tokens: None, + }; + let fun = self.mk_expr(span, ExprKind::Path(None, from_iter_path)); + + let tuple_exprs: ThinVec> = 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> { let lo = self.token.span; match self.parse_opt_token_lit() { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d54175548e30e..cdfc7ebc2fbad 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1327,6 +1327,7 @@ symbols! { manually_drop, map, map_err, + map_literals, marker, marker_trait_attr, masked, diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index f5fa33b98f3bf..8b6ce594cd77c 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -1273,17 +1273,33 @@ impl Step for LibcxxVersionTool { t!(fs::create_dir_all(&out_dir)); } - let compiler = builder.cxx(self.target).unwrap(); - let mut cmd = command(compiler); - - cmd.arg("-o") - .arg(&executable) - .arg(builder.src.join("src/tools/libcxx-version/main.cpp")); + // Prefer the configured C++ compiler, but fall back to `g++`/`c++` when the + // configured compiler can't locate the system's C++ headers (common in minimal + // environments). + let mut compilers = vec![builder.cxx(self.target).unwrap()]; + if cfg!(unix) { + compilers.push("g++".into()); + compilers.push("c++".into()); + } - cmd.run(builder); + let mut built = false; + for compiler in compilers { + let mut cmd = command(&compiler); + cmd.arg("-o") + .arg(&executable) + .arg(builder.src.join("src/tools/libcxx-version/main.cpp")); + + // Don't abort the whole bootstrap on the first compiler failure; we may have a + // working fallback. + let mut cmd = cmd.allow_failure(); + if cmd.run(builder) && executable.exists() { + built = true; + break; + } + } - if !executable.exists() { - panic!("Something went wrong. {} is not present", executable.display()); + if !built { + panic!("failed to build {} with any C++ compiler", executable.display()); } } diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 6b3236ef47ef6..718b94531838c 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -113,7 +113,11 @@ impl Cargo { match cmd_kind { // No need to configure the target linker for these command types. - Kind::Clean | Kind::Check | Kind::Format | Kind::Setup => {} + // + // Note: `check` still needs the C/C++ toolchain environment variables (CC/CXX, + // CFLAGS/CXXFLAGS, etc.) for build scripts such as `rustc_llvm`, so we do configure + // the linker for `check`. + Kind::Clean | Kind::Format | Kind::Setup => {} _ => { cargo.configure_linker(builder, mode); } diff --git a/tests/ui/feature-gates/feature-gate-map_literals.rs b/tests/ui/feature-gates/feature-gate-map_literals.rs new file mode 100644 index 0000000000000..5853f67fadfcf --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-map_literals.rs @@ -0,0 +1,7 @@ +use std::collections::HashMap; + +fn main() { + let _map: HashMap<&str, i32> = { "a", 1 }; + //~^ ERROR is experimental +} + diff --git a/tests/ui/map-literals/basic.rs b/tests/ui/map-literals/basic.rs new file mode 100644 index 0000000000000..842238d34ad8e --- /dev/null +++ b/tests/ui/map-literals/basic.rs @@ -0,0 +1,18 @@ +//@ check-pass + +#![feature(map_literals)] + +use std::collections::{BTreeMap, HashMap}; + +fn main() { + let map: HashMap<&str, i32> = { "a", 25, "b", 24, "c", 12 }; + assert_eq!(map.get("a"), Some(&25)); + assert_eq!(map.get("b"), Some(&24)); + assert_eq!(map.get("c"), Some(&12)); + + let bmap: BTreeMap = { 1, 10, 2, 20, 3, 30, }; + assert_eq!(bmap.get(&1), Some(&10)); + assert_eq!(bmap.get(&2), Some(&20)); + assert_eq!(bmap.get(&3), Some(&30)); +} +