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