From 7ee97f93dabd0be0cfbee437c42dd6555f7bdfd4 Mon Sep 17 00:00:00 2001 From: Bryanskiy Date: Tue, 18 Jun 2024 14:04:28 +0300 Subject: [PATCH] Delegation: support coercion for target expression --- compiler/rustc_ast_lowering/src/delegation.rs | 84 +++++++++++++++---- compiler/rustc_hir_typeck/src/method/mod.rs | 9 +- compiler/rustc_hir_typeck/src/method/probe.rs | 33 +++++++- tests/ui/delegation/bad-resolve.rs | 3 + tests/ui/delegation/bad-resolve.stderr | 24 +++++- tests/ui/delegation/explicit-paths-pass.rs | 4 +- tests/ui/delegation/explicit-paths.rs | 4 +- tests/ui/delegation/explicit-paths.stderr | 9 +- tests/ui/delegation/ice-issue-122550.stderr | 25 ++++-- tests/ui/delegation/method-call-choice.rs | 25 ++++++ tests/ui/delegation/method-call-choice.stderr | 21 +++++ tests/ui/delegation/method-call-priority.rs | 34 ++++++++ tests/ui/delegation/self-coercion.rs | 26 ++++++ 13 files changed, 264 insertions(+), 37 deletions(-) create mode 100644 tests/ui/delegation/method-call-choice.rs create mode 100644 tests/ui/delegation/method-call-choice.stderr create mode 100644 tests/ui/delegation/method-call-priority.rs create mode 100644 tests/ui/delegation/self-coercion.rs diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index 678cac210f4..6df2c15ce60 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -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>, 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), diff --git a/compiler/rustc_hir_typeck/src/method/mod.rs b/compiler/rustc_hir_typeck/src/method/mod.rs index dc1b888374c..daf4ef5cdb3 100644 --- a/compiler/rustc_hir_typeck/src/method/mod.rs +++ b/compiler/rustc_hir_typeck/src/method/mod.rs @@ -182,8 +182,13 @@ pub fn lookup_method( self_expr: &'tcx hir::Expr<'tcx>, args: &'tcx [hir::Expr<'tcx>], ) -> Result, 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, diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 6a7af5510e0..1c308d4af3e 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -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( 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) }) diff --git a/tests/ui/delegation/bad-resolve.rs b/tests/ui/delegation/bad-resolve.rs index f378e05304b..f15e6aa81af 100644 --- a/tests/ui/delegation/bad-resolve.rs +++ b/tests/ui/delegation/bad-resolve.rs @@ -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 {} diff --git a/tests/ui/delegation/bad-resolve.stderr b/tests/ui/delegation/bad-resolve.stderr index 883ff523bcf..32d2f3b26cb 100644 --- a/tests/ui/delegation/bad-resolve.stderr +++ b/tests/ui/delegation/bad-resolve.stderr @@ -25,6 +25,15 @@ LL | reuse ::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`. diff --git a/tests/ui/delegation/explicit-paths-pass.rs b/tests/ui/delegation/explicit-paths-pass.rs index fada793bd11..dd0ee2c732f 100644 --- a/tests/ui/delegation/explicit-paths-pass.rs +++ b/tests/ui/delegation/explicit-paths-pass.rs @@ -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 ::static_method; reuse ::static_method2 { S::static_method(self) } } diff --git a/tests/ui/delegation/explicit-paths.rs b/tests/ui/delegation/explicit-paths.rs index a91ca4cb931..d42e305b252 100644 --- a/tests/ui/delegation/explicit-paths.rs +++ b/tests/ui/delegation/explicit-paths.rs @@ -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 ::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 ::foo2; reuse to_reuse::foo3; //~^ ERROR method `foo3` is not a member of trait `Trait` diff --git a/tests/ui/delegation/explicit-paths.stderr b/tests/ui/delegation/explicit-paths.stderr index d33c5da4377..b5afe19f878 100644 --- a/tests/ui/delegation/explicit-paths.stderr +++ b/tests/ui/delegation/explicit-paths.stderr @@ -91,10 +91,17 @@ error[E0308]: mismatched types LL | trait Trait2 : Trait { | -------------------- found this type parameter LL | reuse ::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 diff --git a/tests/ui/delegation/ice-issue-122550.stderr b/tests/ui/delegation/ice-issue-122550.stderr index c92170644e7..1a01bee3e1e 100644 --- a/tests/ui/delegation/ice-issue-122550.stderr +++ b/tests/ui/delegation/ice-issue-122550.stderr @@ -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 ::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 ::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. diff --git a/tests/ui/delegation/method-call-choice.rs b/tests/ui/delegation/method-call-choice.rs new file mode 100644 index 00000000000..8d53d8bfdb7 --- /dev/null +++ b/tests/ui/delegation/method-call-choice.rs @@ -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() {} diff --git a/tests/ui/delegation/method-call-choice.stderr b/tests/ui/delegation/method-call-choice.stderr new file mode 100644 index 00000000000..6757af20a6b --- /dev/null +++ b/tests/ui/delegation/method-call-choice.stderr @@ -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`. diff --git a/tests/ui/delegation/method-call-priority.rs b/tests/ui/delegation/method-call-priority.rs new file mode 100644 index 00000000000..8d68740d181 --- /dev/null +++ b/tests/ui/delegation/method-call-priority.rs @@ -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); +} diff --git a/tests/ui/delegation/self-coercion.rs b/tests/ui/delegation/self-coercion.rs new file mode 100644 index 00000000000..96c1f1b140b --- /dev/null +++ b/tests/ui/delegation/self-coercion.rs @@ -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); +}