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