Skip to content

Commit

Permalink
Delegation: support coercion for target expression
Browse files Browse the repository at this point in the history
  • Loading branch information
Bryanskiy committed Jun 27, 2024
1 parent a4ce33c commit 64d78b4
Show file tree
Hide file tree
Showing 15 changed files with 296 additions and 59 deletions.
108 changes: 85 additions & 23 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 @@ -245,12 +245,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
self.lower_body(|this| {
let mut parameters: Vec<hir::Param<'_>> = Vec::with_capacity(param_count);
let mut args: Vec<hir::Expr<'_>> = Vec::with_capacity(param_count);
let mut target_expr = None;

for idx in 0..param_count {
let (param, pat_node_id) = this.generate_param(span);
parameters.push(param);

let arg = if let Some(block) = block
if let Some(block) = block
&& idx == 0
{
let mut self_resolver = SelfResolver {
Expand All @@ -259,44 +260,105 @@ 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)
let mut block = this.lower_block_noalloc(block, false);
if block.expr.is_none() {
let pat_hir_id = this.lower_node_id(pat_node_id);
let arg = &*this.arena.alloc(this.generate_arg(pat_hir_id, span));
// Use `self` as a first argument if no expression is provided.
block.expr = Some(arg);
}
target_expr = Some(block);
} else {
let pat_hir_id = this.lower_node_id(pat_node_id);
this.generate_arg(pat_hir_id, span)
let arg = this.generate_arg(pat_hir_id, span);
args.push(arg);
};
args.push(arg);
}

let final_expr = this.finalize_body_lowering(delegation, args, span);
let final_expr = this.finalize_body_lowering(delegation, target_expr, args, span);
(this.arena.alloc_from_iter(parameters), final_expr)
})
}

// Generates fully qualified call for the resulting body.
// Generates expression for the resulting body. If possible, `MethodCall` is used
// instead of fully qualified call for the self type coercion.
fn finalize_body_lowering(
&mut self,
delegation: &Delegation,
args: Vec<hir::Expr<'hir>>,
target_expr: Option<hir::Block<'hir>>,
mut 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 (stmts, call, block_id) = 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,
None,
);
let segment = self.arena.alloc(segment);

let args = &*self.arena.alloc_from_iter(args);
let (stmts, receiver, args, block_id) = if let Some(target_expr) = target_expr {
let receiver = target_expr.expr.unwrap();
(Some(target_expr.stmts), receiver, args, target_expr.hir_id)
} else {
(None, &args[0], &args[1..], self.next_id())
};

let method_call_id = self.next_id();
if let Some(traits) = self.resolver.trait_map.remove(&delegation.id) {
self.trait_map.insert(method_call_id.local_id, traits.into_boxed_slice());
}

let method_call = self.arena.alloc(hir::Expr {
hir_id: method_call_id,
kind: hir::ExprKind::MethodCall(segment, receiver, args, span),
span,
});

