Skip to content

Commit

Permalink
Rollup merge of #126699 - Bryanskiy:delegation-coercion, r=compiler-e…
Browse files Browse the repository at this point in the history
…rrors

Delegation: support coercion for target expression

(solves #118212 (comment))

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`
  • Loading branch information
tgross35 authored Jul 16, 2024
2 parents 63f239c + 7ee97f9 commit 36ea068
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 37 deletions.
84 changes: 69 additions & 15 deletions compiler/rustc_ast_lowering/src/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -259,8 +259,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
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)
Expand All @@ -273,26 +272,81 @@ impl<'hir> LoweringContext<'_, 'hir> {
})
}

// 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),
Expand Down
9 changes: 7 additions & 2 deletions compiler/rustc_hir_typeck/src/method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
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,
Expand Down
33 changes: 30 additions & 3 deletions compiler/rustc_hir_typeck/src/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use rustc_middle::middle::stability;
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};
Expand Down Expand Up @@ -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,

Expand Down Expand Up @@ -480,12 +484,35 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
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)
})
Expand Down
3 changes: 3 additions & 0 deletions tests/ui/delegation/bad-resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
24 changes: 21 additions & 3 deletions tests/ui/delegation/bad-resolve.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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
|
Expand Down Expand Up @@ -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
|
Expand All @@ -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`.
4 changes: 2 additions & 2 deletions tests/ui/delegation/explicit-paths-pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ reuse to_reuse::zero_args { self }

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) }
}
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/delegation/explicit-paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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`
Expand Down
9 changes: 8 additions & 1 deletion tests/ui/delegation/explicit-paths.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 16 additions & 9 deletions tests/ui/delegation/ice-issue-122550.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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
|
Expand All @@ -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.
Expand Down
25 changes: 25 additions & 0 deletions tests/ui/delegation/method-call-choice.rs
Original file line number Diff line number Diff line change
@@ -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() {}
21 changes: 21 additions & 0 deletions tests/ui/delegation/method-call-choice.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading

0 comments on commit 36ea068

Please sign in to comment.