Skip to content

Commit 7d47489

Browse files
authored
Rollup merge of rust-lang#126699 - Bryanskiy:delegation-coercion, r=petrochenkov
Delegation: support coercion for target expression (solves rust-lang#118212 (comment)) The implementation consist of 3 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. Secondly, traits found in the callee path are also added to `trait_map`. This is done to avoid the side effects of converting body into method calls: ```rust mod inner { pub trait Trait { fn foo(&self) {} } } reuse inner::Trait::foo; // Ok, `Trait` is in scope for implicit method call. ``` Finally, applicable candidates are filtered with pre-resolved method during method lookup. 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`
2 parents fd8a23b + 9568e12 commit 7d47489

17 files changed

+291
-36
lines changed

compiler/rustc_ast_lowering/src/delegation.rs

+62-15
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
3939
use crate::{ImplTraitPosition, ResolverAstLoweringExt};
4040

41-
use super::{ImplTraitContext, LoweringContext, ParamMode};
41+
use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs};
4242

4343
use ast::visit::Visitor;
4444
use hir::def::{DefKind, PartialRes, Res};
@@ -259,8 +259,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
259259
self_param_id: pat_node_id,
260260
};
261261
self_resolver.visit_block(block);
262-
let block = this.lower_block(block, false);
263-
this.mk_expr(hir::ExprKind::Block(block, None), block.span)
262+
this.lower_target_expr(&block)
264263
} else {
265264
let pat_hir_id = this.lower_node_id(pat_node_id);
266265
this.generate_arg(pat_hir_id, span)
@@ -273,26 +272,74 @@ impl<'hir> LoweringContext<'_, 'hir> {
273272
})
274273
}
275274

276-
// Generates fully qualified call for the resulting body.
275+
// FIXME(fn_delegation): Alternatives for target expression lowering:
276+
// https://github.com/rust-lang/rfcs/pull/3530#issuecomment-2197170600.
277+
fn lower_target_expr(&mut self, block: &Block) -> hir::Expr<'hir> {
278+
if block.stmts.len() == 1
279+
&& let StmtKind::Expr(expr) = &block.stmts[0].kind
280+
{
281+
return self.lower_expr_mut(expr);
282+
}
283+
284+
let block = self.lower_block(block, false);
285+
self.mk_expr(hir::ExprKind::Block(block, None), block.span)
286+
}
287+
288+
// Generates expression for the resulting body. If possible, `MethodCall` is used
289+
// instead of fully qualified call for the self type coercion.
277290
fn finalize_body_lowering(
278291
&mut self,
279292
delegation: &Delegation,
280293
args: Vec<hir::Expr<'hir>>,
281294
span: Span,
282295
) -> hir::Expr<'hir> {
283-
let path = self.lower_qpath(
284-
delegation.id,
285-
&delegation.qself,
286-
&delegation.path,
287-
ParamMode::Optional,
288-
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
289-
None,
290-
);
291-
292296
let args = self.arena.alloc_from_iter(args);
293-
let path_expr = self.arena.alloc(self.mk_expr(hir::ExprKind::Path(path), span));
294-
let call = self.arena.alloc(self.mk_expr(hir::ExprKind::Call(path_expr, args), span));
295297