(stmts, method_call, block_id)
} else {
let path = self.lower_qpath(
delegation.id,
&delegation.qself,
&delegation.path,
ParamMode::Optional,
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
None,
);

if let Some(target_expr) = target_expr {
let target_expr = self.arena.alloc(target_expr);
let fst_arg =
self.mk_expr(hir::ExprKind::Block(target_expr, None), target_expr.span);
args.insert(0, fst_arg);
}

let args = self.arena.alloc_from_iter(args);
let callee_path = self.arena.alloc(self.mk_expr(hir::ExprKind::Path(path), span));

let call = self.arena.alloc(self.mk_expr(hir::ExprKind::Call(callee_path, args), span));

(None, call, self.next_id())
};
let block = self.arena.alloc(hir::Block {
stmts: &[],
stmts: stmts.unwrap_or(&[]),
expr: Some(call),
hir_id: self.next_id(),
hir_id: block_id,
rules: hir::BlockCheckMode::DefaultBlock,
span,
targeted_by_break: false,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(true),
callee_ty.peel_refs(),
callee_expr.unwrap().hir_id,
None,
TraitsInScope,
|mut ctxt| ctxt.probe_for_similar_candidate(),
)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1588,6 +1588,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(true),
self_ty,
expr.hir_id,
None,
ProbeScope::TraitsInScope,
)
{
Expand Down
15 changes: 13 additions & 2 deletions compiler/rustc_hir_typeck/src/method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(true),
self_ty,
call_expr_id,
None,
ProbeScope::TraitsInScope,
) {
Ok(pick) => {
Expand Down Expand Up @@ -182,8 +183,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 pick = self.lookup_probe(
segment.ident,
self_ty,
call_expr,
segment.res.opt_def_id(),
ProbeScope::TraitsInScope,
)?;

self.lint_edition_dependent_dot_call(
self_ty, segment, span, call_expr, self_expr, &pick, args,
Expand All @@ -208,6 +214,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
segment.ident,
trait_type,
call_expr,
None,
ProbeScope::TraitsInScope,
) {
Ok(ref new_pick) if pick.differs_from(new_pick) => {
Expand Down Expand Up @@ -276,6 +283,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
method_name: Ident,
self_ty: Ty<'tcx>,
call_expr: &hir::Expr<'_>,
pre_resolved_method: Option<DefId>,
scope: ProbeScope,
) -> probe::PickResult<'tcx> {
let pick = self.probe_for_name(
Expand All @@ -285,6 +293,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(false),
self_ty,
call_expr.hir_id,
pre_resolved_method,
scope,
)?;
pick.maybe_emit_unstable_name_collision_hint(self.tcx, method_name.span, call_expr.hir_id);
Expand All @@ -306,6 +315,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(true),
self_ty,
call_expr.hir_id,
None,
scope,
)?;
Ok(pick)
Expand Down Expand Up @@ -516,6 +526,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(false),
self_ty,
expr_id,
None,
ProbeScope::TraitsInScope,
);
let pick = match (pick, struct_variant) {
Expand Down
39 changes: 39 additions & 0 deletions compiler/rustc_hir_typeck/src/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ pub(crate) struct ProbeContext<'a, 'tcx> {

scope_expr_id: HirId,

/// Delegation item can be expanded into method calls or fully qualified calls
/// depending on the callee's signature. Method calls are used to allow
/// autoref/autoderef for target expression. For example in:
///
/// ```ignore (illustrative)
/// 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. The difference with existing method lookup
/// is that methods in delegation items are pre-resolved by callee path (`Trait::*`).
pre_resolved_method: Option<DefId>,

/// Is this probe being done for a diagnostic? This will skip some error reporting
/// machinery, since we don't particularly care about, for example, similarly named
/// candidates if we're *reporting* similarly named candidates.
Expand Down Expand Up @@ -248,6 +269,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(true),
self_ty,
scope_expr_id,
None,
ProbeScope::AllTraits,
|probe_cx| Ok(probe_cx.candidate_method_names(candidate_filter)),
)
Expand All @@ -263,6 +285,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(true),
self_ty,
scope_expr_id,
None,
ProbeScope::AllTraits,
|probe_cx| probe_cx.pick(),
)
Expand All @@ -281,6 +304,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
is_suggestion: IsSuggestion,
self_ty: Ty<'tcx>,
scope_expr_id: HirId,
pre_resolved_method: Option<DefId>,
scope: ProbeScope,
) -> PickResult<'tcx> {
self.probe_op(
Expand All @@ -291,6 +315,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
is_suggestion,
self_ty,
scope_expr_id,
pre_resolved_method,
scope,
|probe_cx| probe_cx.pick(),
)
Expand All @@ -315,6 +340,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
is_suggestion,
self_ty,
scope_expr_id,
None,
scope,
|probe_cx| {
Ok(probe_cx
Expand All @@ -335,6 +361,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
is_suggestion: IsSuggestion,
self_ty: Ty<'tcx>,
scope_expr_id: HirId,
pre_resolved_method: Option<DefId>,
scope: ProbeScope,
op: OP,
) -> Result<R, MethodError<'tcx>>
Expand Down Expand Up @@ -476,6 +503,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&orig_values,
steps.steps,
scope_expr_id,
pre_resolved_method,
is_suggestion,
);

Expand Down Expand Up @@ -571,6 +599,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
orig_steps_var_values: &'a OriginalQueryValues<'tcx>,
steps: &'tcx [CandidateStep<'tcx>],
scope_expr_id: HirId,
pre_resolved_method: Option<DefId>,
is_suggestion: IsSuggestion,
) -> ProbeContext<'a, 'tcx> {
ProbeContext {
Expand All @@ -590,6 +619,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
static_candidates: RefCell::new(Vec::new()),
unsatisfied_predicates: RefCell::new(Vec::new()),
scope_expr_id,
pre_resolved_method,
is_suggestion,
}
}
Expand Down Expand Up @@ -1220,6 +1250,14 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
) -> Option<PickResult<'tcx>> {
let mut applicable_candidates: Vec<_> = candidates
.iter()
.filter(|candidate| {
if let Some(res_id) = self.pre_resolved_method
&& candidate.item.def_id != res_id
{
return false;
}
true
})
.map(|probe| {
(probe, self.consider_probe(self_ty, probe, possibly_unsatisfied_predicates))
})
Expand Down Expand Up @@ -1677,6 +1715,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
self.orig_steps_var_values,
self.steps,
self.scope_expr_id,
None,
IsSuggestion(true),
);
pcx.allow_similar_names = true;
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2034,6 +2034,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(true),
rcvr_ty,
expr_id,
None,
ProbeScope::TraitsInScope,
)
.is_ok()
Expand Down Expand Up @@ -3095,6 +3096,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsSuggestion(true),
deref_ty,
ty.hir_id,
None,
ProbeScope::TraitsInScope,
) {
if deref_ty.is_suggestable(self.tcx, true)
Expand Down
Loading

0 comments on commit 64d78b4

Please sign in to comment.