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 24, 2024
1 parent 894f7a4 commit 1e5a473
Show file tree
Hide file tree
Showing 16 changed files with 313 additions and 102 deletions.
191 changes: 117 additions & 74 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 @@ -66,17 +66,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
let Ok(sig_id) = sig_id else {
return false;
};
if let Some(local_sig_id) = sig_id.as_local() {
// The value may be missing due to recursive delegation.
// Error will be emmited later during HIR ty lowering.
self.resolver.delegation_fn_sigs.get(&local_sig_id).map_or(false, |sig| sig.has_self)
} else {
match self.tcx.def_kind(sig_id) {
DefKind::Fn => false,
DefKind::AssocFn => self.tcx.associated_item(sig_id).fn_has_self_parameter,
_ => span_bug!(span, "unexpected DefKind for delegation item"),
}
}
self.has_self(sig_id, span)
}

pub(crate) fn lower_delegation(
Expand Down Expand Up @@ -107,12 +97,29 @@ impl<'hir> LoweringContext<'_, 'hir> {
span: Span,
) -> Result<DefId, ErrorGuaranteed> {
let sig_id = if self.is_in_trait_impl { item_id } else { path_id };
let sig_id =
self.resolver.get_partial_res(sig_id).and_then(|r| r.expect_full_res().opt_def_id());
sig_id.ok_or_else(|| {
self.tcx
.dcx()
.span_delayed_bug(span, "LoweringContext: couldn't resolve delegation item")
self.get_resolution_id(sig_id, span)
}

fn has_self(&self, def_id: DefId, span: Span) -> bool {
if let Some(local_sig_id) = def_id.as_local() {
self.resolver.delegation_fn_sigs.get(&local_sig_id).map_or(false, |sig| sig.has_self)
} else {
match self.tcx.def_kind(def_id) {
DefKind::Fn => false,
DefKind::AssocFn => self.tcx.associated_item(def_id).fn_has_self_parameter,
_ => span_bug!(span, "unexpected DefKind for delegation item"),
}
}
}

fn get_resolution_id(&self, node_id: NodeId, span: Span) -> Result<DefId, ErrorGuaranteed> {
let def_id =
self.resolver.get_partial_res(node_id).and_then(|r| r.expect_full_res().opt_def_id());
def_id.ok_or_else(|| {
self.tcx.dcx().span_delayed_bug(
span,
format!("LoweringContext: couldn't resolve node {:?} in delegation item", node_id),
)
})
}

Expand All @@ -122,7 +129,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
predicates: &[],
has_where_clause_predicates: false,
where_clause_span: span,
span: span,
span,
})
}

Expand Down Expand Up @@ -222,12 +229,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
}));

let path = self.arena.alloc(hir::Path { span, res: Res::Local(param_id), segments });

hir::Expr {
hir_id: self.next_id(),
kind: hir::ExprKind::Path(hir::QPath::Resolved(None, path)),
span,
}
self.mk_expr(hir::ExprKind::Path(hir::QPath::Resolved(None, path)), span)
}

fn lower_delegation_body(
Expand All @@ -236,25 +238,18 @@ impl<'hir> LoweringContext<'_, 'hir> {
param_count: usize,
span: Span,
) -> BodyId {
let path = self.lower_qpath(
delegation.id,
&delegation.qself,
&delegation.path,
ParamMode::Optional,
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
None,
);
let block = delegation.body.as_deref();

self.lower_body(|this| {
let mut parameters: Vec<hir::Param<'_>> = Vec::new();
let mut args: Vec<hir::Expr<'hir>> = Vec::new();
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 @@ -263,56 +258,103 @@ impl<'hir> LoweringContext<'_, 'hir> {
self_param_id: pat_node_id,
};
self_resolver.visit_block(block);
let block = this.lower_block(block, false);
hir::Expr {
hir_id: this.next_id(),
kind: hir::ExprKind::Block(block, None),
span: block.span,
}
target_expr = Some(this.lower_block_noalloc(block, false));
} 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 args = self.arena.alloc_from_iter(args);
let final_expr = this.generate_call(path, args);
let final_expr = this.finalize_body_lowering(delegation, target_expr, args, span);
(this.arena.alloc_from_iter(parameters), final_expr)
})
}

fn generate_call(
// Generates expression for the resulting body. If possible, `MethodCall` is used
// instead of fully qualified call for the self type coercion. See `consider_candidates`
// for more information.
fn finalize_body_lowering(
&mut self,
path: hir::QPath<'hir>,
args: &'hir [hir::Expr<'hir>],
delegation: &Delegation,
target_expr: Option<hir::Block<'hir>>,
mut args: Vec<hir::Expr<'hir>>,
span: Span,
) -> hir::Expr<'hir> {
let callee = self.arena.alloc(hir::Expr {
hir_id: self.next_id(),
kind: hir::ExprKind::Path(path),
span: path.span(),
});
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()
{
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 {
// Use unit type as a receiver if no expression is provided.
let receiver = target_expr.expr.unwrap_or_else(|| {
self.arena.alloc(self.mk_expr(hir::ExprKind::Tup(&[]), span))
});
(target_expr.stmts, receiver, args, target_expr.hir_id)
} else {
(&*self.arena.alloc_from_iter([]), &args[0], &args[1..], self.next_id())
};

let expr = self.arena.alloc(hir::Expr {
hir_id: self.next_id(),
kind: hir::ExprKind::Call(callee, args),
span: path.span(),
});
let method_call_id = self.next_id();
if let Some(traits) = self.resolver.delegation_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));

