Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3908,6 +3908,13 @@ pub struct EiiImpl {
pub is_default: bool,
}

#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub enum DelegationSource {
Single,
List,
Glob,
}

#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub struct Delegation {
/// Path resolution id.
Expand All @@ -3918,7 +3925,7 @@ pub struct Delegation {
pub rename: Option<Ident>,
pub body: Option<Box<Block>>,
/// The item was expanded from a glob delegation item.
pub from_glob: bool,
pub source: DelegationSource,
}

#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,9 @@ macro_rules! common_visitor_and_walkers {
ConstItem,
ConstItemRhsKind,
Defaultness,
Delegation,
DelegationMac,
DelegationSuffixes,
DelegationSource,
DelimArgs,
DelimSpan,
EnumDef,
Expand Down Expand Up @@ -573,6 +573,7 @@ macro_rules! common_visitor_and_walkers {
fn visit_fn_decl(FnDecl);
fn visit_fn_header(FnHeader);
fn visit_fn_ret_ty(FnRetTy);
fn visit_delegation(Delegation);
//fn visit_foreign_item(ForeignItem);
fn visit_foreign_mod(ForeignMod);
fn visit_format_args(FormatArgs);
Expand Down
79 changes: 61 additions & 18 deletions compiler/rustc_ast_lowering/src/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ use rustc_span::{Ident, Span, Symbol};
use smallvec::SmallVec;

use crate::delegation::generics::{GenericsGenerationResult, GenericsGenerationResults};
use crate::errors::{CycleInDelegationSignatureResolution, UnresolvedDelegationCallee};
use crate::errors::{
CycleInDelegationSignatureResolution, DelegationAttemptedBlockWithDefsDeletion,
DelegationBlockSpecifiedWhenNoParams, UnresolvedDelegationCallee,
};
use crate::{
AllowReturnTypeNotation, ImplTraitContext, ImplTraitPosition, LoweringContext, ParamMode,
ResolverAstLoweringExt,
Expand Down Expand Up @@ -122,9 +125,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
let span = self.lower_span(delegation.path.segments.last().unwrap().ident.span);

// Delegation can be unresolved in illegal places such as function bodies in extern blocks (see #151356)
let sig_id = if let Some(delegation_info) = self.resolver.delegation_info(self.owner.def_id)
let sig_id = if let Some(resolution_id) =
self.resolver.delegation_info(self.owner.def_id).and_then(|i| i.resolution_id)
{
self.get_sig_id(delegation_info.resolution_id, span)
self.get_sig_id(resolution_id, span)
} else {
self.dcx().span_delayed_bug(
span,
Expand All @@ -142,11 +146,15 @@ impl<'hir> LoweringContext<'_, 'hir> {

let (param_count, c_variadic) = self.param_count(sig_id);

if !self.check_block_soundness(delegation, sig_id, is_method, param_count) {
return self.generate_delegation_error(span, delegation);
Copy link
Copy Markdown
Contributor

@petrochenkov petrochenkov Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to #157248 (comment) this is sub-optimal from error recovery / diagnostics point of view, but that's not a first concern right now.

View changes since the review

}

let mut generics = self.uplift_delegation_generics(delegation, sig_id, is_method);

let (body_id, call_expr_id) = self.lower_delegation_body(
delegation,
is_method,
sig_id,
param_count,
&mut generics,
span,
Expand Down Expand Up @@ -179,6 +187,48 @@ impl<'hir> LoweringContext<'_, 'hir> {
}
}

fn check_block_soundness(
&self,
delegation: &Delegation,
sig_id: DefId,
is_method: bool,
param_count: usize,
) -> bool {
let Some(block) = delegation.body.as_ref() else { return true };

// Report an error if user has explicitly specified delegation's target expression
// in a single delegation when reused function has no params.
if param_count == 0 && matches!(delegation.source, DelegationSource::Single) {
Copy link
Copy Markdown
Contributor

@petrochenkov petrochenkov Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if param_count == 0 && matches!(delegation.source, DelegationSource::Single) {
if param_count == 0 && should_generate_block {

Otherwise we are missing e.g. a free function with zero params in a list.

View changes since the review

self.dcx().emit_err(DelegationBlockSpecifiedWhenNoParams { span: block.span });
return false;
}

// If there are definitions inside and we can't delete target expression, so report an error.
// FIXME(fn_delegation): support deletion of target expression with defs inside.
if !self.should_generate_block(delegation, sig_id, is_method)
&& self
.resolver
.delegation_info(self.owner.def_id)
.is_some_and(|i| i.block_contains_defs)
Copy link
Copy Markdown
Contributor

@petrochenkov petrochenkov Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it possible to detect this post-factum, instead of tracking things in advance through def collector?
Like add a small AST visitor to visit delegation.body and check if there's anything with definitions?
This visitor would only run for delegations where performance is not much of a concern at the moment.
This would avoid polluting the general-purpose infra like def collector with delegation-specific concerns.

View changes since the review

{
self.dcx().emit_err(DelegationAttemptedBlockWithDefsDeletion { span: block.span });
return false;
}

true
}

fn should_generate_block(
&self,
delegation: &Delegation,
sig_id: DefId,
is_method: bool,
) -> bool {
is_method
|| matches!(self.tcx.def_kind(sig_id), DefKind::Fn)
|| matches!(delegation.source, DelegationSource::Single)
}

fn add_attrs_if_needed(&mut self, span: Span, sig_id: DefId) {
let new_attrs =
self.create_new_attrs(ATTRS_ADDITIONS, span, sig_id, self.attrs.get(&PARENT_ID));
Expand Down Expand Up @@ -243,9 +293,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
// it means that we refer to another delegation as a callee, so in order to obtain
// a signature DefId we obtain NodeId of the callee delegation and try to get signature from it.
if let Some(local_id) = def_id.as_local()
&& let Some(delegation_info) = self.resolver.delegation_info(local_id)
&& let Some(resolution_id) =
self.resolver.delegation_info(local_id).and_then(|i| i.resolution_id)
{
def_id = delegation_info.resolution_id;
def_id = resolution_id;
if visited.contains(&def_id) {
// We encountered a cycle in the resolution, or delegation callee refers to non-existent
// entity, in this case emit an error.
Expand Down Expand Up @@ -397,7 +448,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
fn lower_delegation_body(
&mut self,
delegation: &Delegation,
is_method: bool,
sig_id: DefId,
param_count: usize,
generics: &mut GenericsGenerationResults<'hir>,
span: Span,
Expand All @@ -409,12 +460,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
let mut parameters: Vec<hir::Param<'_>> = Vec::with_capacity(param_count);
let mut args: Vec<hir::Expr<'_>> = Vec::with_capacity(param_count);

let is_method = this.is_method(sig_id, span);

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

let arg = if let Some(block) = block
&& idx == 0
&& this.should_generate_block(delegation, sig_id, is_method)
{
let mut self_resolver = SelfResolver {
ctxt: this,
Expand All @@ -431,17 +485,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
args.push(arg);
}

// If we have no params in signature function but user still wrote some code in
// delegation body, then add this code as first arg, eventually an error will be shown,
// also nested delegations may need to access information about this code (#154332),
// so it is better to leave this code as opposed to bodies of extern functions,
// which are completely erased from existence.
if param_count == 0
&& let Some(block) = block
{
args.push(this.lower_target_expr(&block));
}

let (final_expr, hir_id) =
this.finalize_body_lowering(delegation, args, generics, span);

Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_ast_lowering/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,3 +535,17 @@ pub(crate) struct CycleInDelegationSignatureResolution {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("delegation's target expression is specified for function with no params")]
pub(crate) struct DelegationBlockSpecifiedWhenNoParams {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("attempted to delete delegation's target expression that contains definitions inside")]
pub(crate) struct DelegationAttemptedBlockWithDefsDeletion {
#[primary_span]
pub span: Span,
}
6 changes: 5 additions & 1 deletion compiler/rustc_expand/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2066,7 +2066,11 @@ fn build_single_delegations<'a, Node: InvocationCollectorNode>(
ident: rename.unwrap_or(ident),
rename,
body: deleg.body.clone(),
from_glob,
source: if from_glob {
ast::DelegationSource::Glob
} else {
ast::DelegationSource::List
},
})),
tokens: None,
}
Expand Down
5 changes: 3 additions & 2 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 +262,15 @@ pub struct ResolverAstLowering<'tcx> {
pub disambiguators: LocalDefIdMap<Steal<PerParentDisambiguatorState>>,
}

#[derive(Debug)]
#[derive(Debug, Default)]
pub struct DelegationInfo {
// `DefId` (either the resolution at delegation.id or item_id in case of a trait impl) for signature resolution,
// for details see https://github.com/rust-lang/rust/issues/118212#issuecomment-2160686914
/// Refers to the next element in a delegation resolution chain.
/// Usually points to the final resolution, as most "chains" are just
/// one step to a trait or an impl.
pub resolution_id: DefId,
pub resolution_id: Option<DefId>,
pub block_contains_defs: bool,
}

#[derive(Clone, Copy, Debug, StableHash)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ impl<'a> Parser<'a> {
ident,
rename,
body: self.parse_delegation_body()?,
from_glob: false,
source: DelegationSource::Single,
}))
})
}
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_resolve/src/build_reduced_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use std::sync::Arc;

