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 28, 2024
1 parent a4ce33c commit 02a1f30
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 36 deletions.
75 changes: 60 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,72 @@ impl<'hir> LoweringContext<'_, 'hir> {
})
}

// Generates fully qualified call for the resulting body.
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
// instead of fully qualified call for the self type coercion.
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,
None,
);
let segment = self.arena.alloc(segment);

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());
}

self.arena.alloc(hir::Expr {
hir_id: method_call_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
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<'_>,
expected_def_id: 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,
expected_def_id,
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
34 changes: 34 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::*`).
expected_def_id: 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,
expected_def_id: 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,
expected_def_id,
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,
expected_def_id: 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,
expected_def_id,
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,
expected_def_id: 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,
expected_def_id,
is_suggestion,
}
}
Expand Down Expand Up @@ -1220,6 +1250,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
) -> Option<PickResult<'tcx>> {
let mut applicable_candidates: Vec<_> = candidates
.iter()
.filter(|candidate| {
!matches!(self.expected_def_id, Some(def_id) if def_id != candidate.item.def_id)
})
.map(|probe| {
(probe, self.consider_probe(self_ty, probe, possibly_unsatisfied_predicates))
})
Expand Down Expand Up @@ -1677,6 +1710,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
22 changes: 20 additions & 2 deletions compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3316,14 +3316,32 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
self.visit_ty(&qself.ty);
}
self.visit_path(&delegation.path, delegation.id);
let last_ident = delegation.path.segments.last().unwrap().ident;

// Saving traits for a `MethodCall` that has not yet been generated.
// Traits found in the path are also considered visible:
//
// impl Trait for Type {
// reuse inner::TraitFoo::*; // OK, even `TraitFoo` is not in scope.
// }
let mut traits = self.traits_in_scope(last_ident, ValueNS);
for segment in &delegation.path.segments {
if let Some(partial_res) = self.r.partial_res_map.get(&segment.id)
&& let Some(res_id) = partial_res.full_res().and_then(|res| res.opt_def_id())
&& self.r.tcx.def_kind(res_id) == DefKind::Trait
{
traits.push(TraitCandidate { def_id: res_id, import_ids: smallvec![] });
}
}
self.r.trait_map.insert(delegation.id, traits);

if let Some(body) = &delegation.body {
self.with_rib(ValueNS, RibKind::FnOrCoroutine, |this| {
// `PatBoundCtx` is not necessary in this context
let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())];

let span = delegation.path.segments.last().unwrap().ident.span;
this.fresh_binding(
Ident::new(kw::SelfLower, span),
Ident::new(kw::SelfLower, last_ident.span),
delegation.id,
PatternSource::FnParam,
&mut bindings,
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
Loading

0 comments on commit 02a1f30

Please sign in to comment.