rustc_hir_typeck/
fallback.rs

1use std::cell::OnceCell;
2use std::ops::ControlFlow;
3
4use rustc_data_structures::fx::FxHashSet;
5use rustc_data_structures::graph::vec_graph::VecGraph;
6use rustc_data_structures::graph::{self};
7use rustc_data_structures::unord::{UnordMap, UnordSet};
8use rustc_hir as hir;
9use rustc_hir::HirId;
10use rustc_hir::def::{DefKind, Res};
11use rustc_hir::def_id::DefId;
12use rustc_hir::intravisit::{InferKind, Visitor};
13use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
14use rustc_session::lint;
15use rustc_span::def_id::LocalDefId;
16use rustc_span::{DUMMY_SP, Span};
17use rustc_trait_selection::traits::{ObligationCause, ObligationCtxt};
18use tracing::debug;
19
20use crate::{FnCtxt, errors};
21
22#[derive(Copy, Clone)]
23pub(crate) enum DivergingFallbackBehavior {
24    /// Always fallback to `()` (aka "always spontaneous decay")
25    ToUnit,
26    /// Always fallback to `!` (which should be equivalent to never falling back + not making
27    /// never-to-any coercions unless necessary)
28    ToNever,
29    /// Don't fallback at all
30    NoFallback,
31}
32
33impl<'tcx> FnCtxt<'_, 'tcx> {
34    /// Performs type inference fallback, setting [`FnCtxt::diverging_fallback_has_occurred`]
35    /// if the never type fallback has occurred.
36    pub(super) fn type_inference_fallback(&self) {
37        debug!(
38            "type-inference-fallback start obligations: {:#?}",
39            self.fulfillment_cx.borrow_mut().pending_obligations()
40        );
41
42        // All type checking constraints were added, try to fallback unsolved variables.
43        self.select_obligations_where_possible(|_| {});
44
45        debug!(
46            "type-inference-fallback post selection obligations: {:#?}",
47            self.fulfillment_cx.borrow_mut().pending_obligations()
48        );
49
50        let fallback_occurred = self.fallback_types();
51
52        if fallback_occurred {
53            // if fallback occurred, previously stalled goals may make progress again
54            self.select_obligations_where_possible(|_| {});
55        }
56    }
57
58    fn fallback_types(&self) -> bool {
59        // Check if we have any unresolved variables. If not, no need for fallback.
60        let unresolved_variables = self.unresolved_variables();
61
62        if unresolved_variables.is_empty() {
63            return false;
64        }
65
66        let (diverging_fallback, diverging_fallback_ty) =
67            self.calculate_diverging_fallback(&unresolved_variables);
68
69        // We do fallback in two passes, to try to generate
70        // better error messages.
71        // The first time, we do *not* replace opaque types.
72        let mut fallback_occurred = false;
73        for ty in unresolved_variables {
74            debug!("unsolved_variable = {:?}", ty);
75            fallback_occurred |=
76                self.fallback_if_possible(ty, &diverging_fallback, diverging_fallback_ty);
77        }
78
79        fallback_occurred
80    }
81
82    /// Tries to apply a fallback to `ty` if it is an unsolved variable.
83    ///
84    /// - Unconstrained ints are replaced with `i32`.
85    ///
86    /// - Unconstrained floats are replaced with `f64`.
87    ///
88    /// - Non-numerics may get replaced with `()` or `!`, depending on how they
89    ///   were categorized by [`Self::calculate_diverging_fallback`], crate's
90    ///   edition, and the setting of `#![rustc_never_type_options(fallback = ...)]`.
91    ///
92    /// Fallback becomes very dubious if we have encountered
93    /// type-checking errors. In that case, fallback to Error.
94    ///
95    /// Sets [`FnCtxt::diverging_fallback_has_occurred`] if never type fallback
96    /// is performed during this call.
97    fn fallback_if_possible(
98        &self,
99        ty: Ty<'tcx>,
100        diverging_fallback: &UnordSet<Ty<'tcx>>,
101        diverging_fallback_ty: Ty<'tcx>,
102    ) -> bool {
103        // Careful: we do NOT shallow-resolve `ty`. We know that `ty`
104        // is an unsolved variable, and we determine its fallback
105        // based solely on how it was created, not what other type
106        // variables it may have been unified with since then.
107        //
108        // The reason this matters is that other attempts at fallback
109        // may (in principle) conflict with this fallback, and we wish
110        // to generate a type error in that case. (However, this
111        // actually isn't true right now, because we're only using the
112        // builtin fallback rules. This would be true if we were using
113        // user-supplied fallbacks. But it's still useful to write the
114        // code to detect bugs.)
115        //
116        // (Note though that if we have a general type variable `?T`
117        // that is then unified with an integer type variable `?I`
118        // that ultimately never gets resolved to a special integral
119        // type, `?T` is not considered unsolved, but `?I` is. The
120        // same is true for float variables.)
121        let fallback = match ty.kind() {
122            _ if let Some(e) = self.tainted_by_errors() => Ty::new_error(self.tcx, e),
123            ty::Infer(ty::IntVar(_)) => self.tcx.types.i32,
124            ty::Infer(ty::FloatVar(_)) => self.tcx.types.f64,
125            _ if diverging_fallback.contains(&ty) => {
126                self.diverging_fallback_has_occurred.set(true);
127                diverging_fallback_ty
128            }
129            _ => return false,
130        };
131        debug!("fallback_if_possible(ty={:?}): defaulting to `{:?}`", ty, fallback);
132
133        let span = ty.ty_vid().map_or(DUMMY_SP, |vid| self.infcx.type_var_origin(vid).span);
134        self.demand_eqtype(span, ty, fallback);
135        true
136    }
137
138    fn calculate_diverging_fallback(
139        &self,
140        unresolved_variables: &[Ty<'tcx>],
141    ) -> (UnordSet<Ty<'tcx>>, Ty<'tcx>) {
142        debug!("calculate_diverging_fallback({:?})", unresolved_variables);
143
144        let diverging_fallback_ty = match self.diverging_fallback_behavior {
145            DivergingFallbackBehavior::ToUnit => self.tcx.types.unit,
146            DivergingFallbackBehavior::ToNever => self.tcx.types.never,
147            DivergingFallbackBehavior::NoFallback => {
148                // the type doesn't matter, since no fallback will occur
149                return (UnordSet::new(), self.tcx.types.unit);
150            }
151        };
152
153        // Construct a coercion graph where an edge `A -> B` indicates
154        // a type variable is that is coerced
155        let coercion_graph = self.create_coercion_graph();
156
157        // Extract the unsolved type inference variable vids; note that some
158        // unsolved variables are integer/float variables and are excluded.
159        let unsolved_vids = unresolved_variables.iter().filter_map(|ty| ty.ty_vid());
160
161        // Compute the diverging root vids D -- that is, the root vid of
162        // those type variables that (a) are the target of a coercion from
163        // a `!` type and (b) have not yet been solved.
164        //
165        // These variables are the ones that are targets for fallback to
166        // either `!` or `()`.
167        let diverging_roots: UnordSet<ty::TyVid> = self
168            .diverging_type_vars
169            .borrow()
170            .items()
171            .map(|&ty| self.shallow_resolve(ty))
172            .filter_map(|ty| ty.ty_vid())
173            .map(|vid| self.root_var(vid))
174            .collect();
175        debug!(
176            "calculate_diverging_fallback: diverging_type_vars={:?}",
177            self.diverging_type_vars.borrow()
178        );
179        debug!("calculate_diverging_fallback: diverging_roots={:?}", diverging_roots);
180
181        // Find all type variables that are reachable from a diverging
182        // type variable. These will typically default to `!`, unless
183        // we find later that they are *also* reachable from some
184        // other type variable outside this set.
185        let mut diverging_vids = vec![];
186        for unsolved_vid in unsolved_vids {
187            let root_vid = self.root_var(unsolved_vid);
188            debug!(
189                "calculate_diverging_fallback: unsolved_vid={:?} root_vid={:?} diverges={:?}",
190                unsolved_vid,
191                root_vid,
192                diverging_roots.contains(&root_vid),
193            );
194            if diverging_roots.contains(&root_vid) {
195                diverging_vids.push(unsolved_vid);
196
197                debug!(
198                    "calculate_diverging_fallback: root_vid={:?} reaches {:?}",
199                    root_vid,
200                    graph::depth_first_search(&coercion_graph, root_vid).collect::<Vec<_>>()
201                );
202            }
203        }
204
205        debug!("obligations: {:#?}", self.fulfillment_cx.borrow_mut().pending_obligations());
206
207        let mut diverging_fallback = UnordSet::with_capacity(diverging_vids.len());
208        let unsafe_infer_vars = OnceCell::new();
209
210        self.lint_obligations_broken_by_never_type_fallback_change(
211            &diverging_vids,
212            &coercion_graph,
213        );
214
215        for &diverging_vid in &diverging_vids {
216            let diverging_ty = Ty::new_var(self.tcx, diverging_vid);
217            let root_vid = self.root_var(diverging_vid);
218
219            self.lint_never_type_fallback_flowing_into_unsafe_code(
220                &unsafe_infer_vars,
221                &coercion_graph,
222                root_vid,
223            );
224
225            diverging_fallback.insert(diverging_ty);
226        }
227
228        (diverging_fallback, diverging_fallback_ty)
229    }
230
231    fn lint_never_type_fallback_flowing_into_unsafe_code(
232        &self,
233        unsafe_infer_vars: &OnceCell<UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)>>,
234        coercion_graph: &VecGraph<ty::TyVid, true>,
235        root_vid: ty::TyVid,
236    ) {
237        let unsafe_infer_vars = unsafe_infer_vars.get_or_init(|| {
238            let unsafe_infer_vars = compute_unsafe_infer_vars(self, self.body_id);
239            debug!(?unsafe_infer_vars);
240            unsafe_infer_vars
241        });
242
243        let affected_unsafe_infer_vars =
244            graph::depth_first_search_as_undirected(&coercion_graph, root_vid)
245                .filter_map(|x| unsafe_infer_vars.get(&x).copied())
246                .collect::<Vec<_>>();
247
248        let sugg = self.try_to_suggest_annotations(&[root_vid], coercion_graph);
249
250        for (hir_id, span, reason) in affected_unsafe_infer_vars {
251            self.tcx.emit_node_span_lint(
252                lint::builtin::NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
253                hir_id,
254                span,
255                match reason {
256                    UnsafeUseReason::Call => {
257                        errors::NeverTypeFallbackFlowingIntoUnsafe::Call { sugg: sugg.clone() }
258                    }
259                    UnsafeUseReason::Method => {
260                        errors::NeverTypeFallbackFlowingIntoUnsafe::Method { sugg: sugg.clone() }
261                    }
262                    UnsafeUseReason::Path => {
263                        errors::NeverTypeFallbackFlowingIntoUnsafe::Path { sugg: sugg.clone() }
264                    }
265                    UnsafeUseReason::UnionField => {
266                        errors::NeverTypeFallbackFlowingIntoUnsafe::UnionField {
267                            sugg: sugg.clone(),
268                        }
269                    }
270                    UnsafeUseReason::Deref => {
271                        errors::NeverTypeFallbackFlowingIntoUnsafe::Deref { sugg: sugg.clone() }
272                    }
273                },
274            );
275        }
276    }
277
278    fn lint_obligations_broken_by_never_type_fallback_change(
279        &self,
280        diverging_vids: &[ty::TyVid],
281        coercions: &VecGraph<ty::TyVid, true>,
282    ) {
283        let DivergingFallbackBehavior::ToUnit = self.diverging_fallback_behavior else { return };
284
285        // Fallback happens if and only if there are diverging variables
286        if diverging_vids.is_empty() {
287            return;
288        }
289
290        // Returns errors which happen if fallback is set to `fallback`
291        let remaining_errors_if_fallback_to = |fallback| {
292            self.probe(|_| {
293                let obligations = self.fulfillment_cx.borrow().pending_obligations();
294                let ocx = ObligationCtxt::new_with_diagnostics(&self.infcx);
295                ocx.register_obligations(obligations.iter().cloned());
296
297                for &diverging_vid in diverging_vids {
298                    let diverging_ty = Ty::new_var(self.tcx, diverging_vid);
299
300                    ocx.eq(&ObligationCause::dummy(), self.param_env, diverging_ty, fallback)
301                        .expect("expected diverging var to be unconstrained");
302                }
303
304                ocx.try_evaluate_obligations()
305            })
306        };
307
308        // If we have no errors with `fallback = ()`, but *do* have errors with `fallback = !`,
309        // then this code will be broken by the never type fallback change.
310        let unit_errors = remaining_errors_if_fallback_to(self.tcx.types.unit);
311        if unit_errors.is_empty()
312            && let mut never_errors = remaining_errors_if_fallback_to(self.tcx.types.never)
313            && let [never_error, ..] = never_errors.as_mut_slice()
314        {
315            self.adjust_fulfillment_error_for_expr_obligation(never_error);
316            let sugg = self.try_to_suggest_annotations(diverging_vids, coercions);
317            self.tcx.emit_node_span_lint(
318                lint::builtin::DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK,
319                self.tcx.local_def_id_to_hir_id(self.body_id),
320                self.tcx.def_span(self.body_id),
321                errors::DependencyOnUnitNeverTypeFallback {
322                    obligation_span: never_error.obligation.cause.span,
323                    obligation: never_error.obligation.predicate,
324                    sugg,
325                },
326            )
327        }
328    }
329
330    /// Returns a graph whose nodes are (unresolved) inference variables and where
331    /// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
332    fn create_coercion_graph(&self) -> VecGraph<ty::TyVid, true> {
333        let pending_obligations = self.fulfillment_cx.borrow_mut().pending_obligations();
334        debug!("create_coercion_graph: pending_obligations={:?}", pending_obligations);
335        let coercion_edges: Vec<(ty::TyVid, ty::TyVid)> = pending_obligations
336            .into_iter()
337            .filter_map(|obligation| {
338                // The predicates we are looking for look like `Coerce(?A -> ?B)`.
339                // They will have no bound variables.
340                obligation.predicate.kind().no_bound_vars()
341            })
342            .filter_map(|atom| {
343                // We consider both subtyping and coercion to imply 'flow' from
344                // some position in the code `a` to a different position `b`.
345                // This is then used to determine which variables interact with
346                // live code, and as such must fall back to `()` to preserve
347                // soundness.
348                //
349                // In practice currently the two ways that this happens is
350                // coercion and subtyping.
351                let (a, b) = match atom {
352                    ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) => (a, b),
353                    ty::PredicateKind::Subtype(ty::SubtypePredicate { a_is_expected: _, a, b }) => {
354                        (a, b)
355                    }
356                    _ => return None,
357                };
358
359                let a_vid = self.root_vid(a)?;
360                let b_vid = self.root_vid(b)?;
361                Some((a_vid, b_vid))
362            })
363            .collect();
364        debug!("create_coercion_graph: coercion_edges={:?}", coercion_edges);
365        let num_ty_vars = self.num_ty_vars();
366
367        VecGraph::new(num_ty_vars, coercion_edges)
368    }
369
370    /// If `ty` is an unresolved type variable, returns its root vid.
371    fn root_vid(&self, ty: Ty<'tcx>) -> Option<ty::TyVid> {
372        Some(self.root_var(self.shallow_resolve(ty).ty_vid()?))
373    }
374
375    /// Given a set of diverging vids and coercions, walk the HIR to gather a
376    /// set of suggestions which can be applied to preserve fallback to unit.
377    fn try_to_suggest_annotations(
378        &self,
379        diverging_vids: &[ty::TyVid],
380        coercions: &VecGraph<ty::TyVid, true>,
381    ) -> errors::SuggestAnnotations {
382        let body =
383            self.tcx.hir_maybe_body_owned_by(self.body_id).expect("body id must have an owner");
384        // For each diverging var, look through the HIR for a place to give it
385        // a type annotation. We do this per var because we only really need one
386        // suggestion to influence a var to be `()`.
387        let suggestions = diverging_vids
388            .iter()
389            .copied()
390            .filter_map(|vid| {
391                let reachable_vids =
392                    graph::depth_first_search_as_undirected(coercions, vid).collect();
393                AnnotateUnitFallbackVisitor { reachable_vids, fcx: self }
394                    .visit_expr(body.value)
395                    .break_value()
396            })
397            .collect();
398        errors::SuggestAnnotations { suggestions }
399    }
400}
401
402/// Try to walk the HIR to find a place to insert a useful suggestion
403/// to preserve fallback to `()` in 2024.
404struct AnnotateUnitFallbackVisitor<'a, 'tcx> {
405    reachable_vids: FxHashSet<ty::TyVid>,
406    fcx: &'a FnCtxt<'a, 'tcx>,
407}
408impl<'tcx> AnnotateUnitFallbackVisitor<'_, 'tcx> {
409    // For a given path segment, if it's missing a turbofish, try to suggest adding
410    // one so we can constrain an argument to `()`. To keep the suggestion simple,
411    // we want to simply suggest `_` for all the other args. This (for now) only
412    // works when there are only type variables (and region variables, since we can
413    // elide them)...
414    fn suggest_for_segment(
415        &self,
416        arg_segment: &'tcx hir::PathSegment<'tcx>,
417        def_id: DefId,
418        id: HirId,
419    ) -> ControlFlow<errors::SuggestAnnotation> {
420        if arg_segment.args.is_none()
421            && let Some(all_args) = self.fcx.typeck_results.borrow().node_args_opt(id)
422            && let generics = self.fcx.tcx.generics_of(def_id)
423            && let args = all_args[generics.parent_count..].iter().zip(&generics.own_params)
424            // We can't turbofish consts :(
425            && args.clone().all(|(_, param)| matches!(param.kind, ty::GenericParamDefKind::Type { .. } | ty::GenericParamDefKind::Lifetime))
426        {
427            // We filter out APITs, which are not turbofished.
428            let non_apit_type_args = args.filter(|(_, param)| {
429                matches!(param.kind, ty::GenericParamDefKind::Type { synthetic: false, .. })
430            });
431            let n_tys = non_apit_type_args.clone().count();
432            for (idx, (arg, _)) in non_apit_type_args.enumerate() {
433                if let Some(ty) = arg.as_type()
434                    && let Some(vid) = self.fcx.root_vid(ty)
435                    && self.reachable_vids.contains(&vid)
436                {
437                    return ControlFlow::Break(errors::SuggestAnnotation::Turbo(
438                        arg_segment.ident.span.shrink_to_hi(),
439                        n_tys,
440                        idx,
441                    ));
442                }
443            }
444        }
445        ControlFlow::Continue(())
446    }
447}
448impl<'tcx> Visitor<'tcx> for AnnotateUnitFallbackVisitor<'_, 'tcx> {
449    type Result = ControlFlow<errors::SuggestAnnotation>;
450
451    fn visit_infer(
452        &mut self,
453        inf_id: HirId,
454        inf_span: Span,
455        _kind: InferKind<'tcx>,
456    ) -> Self::Result {
457        // Try to replace `_` with `()`.
458        if let Some(ty) = self.fcx.typeck_results.borrow().node_type_opt(inf_id)
459            && let Some(vid) = self.fcx.root_vid(ty)
460            && self.reachable_vids.contains(&vid)
461            && inf_span.can_be_used_for_suggestions()
462        {
463            return ControlFlow::Break(errors::SuggestAnnotation::Unit(inf_span));
464        }
465
466        ControlFlow::Continue(())
467    }
468
469    fn visit_qpath(
470        &mut self,
471        qpath: &'tcx rustc_hir::QPath<'tcx>,
472        id: HirId,
473        span: Span,
474    ) -> Self::Result {
475        let arg_segment = match qpath {
476            hir::QPath::Resolved(_, path) => {
477                path.segments.last().expect("paths should have a segment")
478            }
479            hir::QPath::TypeRelative(_, segment) => segment,
480        };
481        // Alternatively, try to turbofish `::<_, (), _>`.
482        if let Some(def_id) = self.fcx.typeck_results.borrow().qpath_res(qpath, id).opt_def_id()
483            && span.can_be_used_for_suggestions()
484        {
485            self.suggest_for_segment(arg_segment, def_id, id)?;
486        }
487        hir::intravisit::walk_qpath(self, qpath, id)
488    }
489
490    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Self::Result {
491        if let hir::ExprKind::Closure(&hir::Closure { body, .. })
492        | hir::ExprKind::ConstBlock(hir::ConstBlock { body, .. }) = expr.kind
493        {
494            self.visit_body(self.fcx.tcx.hir_body(body))?;
495        }
496
497        // Try to suggest adding an explicit qself `()` to a trait method path.
498        // i.e. changing `Default::default()` to `<() as Default>::default()`.
499        if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
500            && let Res::Def(DefKind::AssocFn, def_id) = path.res
501            && self.fcx.tcx.trait_of_assoc(def_id).is_some()
502            && let Some(args) = self.fcx.typeck_results.borrow().node_args_opt(expr.hir_id)
503            && let self_ty = args.type_at(0)
504            && let Some(vid) = self.fcx.root_vid(self_ty)
505            && self.reachable_vids.contains(&vid)
506            && let [.., trait_segment, _method_segment] = path.segments
507            && expr.span.can_be_used_for_suggestions()
508        {
509            let span = path.span.shrink_to_lo().to(trait_segment.ident.span);
510            return ControlFlow::Break(errors::SuggestAnnotation::Path(span));
511        }
512
513        // Or else, try suggesting turbofishing the method args.
514        if let hir::ExprKind::MethodCall(segment, ..) = expr.kind
515            && let Some(def_id) =
516                self.fcx.typeck_results.borrow().type_dependent_def_id(expr.hir_id)
517            && expr.span.can_be_used_for_suggestions()
518        {
519            self.suggest_for_segment(segment, def_id, expr.hir_id)?;
520        }
521
522        hir::intravisit::walk_expr(self, expr)
523    }
524
525    fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) -> Self::Result {
526        // For a local, try suggest annotating the type if it's missing.
527        if let hir::LocalSource::Normal = local.source
528            && let None = local.ty
529            && let Some(ty) = self.fcx.typeck_results.borrow().node_type_opt(local.hir_id)
530            && let Some(vid) = self.fcx.root_vid(ty)
531            && self.reachable_vids.contains(&vid)
532            && local.span.can_be_used_for_suggestions()
533        {
534            return ControlFlow::Break(errors::SuggestAnnotation::Local(
535                local.pat.span.shrink_to_hi(),
536            ));
537        }
538        hir::intravisit::walk_local(self, local)
539    }
540}
541
542#[derive(Debug, Copy, Clone)]
543pub(crate) enum UnsafeUseReason {
544    Call,
545    Method,
546    Path,
547    UnionField,
548    Deref,
549}
550
551/// Finds all type variables which are passed to an `unsafe` operation.
552///
553/// For example, for this function `f`:
554/// ```ignore (demonstrative)
555/// fn f() {
556///     unsafe {
557///         let x /* ?X */ = core::mem::zeroed();
558///         //               ^^^^^^^^^^^^^^^^^^^ -- hir_id, span, reason
559///
560///         let y = core::mem::zeroed::<Option<_ /* ?Y */>>();
561///         //      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- hir_id, span, reason
562///     }
563/// }
564/// ```
565///
566/// `compute_unsafe_infer_vars` will return `{ id(?X) -> (hir_id, span, Call) }`
567fn compute_unsafe_infer_vars<'a, 'tcx>(
568    fcx: &'a FnCtxt<'a, 'tcx>,
569    body_id: LocalDefId,
570) -> UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)> {
571    let body = fcx.tcx.hir_maybe_body_owned_by(body_id).expect("body id must have an owner");
572    let mut res = UnordMap::default();
573
574    struct UnsafeInferVarsVisitor<'a, 'tcx> {
575        fcx: &'a FnCtxt<'a, 'tcx>,
576        res: &'a mut UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)>,
577    }
578
579    impl Visitor<'_> for UnsafeInferVarsVisitor<'_, '_> {
580        fn visit_expr(&mut self, ex: &'_ hir::Expr<'_>) {
581            let typeck_results = self.fcx.typeck_results.borrow();
582
583            match ex.kind {
584                hir::ExprKind::MethodCall(..) => {
585                    if let Some(def_id) = typeck_results.type_dependent_def_id(ex.hir_id)
586                        && let method_ty = self.fcx.tcx.type_of(def_id).instantiate_identity()
587                        && let sig = method_ty.fn_sig(self.fcx.tcx)
588                        && sig.safety().is_unsafe()
589                    {
590                        let mut collector = InferVarCollector {
591                            value: (ex.hir_id, ex.span, UnsafeUseReason::Method),
592                            res: self.res,
593                        };
594
595                        // Collect generic arguments (incl. `Self`) of the method
596                        typeck_results
597                            .node_args(ex.hir_id)
598                            .types()
599                            .for_each(|t| t.visit_with(&mut collector));
600                    }
601                }
602
603                hir::ExprKind::Call(func, ..) => {
604                    let func_ty = typeck_results.expr_ty(func);
605
606                    if func_ty.is_fn()
607                        && let sig = func_ty.fn_sig(self.fcx.tcx)
608                        && sig.safety().is_unsafe()
609                    {
610                        let mut collector = InferVarCollector {
611                            value: (ex.hir_id, ex.span, UnsafeUseReason::Call),
612                            res: self.res,
613                        };
614
615                        // Try collecting generic arguments of the function.
616                        // Note that we do this below for any paths (that don't have to be called),
617                        // but there we do it with a different span/reason.
618                        // This takes priority.
619                        typeck_results
620                            .node_args(func.hir_id)
621                            .types()
622                            .for_each(|t| t.visit_with(&mut collector));
623
624                        // Also check the return type, for cases like `returns_unsafe_fn_ptr()()`
625                        sig.output().visit_with(&mut collector);
626                    }
627                }
628
629                // Check paths which refer to functions.
630                // We do this, instead of only checking `Call` to make sure the lint can't be
631                // avoided by storing unsafe function in a variable.
632                hir::ExprKind::Path(_) => {
633                    let ty = typeck_results.expr_ty(ex);
634
635                    // If this path refers to an unsafe function, collect inference variables which may affect it.
636                    // `is_fn` excludes closures, but those can't be unsafe.
637                    if ty.is_fn()
638                        && let sig = ty.fn_sig(self.fcx.tcx)
639                        && sig.safety().is_unsafe()
640                    {
641                        let mut collector = InferVarCollector {
642                            value: (ex.hir_id, ex.span, UnsafeUseReason::Path),
643                            res: self.res,
644                        };
645
646                        // Collect generic arguments of the function
647                        typeck_results
648                            .node_args(ex.hir_id)
649                            .types()
650                            .for_each(|t| t.visit_with(&mut collector));
651                    }
652                }
653
654                hir::ExprKind::Unary(hir::UnOp::Deref, pointer) => {
655                    if let ty::RawPtr(pointee, _) = typeck_results.expr_ty(pointer).kind() {
656                        pointee.visit_with(&mut InferVarCollector {
657                            value: (ex.hir_id, ex.span, UnsafeUseReason::Deref),
658                            res: self.res,
659                        });
660                    }
661                }
662
663                hir::ExprKind::Field(base, _) => {
664                    let base_ty = typeck_results.expr_ty(base);
665
666                    if base_ty.is_union() {
667                        typeck_results.expr_ty(ex).visit_with(&mut InferVarCollector {
668                            value: (ex.hir_id, ex.span, UnsafeUseReason::UnionField),
669                            res: self.res,
670                        });
671                    }
672                }
673
674                _ => (),
675            };
676
677            hir::intravisit::walk_expr(self, ex);
678        }
679    }
680
681    struct InferVarCollector<'r, V> {
682        value: V,
683        res: &'r mut UnordMap<ty::TyVid, V>,
684    }
685
686    impl<'tcx, V: Copy> ty::TypeVisitor<TyCtxt<'tcx>> for InferVarCollector<'_, V> {
687        fn visit_ty(&mut self, t: Ty<'tcx>) {
688            if let Some(vid) = t.ty_vid() {
689                _ = self.res.try_insert(vid, self.value);
690            } else {
691                t.super_visit_with(self)
692            }
693        }
694    }
695
696    UnsafeInferVarsVisitor { fcx, res: &mut res }.visit_expr(&body.value);
697
698    debug!(?res, "collected the following unsafe vars for {body_id:?}");
699
700    res
701}