From f583fd18e4d1ecb7a0c2c838a5ac0a10188f24db Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Sun, 9 Jul 2023 18:59:25 -0400 Subject: [PATCH] Fix `unwrap_or_else_default` false positive --- .../src/methods/unwrap_or_else_default.rs | 6 +- clippy_utils/src/ty.rs | 3 + .../src/ty/type_certainty/certainty.rs | 122 +++++++ clippy_utils/src/ty/type_certainty/mod.rs | 315 ++++++++++++++++++ tests/ui/unwrap_or_else_default.fixed | 37 ++ tests/ui/unwrap_or_else_default.rs | 37 ++ tests/ui/unwrap_or_else_default.stderr | 50 ++- 7 files changed, 568 insertions(+), 2 deletions(-) create mode 100644 clippy_utils/src/ty/type_certainty/certainty.rs create mode 100644 clippy_utils/src/ty/type_certainty/mod.rs diff --git a/clippy_lints/src/methods/unwrap_or_else_default.rs b/clippy_lints/src/methods/unwrap_or_else_default.rs index 474a33b67e1c..cfb1f7bbba8f 100644 --- a/clippy_lints/src/methods/unwrap_or_else_default.rs +++ b/clippy_lints/src/methods/unwrap_or_else_default.rs @@ -4,7 +4,7 @@ use super::UNWRAP_OR_ELSE_DEFAULT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_default_equivalent_call; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{expr_type_is_certain, is_type_diagnostic_item}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir as hir; @@ -17,6 +17,10 @@ pub(super) fn check<'tcx>( recv: &'tcx hir::Expr<'_>, u_arg: &'tcx hir::Expr<'_>, ) { + if !expr_type_is_certain(cx, recv) { + return; + } + // something.unwrap_or_else(Default::default) // ^^^^^^^^^- recv ^^^^^^^^^^^^^^^^- u_arg // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- expr diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 15a08370b6a5..53152ab7e135 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -28,6 +28,9 @@ use std::iter; use crate::{match_def_path, path_res, paths}; +mod type_certainty; +pub use type_certainty::expr_type_is_certain; + /// Checks if the given type implements copy. pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty.is_copy_modulo_regions(cx.tcx, cx.param_env) diff --git a/clippy_utils/src/ty/type_certainty/certainty.rs b/clippy_utils/src/ty/type_certainty/certainty.rs new file mode 100644 index 000000000000..0e69ffa2212d --- /dev/null +++ b/clippy_utils/src/ty/type_certainty/certainty.rs @@ -0,0 +1,122 @@ +use rustc_hir::def_id::DefId; +use std::fmt::Debug; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Certainty { + /// Determining the type requires contextual information. + Uncertain, + + /// The type can be determined purely from subexpressions. If the argument is `Some(..)`, the + /// specific `DefId` is known. Such arguments are needed to handle path segments whose `res` is + /// `Res::Err`. + Certain(Option), + + /// The heuristic believes that more than one `DefId` applies to a type---this is a bug. + Contradiction, +} + +pub trait Meet { + fn meet(self, other: Self) -> Self; +} + +pub trait TryJoin: Sized { + fn try_join(self, other: Self) -> Option; +} + +impl Meet for Option { + fn meet(self, other: Self) -> Self { + match (self, other) { + (None, _) | (_, None) => None, + (Some(lhs), Some(rhs)) => (lhs == rhs).then_some(lhs), + } + } +} + +impl TryJoin for Option { + fn try_join(self, other: Self) -> Option { + match (self, other) { + (Some(lhs), Some(rhs)) => (lhs == rhs).then_some(Some(lhs)), + (Some(def_id), _) | (_, Some(def_id)) => Some(Some(def_id)), + (None, None) => Some(None), + } + } +} + +impl Meet for Certainty { + fn meet(self, other: Self) -> Self { + match (self, other) { + (Certainty::Uncertain, _) | (_, Certainty::Uncertain) => Certainty::Uncertain, + (Certainty::Certain(lhs), Certainty::Certain(rhs)) => Certainty::Certain(lhs.meet(rhs)), + (Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner), + (Certainty::Contradiction, Certainty::Contradiction) => Certainty::Contradiction, + } + } +} + +impl Certainty { + /// Join two `Certainty`s preserving their `DefId`s (if any). Generally speaking, this method + /// should be used only when `self` and `other` refer directly to types. Otherwise, + /// `join_clearing_def_ids` should be used. + pub fn join(self, other: Self) -> Self { + match (self, other) { + (Certainty::Contradiction, _) | (_, Certainty::Contradiction) => Certainty::Contradiction, + + (Certainty::Certain(lhs), Certainty::Certain(rhs)) => { + if let Some(inner) = lhs.try_join(rhs) { + Certainty::Certain(inner) + } else { + debug_assert!(false, "Contradiction with {lhs:?} and {rhs:?}"); + Certainty::Contradiction + } + }, + + (Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner), + + (Certainty::Uncertain, Certainty::Uncertain) => Certainty::Uncertain, + } + } + + /// Join two `Certainty`s after clearing their `DefId`s. This method should be used when `self` + /// or `other` do not necessarily refer to types, e.g., when they are aggregations of other + /// `Certainty`s. + pub fn join_clearing_def_ids(self, other: Self) -> Self { + self.clear_def_id().join(other.clear_def_id()) + } + + pub fn clear_def_id(self) -> Certainty { + if matches!(self, Certainty::Certain(_)) { + Certainty::Certain(None) + } else { + self + } + } + + pub fn with_def_id(self, def_id: DefId) -> Certainty { + if matches!(self, Certainty::Certain(_)) { + Certainty::Certain(Some(def_id)) + } else { + self + } + } + + pub fn to_def_id(self) -> Option { + match self { + Certainty::Certain(inner) => inner, + _ => None, + } + } + + pub fn is_certain(self) -> bool { + matches!(self, Self::Certain(_)) + } +} + +/// Think: `iter.all(/* is certain */)` +pub fn meet(iter: impl Iterator) -> Certainty { + iter.fold(Certainty::Certain(None), Certainty::meet) +} + +/// Think: `iter.any(/* is certain */)` +pub fn join(iter: impl Iterator) -> Certainty { + iter.fold(Certainty::Uncertain, Certainty::join) +} diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs new file mode 100644 index 000000000000..67c4a293a865 --- /dev/null +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -0,0 +1,315 @@ +//! A heuristic to tell whether an expression's type can be determined purely from its +//! subexpressions, and the arguments and locals they use. Put another way, `expr_type_is_certain` +//! tries to tell whether an expression's type can be determined without appeal to the surrounding +//! context. +//! +//! This is, in some sense, a counterpart to `let_unit_value`'s `expr_needs_inferred_result`. +//! Intuitively, that function determines whether an expression's type is needed for type inference, +//! whereas `expr_type_is_certain` determines whether type inference is needed for an expression's +//! type. +//! +//! As a heuristic, `expr_type_is_certain` may produce false negatives, but a false positive should +//! be considered a bug. + +use crate::def_path_res; +use rustc_hir::def::Res; +use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::{walk_qpath, walk_ty, Visitor}; +use rustc_hir::{self as hir, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty}; +use rustc_span::{Span, Symbol}; + +mod certainty; +use certainty::{join, meet, Certainty, Meet}; + +pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + expr_type_certainty(cx, expr).is_certain() +} + +fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { + let certainty = match &expr.kind { + ExprKind::Unary(_, expr) + | ExprKind::Field(expr, _) + | ExprKind::Index(expr, _) + | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr), + + ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + + ExprKind::Call(callee, args) => { + let lhs = expr_type_certainty(cx, callee); + let rhs = if type_is_inferrable_from_arguments(cx, expr) { + meet(args.iter().map(|arg| expr_type_certainty(cx, arg))) + } else { + Certainty::Uncertain + }; + lhs.join_clearing_def_ids(rhs) + }, + + ExprKind::MethodCall(method, receiver, args, _) => { + let mut receiver_type_certainty = expr_type_certainty(cx, receiver); + // Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method + // identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`, + // for example. So update the `DefId` in `receiver_type_certainty` (if any). + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && let Some(self_ty_def_id) = adt_def_id(self_ty(cx, method_def_id)) + { + receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id); + }; + let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false); + let rhs = if type_is_inferrable_from_arguments(cx, expr) { + meet( + std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))), + ) + } else { + Certainty::Uncertain + }; + lhs.join(rhs) + }, + + ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + + ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)), + + ExprKind::Lit(_) => Certainty::Certain(None), + + ExprKind::Cast(_, ty) => type_certainty(cx, ty), + + ExprKind::If(_, if_expr, Some(else_expr)) => { + expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr)) + }, + + ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false), + + ExprKind::Struct(qpath, _, _) => qpath_certainty(cx, qpath, true), + + _ => Certainty::Uncertain, + }; + + let expr_ty = cx.typeck_results().expr_ty(expr); + if let Some(def_id) = adt_def_id(expr_ty) { + certainty.with_def_id(def_id) + } else { + certainty + } +} + +struct CertaintyVisitor<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + certainty: Certainty, +} + +impl<'cx, 'tcx> CertaintyVisitor<'cx, 'tcx> { + fn new(cx: &'cx LateContext<'tcx>) -> Self { + Self { + cx, + certainty: Certainty::Certain(None), + } + } +} + +impl<'cx, 'tcx> Visitor<'cx> for CertaintyVisitor<'cx, 'tcx> { + fn visit_qpath(&mut self, qpath: &'cx QPath<'_>, hir_id: HirId, _: Span) { + self.certainty = self.certainty.meet(qpath_certainty(self.cx, qpath, true)); + if self.certainty != Certainty::Uncertain { + walk_qpath(self, qpath, hir_id); + } + } + + fn visit_ty(&mut self, ty: &'cx hir::Ty<'_>) { + if matches!(ty.kind, TyKind::Infer) { + self.certainty = Certainty::Uncertain; + } + if self.certainty != Certainty::Uncertain { + walk_ty(self, ty); + } + } +} + +fn type_certainty(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Certainty { + // Handle `TyKind::Path` specially so that its `DefId` can be preserved. + // + // Note that `CertaintyVisitor::new` initializes the visitor's internal certainty to + // `Certainty::Certain(None)`. Furthermore, if a `TyKind::Path` is encountered while traversing + // `ty`, the result of the call to `qpath_certainty` is combined with the visitor's internal + // certainty using `Certainty::meet`. Thus, if the `TyKind::Path` were not treated specially here, + // the resulting certainty would be `Certainty::Certain(None)`. + if let TyKind::Path(qpath) = &ty.kind { + return qpath_certainty(cx, qpath, true); + } + + let mut visitor = CertaintyVisitor::new(cx); + visitor.visit_ty(ty); + visitor.certainty +} + +fn generic_args_certainty(cx: &LateContext<'_>, args: &GenericArgs<'_>) -> Certainty { + let mut visitor = CertaintyVisitor::new(cx); + visitor.visit_generic_args(args); + visitor.certainty +} + +/// Tries to tell whether a `QPath` resolves to something certain, e.g., whether all of its path +/// segments generic arguments are are instantiated. +/// +/// `qpath` could refer to either a type or a value. The heuristic never needs the `DefId` of a +/// value. So `DefId`s are retained only when `resolves_to_type` is true. +fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bool) -> Certainty { + let certainty = match qpath { + QPath::Resolved(ty, path) => { + let len = path.segments.len(); + path.segments.iter().enumerate().fold( + ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)), + |parent_certainty, (i, path_segment)| { + path_segment_certainty(cx, parent_certainty, path_segment, i != len - 1 || resolves_to_type) + }, + ) + }, + + QPath::TypeRelative(ty, path_segment) => { + path_segment_certainty(cx, type_certainty(cx, ty), path_segment, resolves_to_type) + }, + + QPath::LangItem(lang_item, _, _) => { + cx.tcx + .lang_items() + .get(*lang_item) + .map_or(Certainty::Uncertain, |def_id| { + let generics = cx.tcx.generics_of(def_id); + if generics.parent_count == 0 && generics.params.is_empty() { + Certainty::Certain(if resolves_to_type { Some(def_id) } else { None }) + } else { + Certainty::Uncertain + } + }) + }, + }; + debug_assert!(resolves_to_type || certainty.to_def_id().is_none()); + certainty +} + +fn path_segment_certainty( + cx: &LateContext<'_>, + parent_certainty: Certainty, + path_segment: &PathSegment<'_>, + resolves_to_type: bool, +) -> Certainty { + let certainty = match update_res(cx, parent_certainty, path_segment).unwrap_or(path_segment.res) { + // A definition's type is certain if it refers to something without generics (e.g., a crate or module, or + // an unparameterized type), or the generics are instantiated with arguments that are certain. + // + // If the parent is uncertain, then the current path segment must account for the parent's generic arguments. + // Consider the following examples, where the current path segment is `None`: + // - `Option::None` // uncertain; parent (i.e., `Option`) is uncertain + // - `Option::>::None` // certain; parent (i.e., `Option::<..>`) is certain + // - `Option::None::>` // certain; parent (i.e., `Option`) is uncertain + Res::Def(_, def_id) => { + // Checking `res_generics_def_id(..)` before calling `generics_of` avoids an ICE. + if cx.tcx.res_generics_def_id(path_segment.res).is_some() { + let generics = cx.tcx.generics_of(def_id); + let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && generics.params.is_empty() + { + Certainty::Certain(None) + } else { + Certainty::Uncertain + }; + let rhs = path_segment + .args + .map_or(Certainty::Uncertain, |args| generic_args_certainty(cx, args)); + // See the comment preceding `qpath_certainty`. `def_id` could refer to a type or a value. + let certainty = lhs.join_clearing_def_ids(rhs); + if resolves_to_type { + certainty.with_def_id(def_id) + } else { + certainty + } + } else { + Certainty::Certain(None) + } + }, + + Res::PrimTy(_) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::SelfCtor(_) => { + Certainty::Certain(None) + }, + + // `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`. + Res::Local(hir_id) => match cx.tcx.hir().get_parent(hir_id) { + // An argument's type is always certain. + Node::Param(..) => Certainty::Certain(None), + // A local's type is certain if its type annotation is certain or it has an initializer whose + // type is certain. + Node::Local(local) => { + let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)); + let rhs = local + .init + .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init)); + let certainty = lhs.join(rhs); + if resolves_to_type { + certainty + } else { + certainty.clear_def_id() + } + }, + _ => Certainty::Uncertain, + }, + + _ => Certainty::Uncertain, + }; + debug_assert!(resolves_to_type || certainty.to_def_id().is_none()); + certainty +} + +/// For at least some `QPath::TypeRelative`, the path segment's `res` can be `Res::Err`. +/// `update_res` tries to fix the resolution when `parent_certainty` is `Certain(Some(..))`. +fn update_res(cx: &LateContext<'_>, parent_certainty: Certainty, path_segment: &PathSegment<'_>) -> Option { + if path_segment.res == Res::Err && let Some(def_id) = parent_certainty.to_def_id() { + let mut def_path = cx.get_def_path(def_id); + def_path.push(path_segment.ident.name); + let reses = def_path_res(cx, &def_path.iter().map(Symbol::as_str).collect::>()); + if let [res] = reses.as_slice() { Some(*res) } else { None } + } else { + None + } +} + +#[allow(clippy::cast_possible_truncation)] +fn type_is_inferrable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let Some(callee_def_id) = (match expr.kind { + ExprKind::Call(callee, _) => { + let callee_ty = cx.typeck_results().expr_ty(callee); + if let ty::FnDef(callee_def_id, _) = callee_ty.kind() { + Some(*callee_def_id) + } else { + None + } + }, + ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }) else { + return false; + }; + + let generics = cx.tcx.generics_of(callee_def_id); + let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + + // Check that all type parameters appear in the functions input types. + (0..(generics.parent_count + generics.params.len()) as u32).all(|index| { + fn_sig + .inputs() + .iter() + .any(|input_ty| contains_param(*input_ty.skip_binder(), index)) + }) +} + +fn self_ty<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId) -> Ty<'tcx> { + cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()[0] +} + +fn adt_def_id(ty: Ty<'_>) -> Option { + ty.peel_refs().ty_adt_def().map(AdtDef::did) +} + +fn contains_param(ty: Ty<'_>, index: u32) -> bool { + ty.walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Type(ty) if ty.is_param(index))) +} diff --git a/tests/ui/unwrap_or_else_default.fixed b/tests/ui/unwrap_or_else_default.fixed index 08b89a18bbbd..e6b6ab359dfb 100644 --- a/tests/ui/unwrap_or_else_default.fixed +++ b/tests/ui/unwrap_or_else_default.fixed @@ -74,4 +74,41 @@ fn unwrap_or_else_default() { empty_string.unwrap_or_default(); } +fn type_certainty(option: Option>) { + option.unwrap_or_default().push(1); + + let option: std::option::Option> = None; + option.unwrap_or_default().push(1); + + let option: Option> = None; + option.unwrap_or_default().push(1); + + let option = std::option::Option::>::None; + option.unwrap_or_default().push(1); + + let option = Option::>::None; + option.unwrap_or_default().push(1); + + let option = std::option::Option::None::>; + option.unwrap_or_default().push(1); + + let option = Option::None::>; + option.unwrap_or_default().push(1); + + let option = None::>; + option.unwrap_or_default().push(1); + + // should not be changed: type annotation with infer, unconcretized initializer + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = Option::None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = None; + option.unwrap_or_else(Vec::new).push(1); +} + fn main() {} diff --git a/tests/ui/unwrap_or_else_default.rs b/tests/ui/unwrap_or_else_default.rs index ad2a744908fc..38fa2e4595df 100644 --- a/tests/ui/unwrap_or_else_default.rs +++ b/tests/ui/unwrap_or_else_default.rs @@ -74,4 +74,41 @@ fn unwrap_or_else_default() { empty_string.unwrap_or_else(|| "".to_string()); } +fn type_certainty(option: Option>) { + option.unwrap_or_else(Vec::new).push(1); + + let option: std::option::Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + let option = std::option::Option::>::None; + option.unwrap_or_else(Vec::new).push(1); + + let option = Option::>::None; + option.unwrap_or_else(Vec::new).push(1); + + let option = std::option::Option::None::>; + option.unwrap_or_else(Vec::new).push(1); + + let option = Option::None::>; + option.unwrap_or_else(Vec::new).push(1); + + let option = None::>; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: type annotation with infer, unconcretized initializer + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = Option::None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = None; + option.unwrap_or_else(Vec::new).push(1); +} + fn main() {} diff --git a/tests/ui/unwrap_or_else_default.stderr b/tests/ui/unwrap_or_else_default.stderr index d2b9212223f7..a6815e8f9169 100644 --- a/tests/ui/unwrap_or_else_default.stderr +++ b/tests/ui/unwrap_or_else_default.stderr @@ -36,5 +36,53 @@ error: use of `.unwrap_or_else(..)` to construct default value LL | empty_string.unwrap_or_else(|| "".to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `empty_string.unwrap_or_default()` -error: aborting due to 6 previous errors +error: use of `.unwrap_or_else(..)` to construct default value + --> $DIR/unwrap_or_else_default.rs:78:5 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()` + +error: use of `.unwrap_or_else(..)` to construct default value + --> $DIR/unwrap_or_else_default.rs:81:5 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()` + +error: use of `.unwrap_or_else(..)` to construct default value + --> $DIR/unwrap_or_else_default.rs:84:5 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()` + +error: use of `.unwrap_or_else(..)` to construct default value + --> $DIR/unwrap_or_else_default.rs:87:5 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()` + +error: use of `.unwrap_or_else(..)` to construct default value + --> $DIR/unwrap_or_else_default.rs:90:5 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()` + +error: use of `.unwrap_or_else(..)` to construct default value + --> $DIR/unwrap_or_else_default.rs:93:5 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()` + +error: use of `.unwrap_or_else(..)` to construct default value + --> $DIR/unwrap_or_else_default.rs:96:5 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()` + +error: use of `.unwrap_or_else(..)` to construct default value + --> $DIR/unwrap_or_else_default.rs:99:5 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()` + +error: aborting due to 14 previous errors