Skip to content

Commit

Permalink
Add suggestion to use Option::map_or over erroneous Option::unwrap_or
Browse files Browse the repository at this point in the history
  • Loading branch information
tesuji committed Jul 12, 2024
1 parent abeb720 commit 5e1cabe
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 1 deletion.
11 changes: 11 additions & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&mut err,
);

self.suggest_deref_unwrap_or(
&mut err,
error_span,
callee_ty,
call_ident,
expected_ty,
provided_ty,
provided_args[*provided_idx],
is_method,
);

// Call out where the function is defined
self.label_fn_like(
&mut err,
Expand Down
65 changes: 65 additions & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,71 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
true
}

// Suggest to change `Option<&Vec<T>>::unwrap_or(&[])` to `Option::map_or(&[], |v| v)`.
#[instrument(level = "trace", skip(self, err, provided_expr))]
pub(crate) fn suggest_deref_unwrap_or(
&self,
err: &mut Diag<'_>,
error_span: Span,
callee_ty: Option<Ty<'tcx>>,
call_ident: Option<Ident>,
expected_ty: Ty<'tcx>,
provided_ty: Ty<'tcx>,
provided_expr: &Expr<'tcx>,
is_method: bool,
) {
if !is_method {
return;
}
let Some(callee_ty) = callee_ty else {
return;
};
let ty::Adt(callee_adt, _) = callee_ty.peel_refs().kind() else {
return;
};
if !self.tcx.is_diagnostic_item(sym::Option, callee_adt.did()) {
return;
}

if call_ident.map_or(true, |ident| ident.name != sym::unwrap_or) {
return;
}

let ty::Ref(_, peeled, _mutability) = provided_ty.kind() else {
return;
};

// NOTE: Can we reuse `suggest_deref_or_ref`?

// Create an dummy type `&[_]` so that both &[] and `&Vec<T>` can coerce to it.
let dummy_ty = if let ty::Array(elem_ty, size) = peeled.kind()
&& let ty::Infer(_) = elem_ty.kind()
&& size.try_eval_target_usize(self.tcx, self.param_env) == Some(0)
{
let slice = Ty::new_slice(self.tcx, *elem_ty);
Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, slice)
} else {
provided_ty
};

if !self.can_coerce(expected_ty, dummy_ty) {
return;
}
let (provided_snip, applicability) =
match self.tcx.sess.source_map().span_to_snippet(provided_expr.span) {
Ok(snip) => (snip, Applicability::MachineApplicable),
Err(_) => ("/* _ */".to_owned(), Applicability::MaybeIncorrect),
};
let sugg = &format!("map_or({provided_snip}, |v| v)");
err.span_suggestion_verbose(
error_span,
"use `Option::map_or` to deref inner value of `Option`",
sugg,
applicability,
);
return;
}

/// Suggest wrapping the block in square brackets instead of curly braces
/// in case the block was mistaken array syntax, e.g. `{ 1 }` -> `[ 1 ]`.
pub(crate) fn suggest_block_to_brackets(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ help: try using `.map(|v| &**v)` to convert `Option<&Vec<i32>>` to `Option<&[i32
LL | arg.map(|v| &**v)
| ++++++++++++++

error: aborting due to 1 previous error
error[E0308]: mismatched types
--> $DIR/transforming-option-ref-issue-127545.rs:9:19
|
LL | arg.unwrap_or(&[])
Expand All @@ -32,6 +32,10 @@ LL | arg.unwrap_or(&[])
| this argument influences the return type of `unwrap_or`
note: method defined here
--> $SRC_DIR/core/src/option.rs:LL:COL
help: use `Option::map_or` to deref inner value of `Option`
|
LL | arg.map_or(&[], |v| v)
| ~~~~~~~~~~~~~~~~~~

error[E0308]: mismatched types
--> $DIR/transforming-option-ref-issue-127545.rs:13:19
Expand All @@ -52,6 +56,10 @@ LL | arg.unwrap_or(v)
| this argument influences the return type of `unwrap_or`
note: method defined here
--> $SRC_DIR/core/src/option.rs:LL:COL
help: use `Option::map_or` to deref inner value of `Option`
|
LL | arg.map_or(v, |v| v)
| ~~~~~~~~~~~~~~~~

error: aborting due to 3 previous errors

Expand Down

0 comments on commit 5e1cabe

Please sign in to comment.