diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 662357ce88413..af5357c3d6509 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -469,6 +469,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { "`if let` guards are experimental", "you can write `if matches!(, )` instead of `if let = `" ); + gate_all!(impl_shards, "`impl` shards are experimental"); gate_all!( async_trait_bounds, "`async` trait bounds are unstable", diff --git a/compiler/rustc_ast_passes/src/impl_shards.rs b/compiler/rustc_ast_passes/src/impl_shards.rs new file mode 100644 index 0000000000000..af72d92a81612 --- /dev/null +++ b/compiler/rustc_ast_passes/src/impl_shards.rs @@ -0,0 +1,194 @@ +//! Support for parsing and merging "impl shards". +//! +//! The parser accepts a gated "single associated item" impl form: +//! +//! ```ignore (illustrative) +//! impl Trait for Type fn method(...) { ... } +//! ``` +//! +//! This module merges such shard impls (and only within the same module) into a single +//! `impl Trait for Type { ... }` before name resolution and lowering. + +use rustc_ast as ast; +use rustc_ast::attr; +use rustc_ast::ptr::P; +use rustc_ast_pretty::pprust; +use rustc_data_structures::fx::FxHashMap; +use rustc_span::sym; +use thin_vec::ThinVec; + +pub fn merge_impl_shards(krate: &mut ast::Crate) { + merge_in_items(&mut krate.items); +} + +fn merge_in_items(items: &mut ThinVec>) { + // Recurse first so each module is handled independently. + for item in items.iter_mut() { + recurse_into_modules(item); + } + + let old_items = std::mem::take(items); + let mut new_items: ThinVec> = ThinVec::new(); + let mut first_impl_for_key: FxHashMap = FxHashMap::default(); + + for item in old_items { + let Some(key) = shard_key(&item) else { + new_items.push(item); + continue; + }; + + if let Some(&idx) = first_impl_for_key.get(&key) { + // Merge this shard's items into the first impl with the same header. + let shard_items = extract_impl_items(item); + if let ast::ItemKind::Impl(impl_box) = &mut new_items[idx].kind { + impl_box.items.extend(shard_items.into_iter()); + } + } else { + first_impl_for_key.insert(key, new_items.len()); + new_items.push(item); + } + } + + *items = new_items; +} + +fn recurse_into_modules(item: &mut P) { + if let ast::ItemKind::Mod(_, _, ast::ModKind::Loaded(items, _, _, _)) = &mut item.kind { + merge_in_items(items); + } +} + +fn shard_key(item: &ast::Item) -> Option { + if !attr::contains_name(&item.attrs, sym::rustc_impl_shard) { + return None; + } + let ast::ItemKind::Impl(impl_box) = &item.kind else { + return None; + }; + let imp = &**impl_box; + let trait_ref = imp.of_trait.as_ref()?; + + Some(impl_header_key(imp, trait_ref)) +} + +fn impl_header_key(imp: &ast::Impl, trait_ref: &ast::TraitRef) -> String { + let mut s = String::new(); + + // Header modifiers. + if let ast::Safety::Unsafe(_) = imp.safety { + s.push_str("unsafe "); + } + if let ast::Defaultness::Default(_) = imp.defaultness { + s.push_str("default "); + } + if let ast::Const::Yes(_) = imp.constness { + s.push_str("const "); + } + if let ast::ImplPolarity::Negative(_) = imp.polarity { + s.push('!'); + } + + // Trait + self type. + s.push_str(&pprust::path_to_string(&trait_ref.path)); + s.push_str(" for "); + s.push_str(&pprust::ty_to_string(&imp.self_ty)); + + // Generics + where-clause. + s.push_str(&generics_key(&imp.generics)); + s +} + +fn extract_impl_items(item: P) -> ThinVec> { + let ast::Item { kind, .. } = *item; + let ast::ItemKind::Impl(impl_box) = kind else { + return ThinVec::new(); + }; + impl_box.items +} + +fn generics_key(generics: &ast::Generics) -> String { + let mut out = String::new(); + + if !generics.params.is_empty() { + out.push('<'); + for (i, p) in generics.params.iter().enumerate() { + if i != 0 { + out.push_str(", "); + } + out.push_str(&generic_param_key(p)); + } + out.push('>'); + } + + if generics.where_clause.has_where_token || !generics.where_clause.predicates.is_empty() { + out.push_str(" where "); + for (i, pred) in generics.where_clause.predicates.iter().enumerate() { + if i != 0 { + out.push_str(", "); + } + out.push_str(&where_predicate_key(pred)); + } + } + + out +} + +fn generic_param_key(param: &ast::GenericParam) -> String { + let mut s = String::new(); + match ¶m.kind { + ast::GenericParamKind::Lifetime => { + s.push_str(¶m.ident.to_string()); + if !param.bounds.is_empty() { + // `bounds_to_string` prints trait bounds; lifetime bounds are also representable. + s.push_str(": "); + s.push_str(&pprust::bounds_to_string(¶m.bounds)); + } + } + ast::GenericParamKind::Type { default } => { + s.push_str(¶m.ident.to_string()); + if !param.bounds.is_empty() { + s.push_str(": "); + s.push_str(&pprust::bounds_to_string(¶m.bounds)); + } + if let Some(ty) = default { + s.push_str(" = "); + s.push_str(&pprust::ty_to_string(ty)); + } + } + ast::GenericParamKind::Const { ty, default, .. } => { + s.push_str("const "); + s.push_str(¶m.ident.to_string()); + s.push_str(": "); + s.push_str(&pprust::ty_to_string(ty)); + if !param.bounds.is_empty() { + s.push_str(": "); + s.push_str(&pprust::bounds_to_string(¶m.bounds)); + } + if let Some(default) = default { + s.push_str(" = "); + s.push_str(&pprust::expr_to_string(&default.value)); + } + } + } + s +} + +fn where_predicate_key(pred: &ast::WherePredicate) -> String { + match &pred.kind { + ast::WherePredicateKind::BoundPredicate(p) => pprust::where_bound_predicate_to_string(p), + ast::WherePredicateKind::EqPredicate(p) => format!( + "{} = {}", + pprust::ty_to_string(&p.lhs_ty), + pprust::ty_to_string(&p.rhs_ty) + ), + ast::WherePredicateKind::RegionPredicate(p) => { + // Good-enough structural key: use pretty printing for lifetime and bounds. + let mut s = p.lifetime.ident.to_string(); + s.push_str(": "); + // Lifetime bounds are encoded as `GenericBounds` in the AST. + s.push_str(&pprust::bounds_to_string(&p.bounds)); + s + } + } +} + diff --git a/compiler/rustc_ast_passes/src/lib.rs b/compiler/rustc_ast_passes/src/lib.rs index 6517fdb55bd3f..1e61adf149bd3 100644 --- a/compiler/rustc_ast_passes/src/lib.rs +++ b/compiler/rustc_ast_passes/src/lib.rs @@ -14,5 +14,6 @@ pub mod ast_validation; mod errors; pub mod feature_gate; +pub mod impl_shards; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index ceb7fc5bf6f8a..a862ad8aa40db 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -852,6 +852,12 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ cfg_attr_trace, Normal, template!(Word /* irrelevant */), DuplicatesOk, EncodeCrossCrate::No ), + // Compiler-generated marker used for `impl` shard syntax merging. + // This is not meant to be written by users. + ungated!( + rustc_impl_shard, Normal, template!(Word /* irrelevant */), DuplicatesOk, + EncodeCrossCrate::No + ), // ========================================================================== // Internal attributes, Diagnostics related: diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 6b220faa66ac4..cb2cf0e9c5460 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -534,6 +534,8 @@ declare_features! ( (unstable, half_open_range_patterns_in_slices, "1.66.0", Some(67264)), /// Allows `if let` guard in match arms. (unstable, if_let_guard, "1.47.0", Some(51114)), + /// Allows splitting a trait impl across multiple items ("impl shards"). + (incomplete, impl_shards, "1.90.0", None), /// Allows `impl Trait` to be used inside associated types (RFC 2515). (unstable, impl_trait_in_assoc_type, "1.70.0", Some(63063)), /// Allows `impl Trait` in bindings (`let`). diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 8dec8069bc7bd..eb0cc74b7d66e 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -130,6 +130,11 @@ fn configure_and_expand( let features = tcx.features(); let lint_store = unerased_lint_store(tcx.sess); let crate_name = tcx.crate_name(LOCAL_CRATE); + + // Merge `impl` shards as early as possible (pre-expansion) so subsequent phases + // see only the combined impl shape. + rustc_ast_passes::impl_shards::merge_impl_shards(&mut krate); + let lint_check_node = (&krate, pre_configured_attrs); pre_expansion_lint( sess, diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index cb7c56494332c..5d7361c1cb2ea 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -568,6 +568,7 @@ impl<'a> Parser<'a> { ) -> PResult<'a, ItemKind> { let safety = self.parse_safety(Case::Sensitive); self.expect_keyword(exp!(Impl))?; + let impl_kw_span = self.prev_token.span; // First, parse generic parameters if necessary. let mut generics = if self.choose_generics_over_qpath(0) { @@ -628,7 +629,47 @@ impl<'a> Parser<'a> { generics.where_clause = self.parse_where_clause()?; - let impl_items = self.parse_item_list(attrs, |p| p.parse_impl_item(ForceCollect::No))?; + // `impl Trait for Type { ... }` (existing) or `impl Trait for Type fn ...` (impl shards). + // + // The shard form is gated and desugars to an impl that contains exactly one associated item. + // We mark it with an internal attribute so later passes can merge shards. + let impl_items = if self.check(exp!(OpenBrace)) { + self.parse_item_list(attrs, |p| p.parse_impl_item(ForceCollect::No))? + } else { + // If we're not starting an item list, try parsing exactly one impl item. + // This supports: + // impl A for B fn foo() { ... } + // + // NOTE: We intentionally keep this surface syntax narrow for now: it is intended + // as a building block for "impl shards" experiments. + if !self.token.is_keyword(kw::Fn) + && !self.is_async_fn() + && !self.token.is_keyword(kw::Const) + && !self.token.is_keyword(kw::Type) + { + // Preserve the existing diagnostics for missing `{ ... }`. + return self.unexpected_any(); + } + + // Record the syntax gate span (hard error will be emitted later if not enabled). + // Use a conservative span that starts at the `impl` keyword. + self.psess.gated_spans.gate(sym::impl_shards, impl_kw_span); + + // Mark this impl so post-expansion passes can merge shards in the same module. + attrs.push(rustc_ast::attr::mk_attr_word( + &self.psess.attr_id_generator, + ast::AttrStyle::Outer, + ast::Safety::Default, + sym::rustc_impl_shard, + impl_kw_span, + )); + + let parsed = self.parse_impl_item(ForceCollect::No)?; + let Some(Some(item)) = parsed else { + return self.unexpected_any(); + }; + thin_vec![item] + }; let (of_trait, self_ty) = match ty_second { Some(ty_second) => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d54175548e30e..671bba72c36b4 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1178,6 +1178,7 @@ symbols! { ignore, impl_header_lifetime_elision, impl_lint_pass, + impl_shards, impl_trait_in_assoc_type, impl_trait_in_bindings, impl_trait_in_fn_trait_return, @@ -1855,6 +1856,7 @@ symbols! { rustc_has_incoherent_inherent_impls, rustc_hidden_type_of_opaques, rustc_if_this_changed, + rustc_impl_shard, rustc_inherit_overflow_checks, rustc_insignificant_dtor, rustc_intrinsic, diff --git a/tests/ui/impl-shards/duplicate.rs b/tests/ui/impl-shards/duplicate.rs new file mode 100644 index 0000000000000..474c55084c61f --- /dev/null +++ b/tests/ui/impl-shards/duplicate.rs @@ -0,0 +1,15 @@ +#![feature(impl_shards)] +#![allow(incomplete_features)] + +trait A { + fn a(&self); +} + +struct B; + +impl A for B fn a(&self) {} +impl A for B fn a(&self) {} +//~^ ERROR duplicate definitions with name `a` + +fn main() {} + diff --git a/tests/ui/impl-shards/duplicate.stderr b/tests/ui/impl-shards/duplicate.stderr new file mode 100644 index 0000000000000..ab7b2b2793b7c --- /dev/null +++ b/tests/ui/impl-shards/duplicate.stderr @@ -0,0 +1,14 @@ +error[E0201]: duplicate definitions with name `a`: + --> $DIR/duplicate.rs:11:14 + | +LL | fn a(&self); + | ------------ item in trait +... +LL | impl A for B fn a(&self) {} + | -------------- previous definition here +LL | impl A for B fn a(&self) {} + | ^^^^^^^^^^^^^^ duplicate definition + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0201`. diff --git a/tests/ui/impl-shards/feature-gate.rs b/tests/ui/impl-shards/feature-gate.rs new file mode 100644 index 0000000000000..01b6f24c182d1 --- /dev/null +++ b/tests/ui/impl-shards/feature-gate.rs @@ -0,0 +1,11 @@ +trait A { + fn a(&self) {} +} + +struct B; + +impl A for B fn a(&self) {} +//~^ ERROR `impl` shards are experimental + +fn main() {} + diff --git a/tests/ui/impl-shards/feature-gate.stderr b/tests/ui/impl-shards/feature-gate.stderr new file mode 100644 index 0000000000000..0470125f122c4 --- /dev/null +++ b/tests/ui/impl-shards/feature-gate.stderr @@ -0,0 +1,12 @@ +error[E0658]: `impl` shards are experimental + --> $DIR/feature-gate.rs:7:1 + | +LL | impl A for B fn a(&self) {} + | ^^^^ + | + = help: add `#![feature(impl_shards)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/impl-shards/split.rs b/tests/ui/impl-shards/split.rs new file mode 100644 index 0000000000000..fb3604561ae5a --- /dev/null +++ b/tests/ui/impl-shards/split.rs @@ -0,0 +1,26 @@ +//@ check-pass +#![feature(impl_shards)] +#![allow(incomplete_features)] + +trait A { + fn a(&self) -> i32; + fn b(&self) -> i32 { + 0 + } +} + +struct B; + +impl A for B fn a(&self) -> i32 { + 1 +} +impl A for B fn b(&self) -> i32 { + 2 +} + +fn main() { + let x = B; + assert_eq!(::a(&x), 1); + assert_eq!(::b(&x), 2); +} +