298+
let has_generic_args =
299+
delegation.path.segments.iter().rev().skip(1).any(|segment| segment.args.is_some());
300+
301+
let call = if self
302+
.get_resolution_id(delegation.id, span)
303+
.and_then(|def_id| Ok(self.has_self(def_id, span)))
304+
.unwrap_or_default()
305+
&& delegation.qself.is_none()
306+
&& !has_generic_args
307+
{
308+
let ast_segment = delegation.path.segments.last().unwrap();
309+
let segment = self.lower_path_segment(
310+
delegation.path.span,
311+
ast_segment,
312+
ParamMode::Optional,
313+
ParenthesizedGenericArgs::Err,
314+
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
315+
None,
316+
None,
317+
);
318+
let segment = self.arena.alloc(segment);
319+
320+
let method_call_id = self.next_id();
321+
if let Some(traits) = self.resolver.trait_map.remove(&delegation.id) {
322+
self.trait_map.insert(method_call_id.local_id, traits.into_boxed_slice());
323+
}
324+
325+
self.arena.alloc(hir::Expr {
326+
hir_id: method_call_id,
327+
kind: hir::ExprKind::MethodCall(segment, &args[0], &args[1..], span),
328+
span,
329+
})
330+
} else {
331+
let path = self.lower_qpath(
332+
delegation.id,
333+
&delegation.qself,
334+
&delegation.path,
335+
ParamMode::Optional,
336+
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
337+
None,
338+
);
339+
340+
let callee_path = self.arena.alloc(self.mk_expr(hir::ExprKind::Path(path), span));
341+
self.arena.alloc(self.mk_expr(hir::ExprKind::Call(callee_path, args), span))
342+
};
296343
let block = self.arena.alloc(hir::Block {
297344
stmts: &[],
298345
expr: Some(call),

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+1
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
583583
IsSuggestion(true),
584584
callee_ty.peel_refs(),
585585
callee_expr.unwrap().hir_id,
586+
None,
586587
TraitsInScope,
587588
|mut ctxt| ctxt.probe_for_similar_candidate(),
588589
)

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16101610
IsSuggestion(true),
16111611
self_ty,
16121612
expr.hir_id,
1613+
None,
16131614
ProbeScope::TraitsInScope,
16141615
)
16151616
{

compiler/rustc_hir_typeck/src/method/mod.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
104104
IsSuggestion(true),
105105
self_ty,
106106
call_expr_id,
107+
None,
107108
ProbeScope::TraitsInScope,
108109
) {
109110
Ok(pick) => {
@@ -182,8 +183,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
182183
self_expr: &'tcx hir::Expr<'tcx>,
183184
args: &'tcx [hir::Expr<'tcx>],
184185
) -> Result<MethodCallee<'tcx>, MethodError<'tcx>> {
185-
let pick =
186-
self.lookup_probe(segment.ident, self_ty, call_expr, ProbeScope::TraitsInScope)?;
186+
let pick = self.lookup_probe(
187+
segment.ident,
188+
self_ty,
189+
call_expr,
190+
segment.res.opt_def_id(),
191+
ProbeScope::TraitsInScope,
192+
)?;
187193

188194
self.lint_edition_dependent_dot_call(
189195
self_ty, segment, span, call_expr, self_expr, &pick, args,
@@ -208,6 +214,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
208214
segment.ident,
209215
trait_type,
210216
call_expr,
217+
None,
211218
ProbeScope::TraitsInScope,
212219
) {
213220
Ok(ref new_pick) if pick.differs_from(new_pick) => {
@@ -276,6 +283,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
276283
method_name: Ident,
277284
self_ty: Ty<'tcx>,
278285
call_expr: &hir::Expr<'_>,
286+
expected_def_id: Option<DefId>,
279287
scope: ProbeScope,
280288
) -> probe::PickResult<'tcx> {
281289
let pick = self.probe_for_name(
@@ -285,6 +293,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
285293
IsSuggestion(false),
286294
self_ty,
287295
call_expr.hir_id,
296+
expected_def_id,
288297
scope,
289298
)?;
290299
pick.maybe_emit_unstable_name_collision_hint(self.tcx, method_name.span, call_expr.hir_id);
@@ -306,6 +315,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
306315
IsSuggestion(true),
307316
self_ty,
308317
call_expr.hir_id,
318+
None,
309319
scope,
310320
)?;
311321
Ok(pick)
@@ -529,6 +539,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
529539
IsSuggestion(false),
530540
self_ty,
531541
expr_id,
542+
None,
532543
ProbeScope::TraitsInScope,
533544
);
534545
let pick = match (pick, struct_variant) {

compiler/rustc_hir_typeck/src/method/probe.rs

+34
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,27 @@ pub(crate) struct ProbeContext<'a, 'tcx> {
9797

9898
scope_expr_id: HirId,
9999

100+
/// Delegation item can be expanded into method calls or fully qualified calls
101+
/// depending on the callee's signature. Method calls are used to allow
102+
/// autoref/autoderef for target expression. For example in:
103+
///
104+
/// ```ignore (illustrative)
105+
/// trait Trait : Sized {
106+
/// fn by_value(self) -> i32 { 1 }
107+
/// fn by_mut_ref(&mut self) -> i32 { 2 }
108+
/// fn by_ref(&self) -> i32 { 3 }
109+
/// }
110+
///
111+
/// struct NewType(SomeType);
112+
/// impl Trait for NewType {
113+
/// reuse Trait::* { self.0 }
114+
/// }
115+
/// ```
116+
///
117+
/// `self.0` will automatically coerce. The difference with existing method lookup
118+
/// is that methods in delegation items are pre-resolved by callee path (`Trait::*`).
119+
expected_def_id: Option<DefId>,
120+
100121
/// Is this probe being done for a diagnostic? This will skip some error reporting
101122
/// machinery, since we don't particularly care about, for example, similarly named
102123
/// candidates if we're *reporting* similarly named candidates.
@@ -249,6 +270,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
249270
IsSuggestion(true),
250271
self_ty,
251272
scope_expr_id,
273+
None,
252274
ProbeScope::AllTraits,
253275
|probe_cx| Ok(probe_cx.candidate_method_names(candidate_filter)),
254276
)
@@ -264,6 +286,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
264286
IsSuggestion(true),
265287
self_ty,
266288
scope_expr_id,
289+
None,
267290
ProbeScope::AllTraits,
268291
|probe_cx| probe_cx.pick(),
269292
)
@@ -282,6 +305,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
282305
is_suggestion: IsSuggestion,
283306
self_ty: Ty<'tcx>,
284307
scope_expr_id: HirId,
308+
expected_def_id: Option<DefId>,
285309
scope: ProbeScope,
286310
) -> PickResult<'tcx> {
287311
self.probe_op(
@@ -292,6 +316,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
292316
is_suggestion,
293317
self_ty,
294318
scope_expr_id,
319+
expected_def_id,
295320
scope,
296321
|probe_cx| probe_cx.pick(),
297322
)
@@ -316,6 +341,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
316341
is_suggestion,
317342
self_ty,
318343
scope_expr_id,
344+
None,
319345
scope,
320346
|probe_cx| {
321347
Ok(probe_cx
@@ -336,6 +362,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
336362
is_suggestion: IsSuggestion,
337363
self_ty: Ty<'tcx>,
338364
scope_expr_id: HirId,
365+
expected_def_id: Option<DefId>,
339366
scope: ProbeScope,
340367
op: OP,
341368
) -> Result<R, MethodError<'tcx>>
@@ -477,6 +504,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
477504
&orig_values,
478505
steps.steps,
479506
scope_expr_id,
507+
expected_def_id,
480508
is_suggestion,
481509
);
482510