use rustc_ast::visit::{self, AssocCtxt, Visitor, WalkItemKind};
use rustc_ast::{
self as ast, AssocItem, AssocItemKind, Block, ConstItem, DUMMY_NODE_ID, Delegation, Fn,
ForeignItem, ForeignItemKind, Inline, Item, ItemKind, NodeId, StaticItem, StmtKind, TraitAlias,
TyAlias,
self as ast, AssocItem, AssocItemKind, Block, ConstItem, DUMMY_NODE_ID, Delegation,
DelegationSource, Fn, ForeignItem, ForeignItemKind, Inline, Item, ItemKind, NodeId, StaticItem,
StmtKind, TraitAlias, TyAlias,
};
use rustc_attr_parsing::AttributeParser;
use rustc_expand::base::{ResolverExpand, SyntaxExtension, SyntaxExtensionKind};
Expand Down Expand Up @@ -1461,7 +1461,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
let parent = self.parent_scope.module.expect_local();
let expansion = self.parent_scope.expansion;
self.r.define_local(parent, ident, ns, self.res(def_id), vis, item.span, expansion);
} else if !matches!(&item.kind, AssocItemKind::Delegation(deleg) if deleg.from_glob)
} else if !matches!(&item.kind, AssocItemKind::Delegation(d) if matches!(d.source, DelegationSource::Glob))
Copy link
Copy Markdown
Contributor

