Skip to main content

rustc_ast_lowering/delegation/
resolution.rs

1use std::ops::ControlFlow;
2
3use ast::visit::Visitor;
4use hir::def::DefKind;
5use rustc_ast as ast;
6use rustc_ast::*;
7use rustc_data_structures::fx::FxHashSet;
8use rustc_hir as hir;
9use rustc_middle::span_bug;
10use rustc_span::def_id::{DefId, LocalDefId};
11use rustc_span::{ErrorGuaranteed, Span};
12
13use crate::delegation::generics::GenericsGenerationResults;
14use crate::delegation::resolution::resolver::DelegationResolver;
15use crate::diagnostics::{
16    CycleInDelegationSignatureResolution, DelegationAttemptedBlockWithDefsDeletion,
17    DelegationBlockSpecifiedWhenNoParams, UnresolvedDelegationCallee,
18};
19
20/// Summary info about function parameters.
21#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParamInfo {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "ParamInfo",
            "param_count", &self.param_count, "c_variadic", &self.c_variadic,
            "splatted", &&self.splatted)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ParamInfo {
    #[inline]
    fn clone(&self) -> ParamInfo {
        let _: ::core::clone::AssertParamIsClone<usize>;
        let _: ::core::clone::AssertParamIsClone<bool>;
        let _: ::core::clone::AssertParamIsClone<Option<u8>>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ParamInfo { }Copy, #[automatically_derived]
impl ::core::cmp::Eq for ParamInfo {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<usize>;
        let _: ::core::cmp::AssertParamIsEq<bool>;
        let _: ::core::cmp::AssertParamIsEq<Option<u8>>;
    }
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for ParamInfo {
    #[inline]
    fn eq(&self, other: &ParamInfo) -> bool {
        self.c_variadic == other.c_variadic &&
                self.param_count == other.param_count &&
            self.splatted == other.splatted
    }
}PartialEq)]
22pub(super) struct ParamInfo {
23    /// The number of function parameters, including any C variadic `...` parameter.
24    pub param_count: usize,
25
26    /// Whether the function arguments end in a C variadic `...` parameter.
27    pub c_variadic: bool,
28
29    /// The index of the splatted parameter, if any.
30    pub splatted: Option<u8>,
31}
32
33pub(super) struct DelegationResolution {
34    pub sig_id: DefId,
35    pub is_method: bool,
36    pub param_info: ParamInfo,
37    pub span: Span,
38    pub should_generate_block: bool,
39    pub call_path_res: Option<DefId>,
40    pub source: DelegationSource,
41    pub output_self_mapping: Option<(LocalDefId, bool)>,
42}
43
44pub(super) mod resolver {
45    use rustc_ast::NodeId;
46    use rustc_hir::def_id::{DefId, LocalDefId};
47    use rustc_middle::ty::TyCtxt;
48
49    use crate::LoweringContext;
50
51    /// Abstracts operations that are needed for delegation's resolution, so resolution
52    /// is independent of `LoweringContext`. Placed in a separate module so `LoweringContext`
53    /// can not be accessed directly.
54    pub(crate) struct DelegationResolver<'a, 'hir>(&'a LoweringContext<'a, 'hir>);
55
56    impl<'a, 'tcx> DelegationResolver<'a, 'tcx> {
57        pub(crate) fn new(ctx: &'a LoweringContext<'a, 'tcx>) -> Self {
58            DelegationResolver(ctx)
59        }
60
61        #[inline]
62        pub(crate) fn tcx(&self) -> TyCtxt<'tcx> {
63            self.0.tcx
64        }
65
66        #[inline]
67        pub(crate) fn owner_id(&self) -> LocalDefId {
68            self.0.owner.def_id
69        }
70
71        /// (from `tests\ui\delegation\target-expr-removal-defs-inside.rs`):
72        /// ```rust
73        /// reuse impl Trait for S1 {
74        ///     some::path::<{ fn foo() {} }>::xd();
75        ///     fn foo() {}
76        ///     self.0
77        /// }
78        /// ```
79        ///
80        /// Constant from unresolved path will be in `node_id_to_def_id`,
81        /// `fn foo() {}` will not be in `node_id_to_def_id` but will be in `owners`,
82        /// both have `LocalDefId`, so we check those two maps.
83        #[inline]
84        pub(crate) fn is_definition(&self, id: NodeId) -> bool {
85            self.0.resolver.owners.contains_key(&id)
86                || self.0.owner.node_id_to_def_id.contains_key(&id)
87        }
88
89        #[inline]
90        pub(crate) fn get_resolution_id(&self, id: NodeId) -> Option<DefId> {
91            self.0.get_partial_res(id).and_then(|r| r.expect_full_res().opt_def_id())
92        }
93    }
94}
95
96impl<'tcx> DelegationResolver<'_, 'tcx> {
97    pub(super) fn resolve_delegation(
98        &self,
99        delegation: &Delegation,
100        span: Span,
101    ) -> Result<(DelegationResolution, GenericsGenerationResults<'tcx>), ErrorGuaranteed> {
102        let tcx = self.tcx();
103        let def_id = self.owner_id();
104
105        // Delegation can be missing from the `delegations_resolutions` table
106        // in illegal places such as function bodies in extern blocks (see #151356).
107        let sig_id = tcx
108            .resolutions(())
109            .delegation_infos
110            .get(&def_id)
111            .map(|info| {
112                info.resolution_id.and_then(|id| self.check_for_cycles(id, span).map(|_| id))
113            })
114            .unwrap_or_else(|| {
115                Err(tcx.dcx().span_delayed_bug(
116                    span,
117                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("delegation resolution record was not found for {0:?}",
                def_id))
    })format!("delegation resolution record was not found for {:?}", def_id),
118                ))
119            })?;
120
121        let is_method = match tcx.def_kind(sig_id) {
122            DefKind::Fn => false,
123            DefKind::AssocFn => tcx.associated_item(sig_id).is_method(),
124            _ => ::rustc_middle::util::bug::span_bug_fmt(span,
    format_args!("unexpected DefKind for delegation item"))span_bug!(span, "unexpected DefKind for delegation item"),
125        };
126
127        let sig = tcx.fn_sig(sig_id).skip_binder().skip_binder();
128        let param_count = sig.inputs().len() + usize::from(sig.c_variadic());
129
130        let res = DelegationResolution {
131            is_method,
132            span,
133            sig_id,
134            // FIXME(splat): use `sig.splatted()` once FnSig has it
135            param_info: ParamInfo { param_count, c_variadic: sig.c_variadic(), splatted: None },
136            should_generate_block: self.check_block_soundness(
137                delegation,
138                sig_id,
139                is_method,
140                param_count,
141            )?,
142            source: delegation.source,
143            call_path_res: self.get_resolution_id(delegation.id),
144            output_self_mapping: self.should_map_return_value(delegation),
145        };
146
147        Ok((res, self.resolve_and_generate_generics(delegation, sig_id)))
148    }
149
150    fn check_for_cycles(&self, mut def_id: DefId, span: Span) -> Result<(), ErrorGuaranteed> {
151        let tcx = self.tcx();
152        let mut visited: FxHashSet<DefId> = Default::default();
153
154        loop {
155            visited.insert(def_id);
156
157            // If def_id is in local crate and it corresponds to another delegation
158            // it means that we refer to another delegation as a callee, so in order to obtain
159            // a signature DefId we obtain NodeId of the callee delegation and try to get signature from it.
160            if let Some(local_id) = def_id.as_local()
161                && let Some(info) = tcx.resolutions(()).delegation_infos.get(&local_id)
162                && let Ok(id) = info.resolution_id
163            {
164                def_id = id;
165                if visited.contains(&def_id) {
166                    return Err(match visited.len() {
167                        1 => tcx.dcx().emit_err(UnresolvedDelegationCallee { span }),
168                        _ => tcx.dcx().emit_err(CycleInDelegationSignatureResolution { span }),
169                    });
170                }
171            } else {
172                return Ok(());
173            }
174        }
175    }
176
177    fn check_block_soundness(
178        &self,
179        delegation: &Delegation,
180        sig_id: DefId,
181        is_method: bool,
182        param_count: usize,
183    ) -> Result<bool, ErrorGuaranteed> {
184        let tcx = self.tcx();
185        let should_generate_block = is_method
186            || #[allow(non_exhaustive_omitted_patterns)] match tcx.def_kind(sig_id) {
    DefKind::Fn => true,
    _ => false,
}matches!(tcx.def_kind(sig_id), DefKind::Fn)
187            || #[allow(non_exhaustive_omitted_patterns)] match delegation.source {
    DelegationSource::Single => true,
    _ => false,
}matches!(delegation.source, DelegationSource::Single);
188
189        let Some(block) = &delegation.body else { return Ok(should_generate_block) };
190
191        // Report an error if user has explicitly specified delegation's target expression
192        // in a single delegation when reused function has no params.
193        if param_count == 0 && should_generate_block {
194            let err = DelegationBlockSpecifiedWhenNoParams { span: block.span };
195            return Err(tcx.dcx().emit_err(err));
196        }
197
198        struct DefinitionsFinder<'a, 'hir> {
199            ctx: &'a DelegationResolver<'a, 'hir>,
200        }
201
202        impl<'a> Visitor<'a> for DefinitionsFinder<'a, '_> {
203            type Result = ControlFlow<()>;
204
205            fn visit_id(&mut self, id: NodeId) -> Self::Result {
206                match self.ctx.is_definition(id) {
207                    true => ControlFlow::Break(()),
208                    false => ControlFlow::Continue(()),
209                }
210            }
211        }
212
213        let mut collector = DefinitionsFinder { ctx: self };
214
215        let contains_defs = collector.visit_block(block).is_break();
216
217        // If there are definitions inside and we can't delete target expression, then report an error.
218        // FIXME(fn_delegation): support deletion of target expression with defs inside.
219        if should_generate_block || !contains_defs {
220            Ok(should_generate_block)
221        } else {
222            Err(tcx.dcx().emit_err(DelegationAttemptedBlockWithDefsDeletion { span: block.span }))
223        }
224    }
225
226    fn should_map_return_value(&self, delegation: &Delegation) -> Option<(LocalDefId, bool)> {
227        // Heuristic: don't do wrapping if there is no target expression.
228        if delegation.body.is_none() {
229            return None;
230        }
231
232        let tcx = self.tcx();
233        let parent = tcx.local_parent(self.owner_id());
234        let parent_kind = tcx.def_kind(parent);
235
236        // Apply wrapping for delegations inside
237        // 1) Trait impls, as the return type of both signature function
238        //    and generated delegation has `Self` generic param returned
239        //    (checked below).
240        //    FIXME(fn_delegation): think of enabling wrapping in more scenarios:
241        //      trait-(impl)-to-free
242        //      trait-(impl)-to-inherent
243        //      inherent-to-free
244        // 2) Inherent methods when delegating to trait, as we change the type of
245        //    `Self` to type of struct or enum we delegate from.
246        if !#[allow(non_exhaustive_omitted_patterns)] match tcx.def_kind(parent) {
    DefKind::Impl { .. } => true,
    _ => false,
}matches!(tcx.def_kind(parent), DefKind::Impl { .. }) {
247            return None;
248        }
249
250        let is_trait_impl = parent_kind == DefKind::Impl { of_trait: true };
251
252        // Check that delegation path resolves to a trait AssocFn, not to a free method.
253        Some((parent, is_trait_impl)).filter(|_| {
254            self.get_resolution_id(delegation.id).is_some_and(|id| {
255                tcx.def_kind(id) == DefKind::AssocFn
256                    // Check that the return type of the callee is `Self` param.
257                    // After previous check we are sure that `sig_id` and `delegation.id`
258                    // point to the same function.
259                    && tcx.def_kind(tcx.parent(id)) == DefKind::Trait
260                    && tcx.fn_sig(id).skip_binder().output().skip_binder().is_param(0)
261            })
262        })
263    }
264}