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 compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(<expr>, <pattern>)` instead of `if let <pattern> = <expr>`"
);
gate_all!(impl_shards, "`impl` shards are experimental");
gate_all!(
async_trait_bounds,
"`async` trait bounds are unstable",
Expand Down
194 changes: 194 additions & 0 deletions compiler/rustc_ast_passes/src/impl_shards.rs
Original file line number Diff line number Diff line change
@@ -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<P<ast::Item>>) {
// 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<P<ast::Item>> = ThinVec::new();
let mut first_impl_for_key: FxHashMap<String, usize> = 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<ast::Item>) {
if let ast::ItemKind::Mod(_, _, ast::ModKind::Loaded(items, _, _, _)) = &mut item.kind {
merge_in_items(items);
}
}

fn shard_key(item: &ast::Item) -> Option<String> {
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<ast::Item>) -> ThinVec<P<ast::AssocItem>> {
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 &param.kind {
ast::GenericParamKind::Lifetime => {
s.push_str(&param.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(&param.bounds));
}
}
ast::GenericParamKind::Type { default } => {
s.push_str(&param.ident.to_string());
if !param.bounds.is_empty() {
s.push_str(": ");
s.push_str(&pprust::bounds_to_string(&param.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(&param.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(&param.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
}
}
}

1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
6 changes: 6 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_interface/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
43 changes: 42 additions & 1 deletion compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) => {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions tests/ui/impl-shards/duplicate.rs
Original file line number Diff line number Diff line change
@@ -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() {}

14 changes: 14 additions & 0 deletions tests/ui/impl-shards/duplicate.stderr
Original file line number Diff line number Diff line change
@@ -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`.
11 changes: 11 additions & 0 deletions tests/ui/impl-shards/feature-gate.rs
Original file line number Diff line number Diff line change
@@ -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() {}

12 changes: 12 additions & 0 deletions tests/ui/impl-shards/feature-gate.stderr
Original file line number Diff line number Diff line change
@@ -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`.
26 changes: 26 additions & 0 deletions tests/ui/impl-shards/split.rs
Original file line number Diff line number Diff line change
@@ -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!(<B as A>::a(&x), 1);
assert_eq!(<B as A>::b(&x), 2);
}