@@ -572,6 +600,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
572600
orig_steps_var_values: &'a OriginalQueryValues<'tcx>,
573601
steps: &'tcx [CandidateStep<'tcx>],
574602
scope_expr_id: HirId,
603+
expected_def_id: Option<DefId>,
575604
is_suggestion: IsSuggestion,
576605
) -> ProbeContext<'a, 'tcx> {
577606
ProbeContext {
@@ -591,6 +620,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
591620
static_candidates: RefCell::new(Vec::new()),
592621
unsatisfied_predicates: RefCell::new(Vec::new()),
593622
scope_expr_id,
623+
expected_def_id,
594624
is_suggestion,
595625
}
596626
}
@@ -1221,6 +1251,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
12211251
) -> Option<PickResult<'tcx>> {
12221252
let mut applicable_candidates: Vec<_> = candidates
12231253
.iter()
1254+
.filter(|candidate| {
1255+
!matches!(self.expected_def_id, Some(def_id) if def_id != candidate.item.def_id)
1256+
})
12241257
.map(|probe| {
12251258
(probe, self.consider_probe(self_ty, probe, possibly_unsatisfied_predicates))
12261259
})
@@ -1679,6 +1712,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
16791712
self.orig_steps_var_values,
16801713
self.steps,
16811714
self.scope_expr_id,
1715+
None,
16821716
IsSuggestion(true),
16831717
);
16841718
pcx.allow_similar_names = true;

compiler/rustc_hir_typeck/src/method/suggest.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2034,6 +2034,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
20342034
IsSuggestion(true),
20352035
rcvr_ty,
20362036
expr_id,
2037+
None,
20372038
ProbeScope::TraitsInScope,
20382039
)
20392040
.is_ok()
@@ -3095,6 +3096,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
30953096
IsSuggestion(true),
30963097
deref_ty,
30973098
ty.hir_id,
3099+
None,
30983100
ProbeScope::TraitsInScope,
30993101
) {
31003102
if deref_ty.is_suggestable(self.tcx, true)

compiler/rustc_resolve/src/late.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -3317,14 +3317,32 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
33173317
self.visit_ty(&qself.ty);
33183318
}
33193319
self.visit_path(&delegation.path, delegation.id);
3320+
let last_ident = delegation.path.segments.last().unwrap().ident;
3321+
3322+
// Saving traits for a `MethodCall` that has not yet been generated.
3323+
// Traits found in the path are also considered visible:
3324+
//
3325+
// impl Trait for Type {
3326+
// reuse inner::TraitFoo::*; // OK, even `TraitFoo` is not in scope.
3327+
// }
3328+
let mut traits = self.traits_in_scope(last_ident, ValueNS);
3329+
for segment in &delegation.path.segments {
3330+
if let Some(partial_res) = self.r.partial_res_map.get(&segment.id)
3331+
&& let Some(def_id) = partial_res.full_res().and_then(|res| res.opt_def_id())
3332+
&& self.r.tcx.def_kind(def_id) == DefKind::Trait
3333+
{
3334+
traits.push(TraitCandidate { def_id, import_ids: smallvec![] });
3335+
}
3336+
}
3337+
self.r.trait_map.insert(delegation.id, traits);
3338+
33203339
if let Some(body) = &delegation.body {
33213340
self.with_rib(ValueNS, RibKind::FnOrCoroutine, |this| {
33223341
// `PatBoundCtx` is not necessary in this context
33233342
let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())];
33243343

3325-
let span = delegation.path.segments.last().unwrap().ident.span;
33263344
this.fresh_binding(
3327-
Ident::new(kw::SelfLower, span),
3345+
Ident::new(kw::SelfLower, last_ident.span),
33283346
delegation.id,
33293347
PatternSource::FnParam,
33303348
&mut bindings,

tests/ui/delegation/bad-resolve.rs

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ impl Trait for S {
3434

3535
reuse foo { &self.0 }
3636
//~^ ERROR cannot find function `foo` in this scope
37+
reuse Trait::foo2 { self.0 }
38+
//~^ ERROR cannot find function `foo2` in trait `Trait`
39+
//~| ERROR method `foo2` is not a member of trait `Trait`
3740
}
3841

3942
mod prefix {}

0 commit comments

Comments
 (0)