Skip to content

delegation: supporting inherent impls - #160505

Open
aerooneqq wants to merge 32 commits into
rust-lang:mainfrom
aerooneqq:delegation-inherent-methods
Open

delegation: supporting inherent impls#160505
aerooneqq wants to merge 32 commits into
rust-lang:mainfrom
aerooneqq:delegation-inherent-methods

Conversation

@aerooneqq

@aerooneqq aerooneqq commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

View all comments

This PR adds support for delegation to inherent impl functions on the delegation side.

Support for inherent impls in delegation consists of two problems: we need to resolve inherent function through ProbeContext routine and then we need to generate delegation function knowing the DefId of the signature function. The first problem is a fundamental problem given current compiler architecture, and it is not solved in this PR. To imitate working resolution for tests we adopt simple resolution by name only in inherent impls (not trait impls, which would work if we implement fair resolution through ProbeContext). A resolve_type_relative_delegations query was created which tries to resolve unresolved delegations after resolve stage. In future, when we will be able to fairly resolve delegations through ProbeContext contents of this query can be changed and all other logic implemented in this pull request will work.

Free to inherent impl

Unlike free to trait delegation where we generated explicit Self param, here we just use default parameter.

struct X<'a, T, const B: bool>(...);
impl<'a, T, const B: bool> X<'a, T, B> {
  fn foo<'b, U, const X: usize>(&self) { ... }
}

reuse X::<'static, (), false>::foo as foo1;
reuse X::<'static, (), false,>::foo::<'static, (), 123> as foo3;

//Desugaring:
#[attr = Inline(Hint)]
fn foo1<'b, U, const X: _>(self: _) -> _ where
    'b:'b { X<'static, (), false>::foo::<'b, U, X>(self) }

#[attr = Inline(Hint)]
fn foo3(self: _) -> _ { X<'static, (), false>::foo::<'static, (), 123>(self) }

Trait to inherent impl

In trait to inherent impl delegation we replace the type of self parameter from impl's type to Self generic param (if the signature function is a method).

trait Trait {
    reuse X::<'static, (), false>::foo as foo1;
    reuse X::<'static, (), false,>::foo::<'static, (), true> as foo3;
}

// Desugaring:
trait Trait {
    #[attr = Inline(Hint)]
    fn foo1<'b, U, const X: _>(self: _) -> _ where
        'b:'b { X<'static, (), false>::foo::<'b, U, X>(self) }

    #[attr = Inline(Hint)]
    fn foo3(self: _)
        -> _ { X<'static, (), false>::foo::<'static, (), 123>(self) }
}

Note that we didn't specified target expression, so we would get errors like:

error[E0308]: mismatched types
  --> $DIR/xd.rs:10:14
   |