(&*self.arena.alloc_from_iter([]), call, self.next_id())
};
let block = self.arena.alloc(hir::Block {
stmts: &[],
expr: Some(expr),
hir_id: self.next_id(),
stmts,
expr: Some(call),
hir_id: block_id,
rules: hir::BlockCheckMode::DefaultBlock,
span: path.span(),
span,
targeted_by_break: false,
});

hir::Expr {
hir_id: self.next_id(),
kind: hir::ExprKind::Block(block, None),
span: path.span(),
}
self.mk_expr(hir::ExprKind::Block(block, None), span)
}

fn generate_delegation_error(
Expand All @@ -333,11 +375,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
let header = self.generate_header_error();
let sig = hir::FnSig { decl, header, span };

let body_id = self.lower_body(|this| {
let expr =
hir::Expr { hir_id: this.next_id(), kind: hir::ExprKind::Err(err), span: span };
(&[], expr)
});
let body_id = self.lower_body(|this| (&[], this.mk_expr(hir::ExprKind::Err(err), span)));
DelegationResults { generics, body_id, sig }
}

Expand All @@ -349,6 +387,11 @@ impl<'hir> LoweringContext<'_, 'hir> {
abi: abi::Abi::Rust,
}
}

#[inline]
fn mk_expr(&mut self, kind: hir::ExprKind<'hir>, span: Span) -> hir::Expr<'hir> {
hir::Expr { hir_id: self.next_id(), kind, span }
}
}

struct SelfResolver<'a> {
Expand Down
46 changes: 46 additions & 0 deletions compiler/rustc_hir_typeck/src/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1218,8 +1218,54 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
)>,
mut unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Symbol)>>,
) -> Option<PickResult<'tcx>> {
// 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:
//
// 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::*`).
// Therefore, we are only forced to consider the pre-resolved method here.
let delegation_res = if self.fcx.tcx.hir().opt_delegation_sig_id(self.body_id).is_some() {
let body = self.tcx.hir().body_owned_by(self.body_id);
let block = match body.value.kind {
hir::ExprKind::Block(block, _) => block,
_ => unreachable!(),
};
let expr = block.expr.unwrap();
let res_id = match expr.kind {
hir::ExprKind::MethodCall(segment, ..) => segment.res.def_id(),
hir::ExprKind::Call(
hir::Expr { kind: hir::ExprKind::Path(hir::QPath::Resolved(_, path)), .. },
_,
) => path.res.def_id(),
_ => unreachable!(),
};
Some((expr.hir_id, res_id))
} else {
None
};
let mut applicable_candidates: Vec<_> = candidates
.iter()
.filter(|candidate| {
if let Some((expr_id, res_id)) = delegation_res
&& self.scope_expr_id == expr_id
&& candidate.item.def_id != res_id
{
return false;
}
true
})
.map(|probe| {
(probe, self.consider_probe(self_ty, probe, possibly_unsatisfied_predicates))
})
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_middle/src/hir/map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,21 @@ impl<'hir> Map<'hir> {
}
}

pub fn opt_delegation_sig_id(self, def_id: LocalDefId) -> Option<DefId> {
if let Some(ret) = self.get_fn_output(def_id)
&& let FnRetTy::Return(ty) = ret
&& let TyKind::InferDelegation(sig_id, _) = ty.kind
{
return Some(sig_id);
}
None
}

#[inline]
pub fn delegation_sig_id(self, def_id: LocalDefId) -> DefId {
self.opt_delegation_sig_id(def_id).unwrap()
}

#[inline]
fn opt_ident(self, id: HirId) -> Option<Ident> {
match self.tcx.hir_node(id) {
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,10 @@ pub struct ResolverAstLowering {
/// Lints that were emitted by the resolver and early lints.
pub lint_buffer: Steal<LintBuffer>,

/// Information about functions signatures for delegation items expansion
/// Information about functions signatures for delegation items expansion.
pub delegation_fn_sigs: LocalDefIdMap<DelegationFnSig>,
/// Separate `trait_map` for delegation items only.
pub delegation_trait_map: NodeMap<Vec<hir::TraitCandidate>>,
}

#[derive(Debug)]
Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3315,15 +3315,19 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
if let Some(qself) = &delegation.qself {
self.visit_ty(&qself.ty);
}
let last_segment = delegation.path.segments.last().unwrap();
let traits = self.traits_in_scope(last_segment.ident, ValueNS);
// Saving traits for a `MethodCall` that has not yet been generated.
self.r.delegation_trait_map.insert(delegation.id, traits);

self.visit_path(&delegation.path, delegation.id);
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_segment.ident.span),
delegation.id,
PatternSource::FnParam,
&mut bindings,
Expand Down
Loading

0 comments on commit 1e5a473

Please sign in to comment.