Rollup merge of #126699 - Bryanskiy:delegation-coercion, r=compiler-errors

Delegation: support coercion for target expression

(solves https://github.com/rust-lang/rust/issues/118212#issuecomment-2160723092)

The implementation consist of 2 parts. Firstly, method call is generated instead of fully qualified call in AST->HIR lowering if there were no generic arguments or `Qpath` were provided. These restrictions are imposed due to the loss of information after desugaring. For example in

```rust
trait Trait {
  fn foo(&self) {}
}

reuse <u8 as Trait>::foo;
```

We would like to generate such a code:

```rust
fn foo<u8: Trait>(x: &u8) {
  x.foo(x)
}
```

however, the signature is inherited during HIR analysis where `u8` was discarded.

Then, we probe the single pre-resolved method.

P.S In the future, we would like to avoid restrictions on the callee path by `Self` autoref/autoderef in fully qualified calls, but at the moment it didn't work out.

r? `@petrochenkov`
This commit is contained in:
Trevor Gross 2024-07-16 16:15:14 -05:00 committed by GitHub
commit 36ea06827b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 264 additions and 37 deletions

View File

@ -38,7 +38,7 @@
use crate::{ImplTraitPosition, ResolverAstLoweringExt};
use super::{ImplTraitContext, LoweringContext, ParamMode};
use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs};
use ast::visit::Visitor;
use hir::def::{DefKind, PartialRes, Res};
@ -259,8 +259,7 @@ fn lower_delegation_body(
self_param_id: pat_node_id,
};
self_resolver.visit_block(block);
let block = this.lower_block(block, false);
this.mk_expr(hir::ExprKind::Block(block, None), block.span)
this.lower_target_expr(&block)
} else {
let pat_hir_id = this.lower_node_id(pat_node_id);
this.generate_arg(pat_hir_id, span)
@ -273,26 +272,81 @@ fn lower_delegation_body(
})
}
// Generates fully qualified call for the resulting body.
// FIXME(fn_delegation): Alternatives for target expression lowering:
// https://github.com/rust-lang/rfcs/pull/3530#issuecomment-2197170600.
fn lower_target_expr(&mut self, block: &Block) -> hir::Expr<'hir> {
if block.stmts.len() == 1
&& let StmtKind::Expr(expr) = &block.stmts[0].kind
{
return self.lower_expr_mut(expr);
}
let block = self.lower_block(block, false);
self.mk_expr(hir::ExprKind::Block(block, None), block.span)
}
// Generates expression for the resulting body. If possible, `MethodCall` is used
// to allow autoref/autoderef for target expression. For example in:
//
// trait Trait : Sized {
// fn by_value(self) -> i32 { 1 }
// fn by_mut_ref(&mut self) -> i32 { 2 }
// fn by_ref(&self) -> i32 { 3 }
// }
//
// struct NewType(SomeType);
// impl Trait for NewType {
// reuse Trait::* { self.0 }
// }
//
// `self.0` will automatically coerce.
fn finalize_body_lowering(
&mut self,
delegation: &Delegation,
args: Vec<hir::Expr<'hir>>,
span: Span,
) -> hir::Expr<'hir> {
let path = self.lower_qpath(
delegation.id,
&delegation.qself,
&delegation.path,
ParamMode::Optional,
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
None,
);
let args = self.arena.alloc_from_iter(args);
let path_expr = self.arena.alloc(self.mk_expr(hir::ExprKind::Path(path), span));
let call = self.arena.alloc(self.mk_expr(hir::ExprKind::Call(path_expr, args), span));
let has_generic_args =
delegation.path.segments.iter().rev().skip(1).any(|segment| segment.args.is_some());
let call = if self
.get_resolution_id(delegation.id, span)
.and_then(|def_id| Ok(self.has_self(def_id, span)))
.unwrap_or_default()
&& delegation.qself.is_none()
&& !has_generic_args
{
let ast_segment = delegation.path.segments.last().unwrap();
let segment = self.lower_path_segment(
delegation.path.span,
ast_segment,
ParamMode::Optional,
ParenthesizedGenericArgs::Err,
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
None,
);
let segment = self.arena.alloc(segment);
self.arena.alloc(hir::Expr {
hir_id: self.next_id(),
kind: hir::ExprKind::MethodCall(segment, &args[0], &args[1..], span),
span,
})
} else {
let path = self.lower_qpath(
delegation.id,
&delegation.qself,
&delegation.path,
ParamMode::Optional,
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
None,
);
let callee_path = self.arena.alloc(self.mk_expr(hir::ExprKind::Path(path), span));
self.arena.alloc(self.mk_expr(hir::ExprKind::Call(callee_path, args), span))
};
let block = self.arena.alloc(hir::Block {
stmts: &[],
expr: Some(call),

View File

@ -182,8 +182,13 @@ pub fn lookup_method(
self_expr: &'tcx hir::Expr<'tcx>,
args: &'tcx [hir::Expr<'tcx>],
) -> Result<MethodCallee<'tcx>, MethodError<'tcx>> {
let pick =
self.lookup_probe(segment.ident, self_ty, call_expr, ProbeScope::TraitsInScope)?;
let scope = if let Some(only_method) = segment.res.opt_def_id() {
ProbeScope::Single(only_method)
} else {
ProbeScope::TraitsInScope
};
let pick = self.lookup_probe(segment.ident, self_ty, call_expr, scope)?;
self.lint_edition_dependent_dot_call(
self_ty, segment, span, call_expr, self_expr, &pick, args,

View File

@ -20,6 +20,7 @@
use rustc_middle::query::Providers;
use rustc_middle::ty::fast_reject::{simplify_type, TreatParams};
use rustc_middle::ty::AssocItem;
use rustc_middle::ty::AssocItemContainer;
use rustc_middle::ty::GenericParamDefKind;
use rustc_middle::ty::Upcast;
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
@ -216,6 +217,9 @@ pub enum Mode {
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum ProbeScope {
// Single candidate coming from pre-resolved delegation method.
Single(DefId),
// Assemble candidates coming only from traits in scope.
TraitsInScope,
@ -480,12 +484,35 @@ pub(crate) fn probe_op<OP, R>(
is_suggestion,
);
probe_cx.assemble_inherent_candidates();
match scope {
ProbeScope::TraitsInScope => {
probe_cx.assemble_extension_candidates_for_traits_in_scope()
probe_cx.assemble_inherent_candidates();
probe_cx.assemble_extension_candidates_for_traits_in_scope();
}
ProbeScope::AllTraits => {
probe_cx.assemble_inherent_candidates();
probe_cx.assemble_extension_candidates_for_all_traits();
}
ProbeScope::Single(def_id) => {
let item = self.tcx.associated_item(def_id);
// FIXME(fn_delegation): Delegation to inherent methods is not yet supported.
assert_eq!(item.container, AssocItemContainer::TraitContainer);
let trait_def_id = self.tcx.parent(def_id);
let trait_span = self.tcx.def_span(trait_def_id);
let trait_args = self.fresh_args_for_item(trait_span, trait_def_id);
let trait_ref = ty::TraitRef::new_from_args(self.tcx, trait_def_id, trait_args);
probe_cx.push_candidate(
Candidate {
item,
kind: CandidateKind::TraitCandidate(ty::Binder::dummy(trait_ref)),
import_ids: smallvec![],
},
false,
);
}
ProbeScope::AllTraits => probe_cx.assemble_extension_candidates_for_all_traits(),
};
op(probe_cx)
})

View File

@ -34,6 +34,9 @@ impl Trait for S {
reuse foo { &self.0 }
//~^ ERROR cannot find function `foo` in this scope
reuse Trait::foo2 { self.0 }
//~^ ERROR cannot find function `foo2` in trait `Trait`
//~| ERROR method `foo2` is not a member of trait `Trait`
}
mod prefix {}

View File

@ -25,6 +25,15 @@ LL | reuse <F as Trait>::baz;
| | help: there is an associated function with a similar name: `bar`
| not a member of trait `Trait`
error[E0407]: method `foo2` is not a member of trait `Trait`
--> $DIR/bad-resolve.rs:37:5
|
LL | reuse Trait::foo2 { self.0 }
| ^^^^^^^^^^^^^----^^^^^^^^^^^
| | |
| | help: there is an associated function with a similar name: `foo`
| not a member of trait `Trait`
error[E0423]: expected function, found associated constant `Trait::C`
--> $DIR/bad-resolve.rs:24:11
|
@ -54,6 +63,15 @@ error[E0425]: cannot find function `foo` in this scope
LL | reuse foo { &self.0 }
| ^^^ not found in this scope
error[E0425]: cannot find function `foo2` in trait `Trait`
--> $DIR/bad-resolve.rs:37:18
|
LL | fn foo(&self, x: i32) -> i32 { x }
| ---------------------------- similarly named associated function `foo` defined here
...
LL | reuse Trait::foo2 { self.0 }
| ^^^^ help: an associated function with a similar name exists: `foo`
error[E0046]: not all trait items implemented, missing: `Type`
--> $DIR/bad-resolve.rs:22:1
|
@ -64,18 +82,18 @@ LL | impl Trait for S {
| ^^^^^^^^^^^^^^^^ missing `Type` in implementation
error[E0433]: failed to resolve: use of undeclared crate or module `unresolved_prefix`
--> $DIR/bad-resolve.rs:40:7
--> $DIR/bad-resolve.rs:43:7
|
LL | reuse unresolved_prefix::{a, b, c};
| ^^^^^^^^^^^^^^^^^ use of undeclared crate or module `unresolved_prefix`
error[E0433]: failed to resolve: `crate` in paths can only be used in start position
--> $DIR/bad-resolve.rs:41:29
--> $DIR/bad-resolve.rs:44:29
|
LL | reuse prefix::{self, super, crate};
| ^^^^^ `crate` in paths can only be used in start position
error: aborting due to 10 previous errors
error: aborting due to 12 previous errors
Some errors have detailed explanations: E0046, E0324, E0407, E0423, E0425, E0433, E0575, E0576.
For more information about an error, try `rustc --explain E0046`.

View File

@ -24,8 +24,8 @@ pub fn zero_args() -> i32 { 15 }
struct S(F);
impl Trait for S {
reuse Trait::bar { &self.0 }
reuse Trait::description { &self.0 }
reuse Trait::bar { self.0 }
reuse Trait::description { self.0 }
reuse <F as Trait>::static_method;
reuse <F as Trait>::static_method2 { S::static_method(self) }
}

View File

@ -34,7 +34,7 @@ mod inherent_impl_assoc_fn_to_other {
use crate::*;
impl S {
reuse Trait::foo1 { &self.0 }
reuse Trait::foo1 { self.0 }
reuse <S as Trait>::foo2;
reuse to_reuse::foo3;
reuse F::foo4 { &self.0 }
@ -46,7 +46,7 @@ mod trait_impl_assoc_fn_to_other {
use crate::*;
impl Trait for S {
reuse Trait::foo1 { &self.0 }
reuse Trait::foo1 { self.0 }
reuse <F as Trait>::foo2;
reuse to_reuse::foo3;
//~^ ERROR method `foo3` is not a member of trait `Trait`

View File

@ -91,10 +91,17 @@ error[E0308]: mismatched types
LL | trait Trait2 : Trait {
| -------------------- found this type parameter
LL | reuse <F as Trait>::foo1 { self }
| ^^^^ expected `&F`, found `&Self`
| ---- ^^^^ expected `&F`, found `&Self`
| |
| arguments to this function are incorrect
|
= note: expected reference `&F`
found reference `&Self`
note: method defined here
--> $DIR/explicit-paths.rs:5:8
|
LL | fn foo1(&self, x: i32) -> i32 { x }
| ^^^^ -----
error[E0277]: the trait bound `S2: Trait` is not satisfied
--> $DIR/explicit-paths.rs:78:16

View File

@ -4,15 +4,6 @@ error[E0308]: mismatched types
LL | fn description(&self) -> &str {}
| ^^ expected `&str`, found `()`
error[E0308]: mismatched types
--> $DIR/ice-issue-122550.rs:13:39
|
LL | reuse <S as Trait>::description { &self.0 }
| ^^^^^^^ expected `&S`, found `&F`
|
= note: expected reference `&S`
found reference `&F`
error[E0277]: the trait bound `S: Trait` is not satisfied
--> $DIR/ice-issue-122550.rs:13:12
|
@ -25,6 +16,22 @@ help: this trait has no implementations, consider adding one
LL | trait Trait {
| ^^^^^^^^^^^
error[E0308]: mismatched types
--> $DIR/ice-issue-122550.rs:13:39
|
LL | reuse <S as Trait>::description { &self.0 }
| ----------- ^^^^^^^ expected `&S`, found `&F`
| |
| arguments to this function are incorrect
|
= note: expected reference `&S`
found reference `&F`
note: method defined here
--> $DIR/ice-issue-122550.rs:5:8
|
LL | fn description(&self) -> &str {}
| ^^^^^^^^^^^ -----
error: aborting due to 3 previous errors
Some errors have detailed explanations: E0277, E0308.

View File

@ -0,0 +1,25 @@
#![feature(fn_delegation)]
#![allow(incomplete_features)]
trait Trait {
fn foo(&self) {}
}
struct F;
impl Trait for F {}
struct S(F);
pub mod to_reuse {
use crate::F;
pub fn foo(_: &F) {}
}
impl Trait for S {
// Make sure that the method call is not generated if the path resolution
// does not have a `self` parameter.
reuse to_reuse::foo { self.0 }
//~^ ERROR mismatched types
}
fn main() {}

View File

@ -0,0 +1,21 @@
error[E0308]: mismatched types
--> $DIR/method-call-choice.rs:21:27
|
LL | reuse to_reuse::foo { self.0 }
| --- ^^^^^^ expected `&F`, found `F`
| |
| arguments to this function are incorrect
|
note: function defined here
--> $DIR/method-call-choice.rs:15:12
|
LL | pub fn foo(_: &F) {}
| ^^^ -----
help: consider borrowing here
|
LL | reuse to_reuse::foo { &self.0 }
| +
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.

View File

@ -0,0 +1,34 @@
//@ run-pass
#![feature(fn_delegation)]
#![allow(incomplete_features)]
#![allow(dead_code)]
trait Trait1 {
fn foo(&self) -> i32 { 1 }
}
trait Trait2 {
fn foo(&self) -> i32 { 2 }
}
struct F;
impl Trait1 for F {}
impl Trait2 for F {}
impl F {
fn foo(&self) -> i32 { 3 }
}
struct S(F);
impl Trait1 for S {
// Make sure that the generated `self.0.foo()` does not turn into the inherent method `F::foo`
// that has a higher priority than methods from traits.
reuse Trait1::foo { self.0 }
}
fn main() {
let s = S(F);
assert_eq!(s.foo(), 1);
}

View File

@ -0,0 +1,26 @@
//@ run-pass
#![feature(fn_delegation)]
#![allow(incomplete_features)]
trait Trait : Sized {
fn by_value(self) -> i32 { 1 }
fn by_mut_ref(&mut self) -> i32 { 2 }
fn by_ref(&self) -> i32 { 3 }
}
struct F;
impl Trait for F {}
struct S(F);
impl Trait for S {
reuse Trait::{by_value, by_mut_ref, by_ref} { self.0 }
}
fn main() {
let mut s = S(F);
assert_eq!(s.by_ref(), 3);
assert_eq!(s.by_mut_ref(), 2);
assert_eq!(s.by_value(), 1);
}