LL | trait Trait {
   | ----------- found this type parameter
LL |     reuse X::foo;
   |              ^^^
   |              |
   |              expected `&X<'_, T, B>`, found `&Self`
   |              arguments to this function are incorrect
   |
   = note: expected reference `&X<'_, T, B>`
              found reference `&Self`

Trait impl to inherent impl

Here the resolution should look signature in trait as in other cases where we delegate from trait impl. We generate function whose signature matches the resolved function in trait. We propagate only child generics if they are not specified.

trait Trait {
    fn foo<A, B, C>(&self) { }
    fn foo1<T, U, V>(&self) { }
    fn foo2<'a, T, U, V>(&self) where 'a:'a { }
    fn foo3(&self) { }
}

impl Trait for X {
    reuse X::<'static, (), false>::foo as foo1;
    reuse X::<'static, (), false,>::foo::<'static, (), 123> as foo3;
}

// Desugaring:
impl Trait for X<'_> {
    #[attr = Inline(Hint)]
    fn foo1<T, U, V>(self: _)
        -> _ { X<'static, (), false>::foo::<T, U, V>(self) }

    #[attr = Inline(Hint)]
    fn foo3(self: _)
        -> _ { X<'static, (), false>::foo::<'static, (), 123>(self) }
}

Inherent impl to inherent impl

In inherent impl to inherent impl delegation we replace signature self type with delegation parent self type in case of methods.

trait Trait {
    fn foo<A, B, C>(&self) { }
    fn foo1<T, U, V>(&self) { }
    fn foo2<'a, T, U, V>(&self) where 'a:'a { }
    fn foo3(&self) { }
}

struct Y;

impl Trait for Y {
    reuse X::<'static, (), false>::foo as foo1;
    reuse X::<'static, (), false,>::foo::<'static, (), 123> as foo3;
}

impl Trait for Y {
    #[attr = Inline(Hint)]
    fn foo1<T, U, V>(self: _)
        -> _ { X<'static, (), false>::foo::<T, U, V>(self) }

    #[attr = Inline(Hint)]
    fn foo3(self: _)
        -> _ { X<'static, (), false>::foo::<'static, (), 123>(self) }
}

We did not specify target expression so we would get errors like:

error[E0308]: mismatched types
  --> $DIR/xd.rs:12:14
   |
LL |     reuse X::foo;
   |              ^^^
   |              |
   |              expected `&X<'_, T, B>`, found `Y`
   |              arguments to this function are incorrect
   |
   = note: expected reference `&X<'_, T, B>`
                 found struct `Y`

Generics

After some experiments I think that we should force user to always specify generics for parent segment of delegation to inherent impls. Consider the following example and imagine that we can use fair resolution through ProbeContext:

trait M1 {}
trait M2 {}

struct S1;
struct S2;

impl M1 for S1 {}
impl M2 for S2 {}

struct X<T, U>(T, U);

impl<T: M1> X<T, ()> {
    fn foo() {}
}

impl<T: M2> X<T, usize> {
    fn foo() {}
}

reuse X::foo;

How to resolve X::foo? If we generate parent generics (fn foo<T, U>() { X::<T, U>::foo() }) which clauses should we inherit? It is impossible to determine which function to reuse, and despite the fact that there may be some cases where it is possible, I don't think that we should write heuristics for that. So always specifying parent generics seems to be a good option. Also I think we should ban infers in parent segment too.

One implementation aspect of how we map generic args for signature and predicates inheritance, as we inherit predicates not from the ADT declaration but from the impl block we need to take generic args from this impl, not from the declaration. So indices of generic args are taken from the impl block and then they are used in mapping and future instantiation:

struct S<'a, A, const C: usize> {
    xd: &'a [A; C],
}

// index of A = 3
// index of C = 4
impl<'a, 'b, 'c, A, const C: usize> S<A, C> {
    fn foo_self<'d: 'd, 'e, T, const B: bool>(self) {}
}

trait Trait<'a, AA, BB> where Self: Sized {
    reuse S::<(), ()>::foo_self;
    // Args: [Self/#0, 'a/#1, AA/#2, BB/#3, '{region error}, 'd/#4, (), {const error}, T/#5, B/#6]
    // Mapping: {0: 0, 7: 9, 5: 5, 3: 6, 6: 8, 4: 7}, A (index 3) is mapped into index 6 (`()`), C (index 4) mapped into index 7 (const error)
}

Other concerns

Glob and list delegations

List delegations are supported, glob delegations are not supported:

struct X;

impl X {
    fn foo(&self) {}
    fn foo2(&self) {}
}

struct Y;

impl Y {
    reuse X::{foo, foo2} { X }
}

impl Y {
    reuse X::*;
    //~^ ERROR: expected trait, found struct `X`
}

Self type adjustments and target expression deletion

Adjustments for receiver are applied, adjustments for other parameters whose types contain Self are not applied as Self acts as a type alias to the struct, not a generic param which will can get replaced. The deletion of target expression should work as before.

enum X {
   ...
}

impl X {
    fn static_f() {}
    fn by_value(self) {}
    fn by_ref(&self) {}
    fn by_mut_ref(&mut self) {}
}

struct Y;

impl Y {
    fn get_x(&self) -> X { X }
    reuse X::{static_f, by_value, by_ref, by_mut_ref} { self.get_x() }
}

impl Y {
    fn get_x(&self) -> X { X }
    
    #[attr = Inline(Hint)]
    fn static_f() -> _ { X::static_f() }

    #[attr = Inline(Hint)]
    fn by_value(self: _) -> _ { X::by_value(self.get_x()) }

    #[attr = Inline(Hint)]
    fn by_ref(self: _) -> _ { X::by_ref(self.get_x()) }

    #[attr = Inline(Hint)]
    fn by_mut_ref(self: _) -> _ { X::by_mut_ref(self.get_x()) }
}

fn main() {
    let y = Y;
    y.by_ref();
    y.by_mut_ref();
    //~^ ERROR: cannot borrow `y` as mutable, as it is not declared as mutable
    y.by_value();

    let y = &Y;
    y.by_value();
    //~^ ERROR: cannot move out of `*y` which is behind a shared reference
    y.by_ref();
    y.by_mut_ref();
    //~^ ERROR: cannot borrow `*y` as mutable, as it is behind a `&` reference

    let y = &mut Y;
    y.by_value();
    //~^ ERROR: cannot move out of `*y` which is behind a mutable reference
    y.by_ref();
    y.by_mut_ref();
}