@petrochenkov petrochenkov Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if !matches!(&item.kind, AssocItemKind::Delegation(d) if matches!(d.source, DelegationSource::Glob))
} else if let AssocItemKind::Delegation(d) = &item.kind && d.source == DelegationSource::Glob

View changes since the review

&& ident.name != kw::Underscore
{
// Don't add underscore names, they cannot be looked up anyway.
Expand Down
56 changes: 43 additions & 13 deletions compiler/rustc_resolve/src/def_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,39 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
name: Option<Symbol>,
def_kind: DefKind,
span: Span,
) -> TyCtxtFeed<'tcx, LocalDefId> {
self.create_feed(node_id, name, def_kind, span, false)
}

fn create_feed(
&mut self,
node_id: NodeId,
name: Option<Symbol>,
def_kind: DefKind,
span: Span,
is_owner: bool,
) -> TyCtxtFeed<'tcx, LocalDefId> {
let parent_def = self.invocation_parent.parent_def;
debug!(
"create_def(node_id={:?}, def_kind={:?}, parent_def={:?})",
node_id, def_kind, parent_def
);
self.r.create_def(

let feed = self.r.create_def(
parent_def,
node_id,
name,
def_kind,
self.parent_scope.expansion.to_expn_id(),
span.with_parent(None),
false,
)
is_owner,
);

if let Some(last_delegation) = self.invocation_parent.last_delegation {
self.r.delegation_infos.entry(last_delegation).or_default().block_contains_defs = true;
}

feed
}

fn with_parent<F: FnOnce(&mut Self)>(&mut self, parent_def: LocalDefId, f: F) {
Expand All @@ -86,16 +104,8 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> {
// We only get here if the owner didn't exist yet. After the owner has been created,
// future invocations of `collect_definitions` will get the owner out of the `owners`
// table.
let parent_def = self.invocation_parent.parent_def;
let feed = self.r.create_def(
parent_def,
owner,
name,
def_kind,
self.parent_scope.expansion.to_expn_id(),
span.with_parent(None),
true,
);

let feed = self.create_feed(owner, name, def_kind, span, true);
let tables = PerOwnerResolverData::new(owner, feed.key());

let orig_invoc_owner = mem::replace(&mut self.invocation_parent.owner, owner);
Expand Down Expand Up @@ -240,6 +250,26 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> {
);
}

fn visit_delegation(&mut self, node: &'a Delegation) {
// Do not map defs in path (i.e., consts with blocks), consider only
// delegation's target expression which is its first argument.
self.visit_path(&node.path);
if let Some(qself) = node.qself.as_ref() {
self.visit_qself(qself);
}

let orig_last_delegation = mem::replace(
&mut self.invocation_parent.last_delegation,
Some(self.invocation_parent.parent_def),
);

if let Some(block) = node.body.as_ref() {
self.visit_block(block);
}

self.invocation_parent.last_delegation = orig_last_delegation;
}

fn visit_block(&mut self, block: &'a Block) {
self.brg_visit_block(block);
}
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use rustc_hir::def::{CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRe
use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId};
use rustc_hir::{MissingLifetimeKind, PrimTy};
use rustc_middle::middle::resolve_bound_vars::Set1;
use rustc_middle::ty::{AssocTag, DelegationInfo, Visibility};
use rustc_middle::ty::{AssocTag, Visibility};
use rustc_middle::{bug, span_bug};
use rustc_session::config::{CrateType, ResolveDocLinks};
use rustc_session::errors::feature_err;
Expand Down Expand Up @@ -3905,10 +3905,10 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
.partial_res_map
.get(&resolution_id)
.and_then(|r| r.expect_full_res().opt_def_id());

if let Some(resolution_id) = def_id {
self.r
.delegation_infos
.insert(self.r.current_owner.def_id, DelegationInfo { resolution_id });
let info = self.r.delegation_infos.entry(self.r.current_owner.def_id).or_default();
info.resolution_id = Some(resolution_id);
} else {
self.r.tcx.dcx().span_delayed_bug(
delegation.path.span,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ struct InvocationParent {
in_attr: bool,
const_arg_context: ConstArgContext,
owner: NodeId,
last_delegation: Option<LocalDefId>,
}

impl InvocationParent {
Expand All @@ -197,6 +198,7 @@ impl InvocationParent {
in_attr: false,
const_arg_context: ConstArgContext::NonDirect,
owner: CRATE_NODE_ID,
last_delegation: None,
};
}

Expand Down
Loading
Loading