clippy_utils/ty/type_certainty/
mod.rs

1//! A heuristic to tell whether an expression's type can be determined purely from its
2//! subexpressions, and the arguments and locals they use. Put another way, `expr_type_is_certain`
3//! tries to tell whether an expression's type can be determined without appeal to the surrounding
4//! context.
5//!
6//! This is, in some sense, a counterpart to `let_unit_value`'s `expr_needs_inferred_result`.
7//! Intuitively, that function determines whether an expression's type is needed for type inference,
8//! whereas `expr_type_is_certain` determines whether type inference is needed for an expression's
9//! type.
10//!
11//! As a heuristic, `expr_type_is_certain` may produce false negatives, but a false positive should
12//! be considered a bug.
13
14use crate::paths::{PathNS, lookup_path};
15use rustc_ast::{LitFloatType, LitIntType, LitKind};
16use rustc_hir::def::{DefKind, Res};
17use rustc_hir::def_id::DefId;
18use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_qpath, walk_ty};
19use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, Param, PathSegment, QPath, TyKind};
20use rustc_lint::LateContext;
21use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty};
22use rustc_span::Span;
23
24mod certainty;
25use certainty::{Certainty, Meet, join, meet};
26
27pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
28    expr_type_certainty(cx, expr, false).is_certain()
29}
30
31/// Determine the type certainty of `expr`. `in_arg` indicates that the expression happens within
32/// the evaluation of a function or method call argument.
33fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>, in_arg: bool) -> Certainty {
34    let certainty = match &expr.kind {
35        ExprKind::Unary(_, expr)
36        | ExprKind::Field(expr, _)
37        | ExprKind::Index(expr, _, _)
38        | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr, in_arg),
39
40        ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))),
41
42        ExprKind::Call(callee, args) => {
43            let lhs = expr_type_certainty(cx, callee, false);
44            let rhs = if type_is_inferable_from_arguments(cx, expr) {
45                meet(args.iter().map(|arg| expr_type_certainty(cx, arg, true)))
46            } else {
47                Certainty::Uncertain
48            };
49            lhs.join_clearing_def_ids(rhs)
50        },
51
52        ExprKind::MethodCall(method, receiver, args, _) => {
53            let mut receiver_type_certainty = expr_type_certainty(cx, receiver, false);
54            // Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method
55            // identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`,
56            // for example. So update the `DefId` in `receiver_type_certainty` (if any).
57            if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
58                && let Some(self_ty_def_id) = adt_def_id(self_ty(cx, method_def_id))
59            {
60                receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id);
61            }
62            let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false);
63            let rhs = if type_is_inferable_from_arguments(cx, expr) {
64                meet(
65                    std::iter::once(receiver_type_certainty)
66                        .chain(args.iter().map(|arg| expr_type_certainty(cx, arg, true))),
67                )
68            } else {
69                Certainty::Uncertain
70            };
71            lhs.join(rhs)
72        },
73
74        ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))),
75
76        ExprKind::Binary(_, lhs, rhs) => {
77            // If one of the side of the expression is uncertain, the certainty will come from the other side,
78            // with no information on the type.
79            match (
80                expr_type_certainty(cx, lhs, in_arg),
81                expr_type_certainty(cx, rhs, in_arg),
82            ) {
83                (Certainty::Uncertain, Certainty::Certain(_)) | (Certainty::Certain(_), Certainty::Uncertain) => {
84                    Certainty::Certain(None)
85                },
86                (l, r) => l.meet(r),
87            }
88        },
89
90        ExprKind::Lit(lit) => {
91            if !in_arg
92                && matches!(
93                    lit.node,
94                    LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
95                )
96            {
97                Certainty::Uncertain
98            } else {
99                Certainty::Certain(None)
100            }
101        },
102
103        ExprKind::Cast(_, ty) => type_certainty(cx, ty),
104
105        ExprKind::If(_, if_expr, Some(else_expr)) => {
106            expr_type_certainty(cx, if_expr, in_arg).join(expr_type_certainty(cx, else_expr, in_arg))
107        },
108
109        ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false),
110
111        ExprKind::Struct(qpath, _, _) => qpath_certainty(cx, qpath, true),
112
113        _ => Certainty::Uncertain,
114    };
115
116    let expr_ty = cx.typeck_results().expr_ty(expr);
117    if let Some(def_id) = adt_def_id(expr_ty) {
118        certainty.with_def_id(def_id)
119    } else {
120        certainty.clear_def_id()
121    }
122}
123
124struct CertaintyVisitor<'cx, 'tcx> {
125    cx: &'cx LateContext<'tcx>,
126    certainty: Certainty,
127}
128
129impl<'cx, 'tcx> CertaintyVisitor<'cx, 'tcx> {
130    fn new(cx: &'cx LateContext<'tcx>) -> Self {
131        Self {
132            cx,
133            certainty: Certainty::Certain(None),
134        }
135    }
136}
137
138impl<'cx> Visitor<'cx> for CertaintyVisitor<'cx, '_> {
139    fn visit_qpath(&mut self, qpath: &'cx QPath<'_>, hir_id: HirId, _: Span) {
140        self.certainty = self.certainty.meet(qpath_certainty(self.cx, qpath, true));
141        if self.certainty != Certainty::Uncertain {
142            walk_qpath(self, qpath, hir_id);
143        }
144    }
145
146    fn visit_ty(&mut self, ty: &'cx hir::Ty<'_, AmbigArg>) {
147        if self.certainty != Certainty::Uncertain {
148            walk_ty(self, ty);
149        }
150    }
151
152    fn visit_infer(&mut self, _inf_id: HirId, _inf_span: Span, _kind: InferKind<'cx>) -> Self::Result {
153        self.certainty = Certainty::Uncertain;
154    }
155}
156
157fn type_certainty(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Certainty {
158    // Handle `TyKind::Path` specially so that its `DefId` can be preserved.
159    //
160    // Note that `CertaintyVisitor::new` initializes the visitor's internal certainty to
161    // `Certainty::Certain(None)`. Furthermore, if a `TyKind::Path` is encountered while traversing
162    // `ty`, the result of the call to `qpath_certainty` is combined with the visitor's internal
163    // certainty using `Certainty::meet`. Thus, if the `TyKind::Path` were not treated specially here,
164    // the resulting certainty would be `Certainty::Certain(None)`.
165    if let TyKind::Path(qpath) = &ty.kind {
166        return qpath_certainty(cx, qpath, true);
167    }
168
169    let mut visitor = CertaintyVisitor::new(cx);
170    visitor.visit_ty_unambig(ty);
171    visitor.certainty
172}
173
174fn generic_args_certainty(cx: &LateContext<'_>, args: &GenericArgs<'_>) -> Certainty {
175    let mut visitor = CertaintyVisitor::new(cx);
176    visitor.visit_generic_args(args);
177    visitor.certainty
178}
179
180/// Tries to tell whether a `QPath` resolves to something certain, e.g., whether all of its path
181/// segments generic arguments are instantiated.
182///
183/// `qpath` could refer to either a type or a value. The heuristic never needs the `DefId` of a
184/// value. So `DefId`s are retained only when `resolves_to_type` is true.
185fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bool) -> Certainty {
186    let certainty = match qpath {
187        QPath::Resolved(ty, path) => {
188            let len = path.segments.len();
189            path.segments.iter().enumerate().fold(
190                ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)),
191                |parent_certainty, (i, path_segment)| {
192                    path_segment_certainty(cx, parent_certainty, path_segment, i != len - 1 || resolves_to_type)
193                },
194            )
195        },
196
197        QPath::TypeRelative(ty, path_segment) => {
198            path_segment_certainty(cx, type_certainty(cx, ty), path_segment, resolves_to_type)
199        },
200
201        QPath::LangItem(lang_item, ..) => cx
202            .tcx
203            .lang_items()
204            .get(*lang_item)
205            .map_or(Certainty::Uncertain, |def_id| {
206                let generics = cx.tcx.generics_of(def_id);
207                if generics.is_empty() {
208                    Certainty::Certain(if resolves_to_type { Some(def_id) } else { None })
209                } else {
210                    Certainty::Uncertain
211                }
212            }),
213    };
214    debug_assert!(resolves_to_type || certainty.to_def_id().is_none());
215    certainty
216}
217
218/// Tries to tell whether `param` resolves to something certain, e.g., a non-wildcard type if
219/// present. The certainty `DefId` is cleared before returning.
220fn param_certainty(cx: &LateContext<'_>, param: &Param<'_>) -> Certainty {
221    let owner_did = cx.tcx.hir_enclosing_body_owner(param.hir_id);
222    let Some(fn_decl) = cx.tcx.hir_fn_decl_by_hir_id(cx.tcx.local_def_id_to_hir_id(owner_did)) else {
223        return Certainty::Uncertain;
224    };
225    let inputs = fn_decl.inputs;
226    let body_params = cx.tcx.hir_body_owned_by(owner_did).params;
227    std::iter::zip(body_params, inputs)
228        .find(|(p, _)| p.hir_id == param.hir_id)
229        .map_or(Certainty::Uncertain, |(_, ty)| type_certainty(cx, ty).clear_def_id())
230}
231
232fn path_segment_certainty(
233    cx: &LateContext<'_>,
234    parent_certainty: Certainty,
235    path_segment: &PathSegment<'_>,
236    resolves_to_type: bool,
237) -> Certainty {
238    let certainty = match update_res(cx, parent_certainty, path_segment, resolves_to_type).unwrap_or(path_segment.res) {
239        // A definition's type is certain if it refers to something without generics (e.g., a crate or module, or
240        // an unparameterized type), or the generics are instantiated with arguments that are certain.
241        //
242        // If the parent is uncertain, then the current path segment must account for the parent's generic arguments.
243        // Consider the following examples, where the current path segment is `None`:
244        // - `Option::None`             // uncertain; parent (i.e., `Option`) is uncertain
245        // - `Option::<Vec<u64>>::None` // certain; parent (i.e., `Option::<..>`) is certain
246        // - `Option::None::<Vec<u64>>` // certain; parent (i.e., `Option`) is uncertain
247        Res::Def(_, def_id) => {
248            // Checking `res_generics_def_id(..)` before calling `generics_of` avoids an ICE.
249            if cx.tcx.res_generics_def_id(path_segment.res).is_some() {
250                let generics = cx.tcx.generics_of(def_id);
251
252                let own_count = generics.own_params.len();
253                let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && own_count == 0 {
254                    Certainty::Certain(None)
255                } else {
256                    Certainty::Uncertain
257                };
258                let rhs = path_segment
259                    .args
260                    .map_or(Certainty::Uncertain, |args| generic_args_certainty(cx, args));
261                // See the comment preceding `qpath_certainty`. `def_id` could refer to a type or a value.
262                let certainty = lhs.join_clearing_def_ids(rhs);
263                if resolves_to_type {
264                    if let DefKind::TyAlias = cx.tcx.def_kind(def_id) {
265                        adt_def_id(cx.tcx.type_of(def_id).instantiate_identity())
266                            .map_or(certainty, |def_id| certainty.with_def_id(def_id))
267                    } else {
268                        certainty.with_def_id(def_id)
269                    }
270                } else {
271                    certainty
272                }
273            } else {
274                Certainty::Certain(None)
275            }
276        },
277
278        Res::PrimTy(_) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::SelfCtor(_) => {
279            Certainty::Certain(None)
280        },
281
282        // `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`.
283        Res::Local(hir_id) => match cx.tcx.parent_hir_node(hir_id) {
284            // A parameter's type is not always certain, as it may come from an untyped closure definition,
285            // or from a wildcard in a typed closure definition.
286            Node::Param(param) => param_certainty(cx, param),
287            // A local's type is certain if its type annotation is certain or it has an initializer whose
288            // type is certain.
289            Node::LetStmt(local) => {
290                let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty));
291                let rhs = local
292                    .init
293                    .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init, false));
294                let certainty = lhs.join(rhs);
295                if resolves_to_type {
296                    certainty
297                } else {
298                    certainty.clear_def_id()
299                }
300            },
301            _ => Certainty::Uncertain,
302        },
303
304        _ => Certainty::Uncertain,
305    };
306    debug_assert!(resolves_to_type || certainty.to_def_id().is_none());
307    certainty
308}
309
310/// For at least some `QPath::TypeRelative`, the path segment's `res` can be `Res::Err`.
311/// `update_res` tries to fix the resolution when `parent_certainty` is `Certain(Some(..))`.
312fn update_res(
313    cx: &LateContext<'_>,
314    parent_certainty: Certainty,
315    path_segment: &PathSegment<'_>,
316    resolves_to_type: bool,
317) -> Option<Res> {
318    if path_segment.res == Res::Err
319        && let Some(def_id) = parent_certainty.to_def_id()
320    {
321        let mut def_path = cx.get_def_path(def_id);
322        def_path.push(path_segment.ident.name);
323        let ns = if resolves_to_type { PathNS::Type } else { PathNS::Value };
324        if let &[id] = lookup_path(cx.tcx, ns, &def_path).as_slice() {
325            return Some(Res::Def(cx.tcx.def_kind(id), id));
326        }
327    }
328
329    None
330}
331
332#[expect(clippy::cast_possible_truncation)]
333fn type_is_inferable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
334    let Some(callee_def_id) = (match expr.kind {
335        ExprKind::Call(callee, _) => {
336            let callee_ty = cx.typeck_results().expr_ty(callee);
337            if let ty::FnDef(callee_def_id, _) = callee_ty.kind() {
338                Some(*callee_def_id)
339            } else {
340                None
341            }
342        },
343        ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
344        _ => None,
345    }) else {
346        return false;
347    };
348
349    let generics = cx.tcx.generics_of(callee_def_id);
350    let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
351
352    // Check that all type parameters appear in the functions input types.
353    (0..(generics.parent_count + generics.own_params.len()) as u32).all(|index| {
354        fn_sig
355            .inputs()
356            .iter()
357            .any(|input_ty| contains_param(*input_ty.skip_binder(), index))
358    })
359}
360
361fn self_ty<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId) -> Ty<'tcx> {
362    cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()[0]
363}
364
365fn adt_def_id(ty: Ty<'_>) -> Option<DefId> {
366    ty.peel_refs().ty_adt_def().map(AdtDef::did)
367}
368
369fn contains_param(ty: Ty<'_>, index: u32) -> bool {
370    ty.walk()
371        .any(|arg| matches!(arg.kind(), GenericArgKind::Type(ty) if ty.is_param(index)))
372}