Recursive delegations

Works as before, we just check the resolution chain and we do not care whether it came from resolution at resolve stage or from resolution of type relative delegations.

r? @petrochenkov

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 4, 2026
@petrochenkov

Copy link
Copy Markdown
Contributor

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Aug 4, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Aug 4, 2026
@rust-log-analyzer

This comment has been minimized.

@rust-bors

rust-bors Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 38e5ff7 (38e5ff7311d36112747c8aa89f4bb52168984074)
Base parent: c9ff496 (c9ff496891c278ad660bc0ab85c1f0b72059464a)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (38e5ff7): comparison URL.

Overall result: ❌ regressions - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
0.3% [0.3%, 0.3%] 1
Regressions ❌
(secondary)
0.2% [0.2%, 0.2%] 2
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.3% [0.3%, 0.3%] 1

Max RSS (memory usage)

Results (primary 1.9%, secondary -0.0%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
1.9% [0.4%, 4.2%] 3
Regressions ❌
(secondary)
1.4% [0.5%, 2.4%] 2
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.6% [-1.0%, -0.4%] 5
All ❌✅ (primary) 1.9% [0.4%, 4.2%] 3

Cycles

Results (primary -1.1%, secondary 0.4%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
0.5% [0.4%, 0.5%] 3
Regressions ❌
(secondary)
1.6% [0.4%, 5.3%] 6
Improvements ✅
(primary)
-1.8% [-6.6%, -0.5%] 7
Improvements ✅
(secondary)
-1.4% [-3.6%, -0.5%] 4
All ❌✅ (primary) -1.1% [-6.6%, 0.5%] 10

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: 489.838s -> 487.916s (-0.39%)
Artifact size: 390.28 MiB -> 391.07 MiB (0.20%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Aug 4, 2026
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@aerooneqq aerooneqq changed the title delegation: supporting inherent methods delegation: supporting inherent impls Aug 6, 2026
@petrochenkov

Copy link
Copy Markdown
Contributor

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Aug 7, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Aug 7, 2026
@rust-bors

rust-bors Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 40975b9 (40975b9db210413c2edea725163a22b41796ad3e)
Base parent: 65bcac4 (65bcac45b3d8a8b2126e5cc844cf6fff5795d32a)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (40975b9): comparison URL.

Overall result: ❌ regressions - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
0.2% [0.2%, 0.2%] 1
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.2% [0.2%, 0.2%] 1

Max RSS (memory usage)

Results (primary 1.1%, secondary -0.2%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
1.1% [0.8%, 1.3%] 2
Regressions ❌
(secondary)
0.6% [0.4%, 0.8%] 7
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-1.7% [-3.5%, -0.4%] 4
All ❌✅ (primary) 1.1% [0.8%, 1.3%] 2

Cycles

Results (primary 2.2%, secondary 0.2%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.2% [2.2%, 2.2%] 1
Regressions ❌
(secondary)
0.9% [0.4%, 2.7%] 12
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.8% [-1.2%, -0.4%] 8
All ❌✅ (primary) 2.2% [2.2%, 2.2%] 1

Binary size

Results (primary -0.1%, secondary -0.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-0.1% [-0.1%, -0.0%] 48
Improvements ✅
(secondary)
-0.1% [-0.1%, -0.0%] 23
All ❌✅ (primary) -0.1% [-0.1%, -0.0%] 48

Bootstrap: 458.664s -> 461.027s (0.52%)
Artifact size: 398.60 MiB -> 398.58 MiB (-0.01%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Aug 7, 2026
@rust-bors

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

Comment thread compiler/rustc_ast/src/ast.rs Outdated
}
}

pub fn is_method(self, id: DefId) -> bool {

@petrochenkov petrochenkov Aug 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd rather inline and remove this.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used in two different crates, so I prefer not removing it.

Comment thread compiler/rustc_middle/src/ty/generic_args.rs Outdated
Comment thread compiler/rustc_hir_typeck/src/method/probe.rs
@petrochenkov

Copy link
Copy Markdown
Contributor

(I'll continue reviewing tomorrow.)

Comment thread compiler/rustc_middle/src/ty/sty.rs Outdated
Comment thread compiler/rustc_ast_lowering/src/delegation/generics.rs Outdated
Comment thread compiler/rustc_ast_lowering/src/delegation/generics.rs Outdated
Comment thread compiler/rustc_ast_lowering/src/delegation/generics.rs
Comment thread compiler/rustc_ast_lowering/src/delegation/resolution.rs Outdated
Comment thread compiler/rustc_resolve/src/late.rs
Comment thread compiler/rustc_ast_lowering/src/delegation/resolution.rs Outdated
Comment thread compiler/rustc_ast_lowering/src/delegation/resolution.rs
.and_then(get_segment)
.filter(|(_, def_id)| matches!(tcx.def_kind(*def_id), DefKind::Trait))
.filter(|(_, def_id)| {
matches!(tcx.def_kind(*def_id), DefKind::Trait | DefKind::Struct | DefKind::Enum)

@petrochenkov petrochenkov Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you panic on any unexpected DefKinds here?
And other similar matches on the resolution kind of the parent segment too.
I suspect that most of type namespace DefKinds are actually possible to encounter here.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is possible to encounter other def kinds here, and that is why we added those checks, they prevent ICEs further. Maybe delayed bug is better than panic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we need to generate error delegation for unexpected def kinds during AST -> HIR lowering and then we can add asserts here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe delayed bug is better than panic?

Panic (preferably) or delayed bug, whatever keeps the tests passing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we need to generate error delegation for unexpected def kinds during AST -> HIR lowering and then we can add asserts here.

In general, it is usually better to make erroneous state (def kinds) impossible as early as possible, although I don't know how convenient it will be in this specific case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, it is usually better to make erroneous state (def kinds) impossible as early as possible, although I don't know how convenient it will be in this specific case.

Implemented it.

Comment thread compiler/rustc_hir_analysis/src/delegation.rs
@petrochenkov petrochenkov added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 2, 2026
asukaminato0721 pushed a commit to asukaminato0721/rust-analyzer that referenced this pull request Sep 2, 2026
…ochenkov

delegation: add tests for delegations to inherent impls

Second part of rust-lang/rust#160505, for more convenient reviewing.

Part of rust-lang/rust#118212.
r? @petrochenkov
@rust-log-analyzer

This comment has been minimized.

@aerooneqq

Copy link
Copy Markdown
Contributor Author

@rustbot ready (comments feedback)

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 3, 2026
}

#[derive(Debug, StableHash)]
pub enum DelegationInhFuncKind {

@petrochenkov petrochenkov Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub enum DelegationInhFuncKind {
pub enum DelegationInherentFnKind {

View changes since the review

// Information about delegations which is used when handling recursive delegations
// and ensures easy access to delegation-only `LocalDefId`s.
pub delegation_infos: FxIndexMap<LocalDefId, DelegationInfo>,
pub delegation_inh_functions_map:

@petrochenkov petrochenkov Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub delegation_inh_functions_map:
pub delegation_inherent_fn_map:

View changes since the review

}
}

fn opt_inherent_impl_adt(&self, sig_id: DefId) -> Option<(DefId, ty::GenericArgsRef<'hir>)> {

@petrochenkov petrochenkov Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be inlined and removed now.

View changes since the review

/// Simple (hack or heuristic) resolution of some delegations to inherent impls
/// while correct resolution through `ProbeContext` is not available
/// during AST -> HIR lowering due to query cycles.
/// FIXME(fn_delegation): correct resolution through `ProbeContext` engine

@petrochenkov petrochenkov Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment telling that successful resolutions from this heuristics are not a subset of successful resolutions from the correct approach.
And that to make it somewhat stabilizable we need a second pass in type checking (i.e. when there's no cycles) that makes sure that resolutions from the heuristic match the correct resolutions, or reports errors otherwise.
#160505 (comment)

View changes since the review

'inh_loop: for inh_impl_id in tcx.inherent_impls(res) {
let assoc_items = tcx.associated_items(*inh_impl_id);
let mut candidates = assoc_items
.filter_by_name_unhygienic(ident.name)

@petrochenkov petrochenkov Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// FIXME: use correct identifier hygiene

View changes since the review


let map = self.r.delegation_inh_functions_map.entry(self_type_def_id).or_default();

match map.get(ident) {

@petrochenkov petrochenkov Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// FIXME: use correct identifier hygiene

View changes since the review

@petrochenkov petrochenkov added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

F-fn_delegation `#![feature(fn_delegation)]` S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants