Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .worktrees/rust-1.90.0
Submodule rust-1.90.0 added at 1159e7
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
139 changes: 138 additions & 1 deletion compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }`
Expand Down Expand Up @@ -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<P<Expr>>> {
// 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<Expr>> {
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<Expr>, P<Expr>)> = 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<P<Expr>> = 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<Expr>> {
let lo = self.token.span;
match self.parse_opt_token_lit() {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,7 @@ symbols! {
manually_drop,
map,
map_err,
map_literals,
marker,
marker_trait_attr,
masked,
Expand Down
34 changes: 25 additions & 9 deletions src/bootstrap/src/core/build_steps/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/bootstrap/src/core/builder/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
7 changes: 7 additions & 0 deletions tests/ui/feature-gates/feature-gate-map_literals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use std::collections::HashMap;

fn main() {
let _map: HashMap<&str, i32> = { "a", 1 };
//~^ ERROR is experimental
}

18 changes: 18 additions & 0 deletions tests/ui/map-literals/basic.rs
Original file line number Diff line number Diff line change
@@ -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<i32, i32> = { 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));
}