Skip to content

Commit 91f222f

Browse files
authored
Rollup merge of #111659 - y21:suggest-as-deref, r=cjgillot
suggest `Option::as_deref(_mut)` on type mismatch in option combinator if it passes typeck Fixes #106342. This adds a suggestion to call `.as_deref()` (or `.as_deref_mut()` resp.) if typeck fails due to a type mismatch in the function passed to an `Option` combinator such as `.map()` or `.and_then()`. For example: ```rs fn foo(_: &str) {} Some(String::new()).map(foo); ``` The `.map()` method requires its argument to satisfy `F: FnOnce(String)`, but it received `fn(&str)`, which won't pass. However, placing a `.as_deref()` before the `.map()` call fixes this since `&str == &<String as Deref>::Target`
2 parents 2f5e6bb + 268b08b commit 91f222f

File tree

6 files changed

+354
-9
lines changed

6 files changed

+354
-9
lines changed

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+95-9
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_data_structures::fx::FxHashSet;
1313
use rustc_data_structures::stack::ensure_sufficient_stack;
1414
use rustc_errors::{
1515
error_code, pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder,
16-
ErrorGuaranteed, MultiSpan, Style,
16+
ErrorGuaranteed, MultiSpan, Style, SuggestionStyle,
1717
};
1818
use rustc_hir as hir;
1919
use rustc_hir::def::DefKind;
@@ -362,6 +362,15 @@ pub trait TypeErrCtxtExt<'tcx> {
362362
err: &mut Diagnostic,
363363
trait_pred: ty::PolyTraitPredicate<'tcx>,
364364
);
365+
366+
fn suggest_option_method_if_applicable(
367+
&self,
368+
failed_pred: ty::Predicate<'tcx>,
369+
param_env: ty::ParamEnv<'tcx>,
370+
err: &mut Diagnostic,
371+
expr: &hir::Expr<'_>,
372+
);
373+
365374
fn note_function_argument_obligation(
366375
&self,
367376
body_id: LocalDefId,
@@ -3521,15 +3530,92 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
35213530
err.replace_span_with(path.ident.span, true);
35223531
}
35233532
}
3524-
if let Some(Node::Expr(hir::Expr {
3525-
kind:
3526-
hir::ExprKind::Call(hir::Expr { span, .. }, _)
3527-
| hir::ExprKind::MethodCall(hir::PathSegment { ident: Ident { span, .. }, .. }, ..),
3528-
..
3529-
})) = hir.find(call_hir_id)
3533+
3534+
if let Some(Node::Expr(expr)) = hir.find(call_hir_id) {
3535+
if let hir::ExprKind::Call(hir::Expr { span, .. }, _)
3536+
| hir::ExprKind::MethodCall(
3537+
hir::PathSegment { ident: Ident { span, .. }, .. },
3538+
..,
3539+
) = expr.kind
3540+
{
3541+
if Some(*span) != err.span.primary_span() {
3542+
err.span_label(*span, "required by a bound introduced by this call");
3543+
}
3544+
}
3545+
3546+
if let hir::ExprKind::MethodCall(_, expr, ..) = expr.kind {
3547+
self.suggest_option_method_if_applicable(failed_pred, param_env, err, expr);
3548+
}
3549+
}
3550+
}
3551+
3552+
fn suggest_option_method_if_applicable(
3553+
&self,
3554+
failed_pred: ty::Predicate<'tcx>,
3555+
param_env: ty::ParamEnv<'tcx>,
3556+
err: &mut Diagnostic,
3557+
expr: &hir::Expr<'_>,
3558+
) {
3559+
let tcx = self.tcx;
3560+
let infcx = self.infcx;
3561+
let Some(typeck_results) = self.typeck_results.as_ref() else { return };
3562+
3563+
// Make sure we're dealing with the `Option` type.
3564+
let Some(option_ty_adt) = typeck_results.expr_ty_adjusted(expr).ty_adt_def() else { return };
3565+
if !tcx.is_diagnostic_item(sym::Option, option_ty_adt.did()) {
3566+
return;
3567+
}
3568+
3569+
// Given the predicate `fn(&T): FnOnce<(U,)>`, extract `fn(&T)` and `(U,)`,
3570+
// then suggest `Option::as_deref(_mut)` if `U` can deref to `T`
3571+
if let ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { trait_ref, .. }))
3572+
= failed_pred.kind().skip_binder()
3573+
&& tcx.is_fn_trait(trait_ref.def_id)
3574+
&& let [self_ty, found_ty] = trait_ref.substs.as_slice()
3575+
&& let Some(fn_ty) = self_ty.as_type().filter(|ty| ty.is_fn())
3576+
&& let fn_sig @ ty::FnSig {
3577+
abi: abi::Abi::Rust,
3578+
c_variadic: false,
3579+
unsafety: hir::Unsafety::Normal,
3580+
..
3581+
} = fn_ty.fn_sig(tcx).skip_binder()
3582+
3583+
// Extract first param of fn sig with peeled refs, e.g. `fn(&T)` -> `T`
3584+
&& let Some(&ty::Ref(_, target_ty, needs_mut)) = fn_sig.inputs().first().map(|t| t.kind())
3585+
&& !target_ty.has_escaping_bound_vars()
3586+
3587+
// Extract first tuple element out of fn trait, e.g. `FnOnce<(U,)>` -> `U`
3588+
&& let Some(ty::Tuple(tys)) = found_ty.as_type().map(Ty::kind)
3589+
&& let &[found_ty] = tys.as_slice()
3590+
&& !found_ty.has_escaping_bound_vars()
3591+
3592+
// Extract `<U as Deref>::Target` assoc type and check that it is `T`
3593+
&& let Some(deref_target_did) = tcx.lang_items().deref_target()
3594+
&& let projection = tcx.mk_projection(deref_target_did, tcx.mk_substs(&[ty::GenericArg::from(found_ty)]))
3595+
&& let Ok(deref_target) = tcx.try_normalize_erasing_regions(param_env, projection)
3596+
&& deref_target == target_ty
35303597
{
3531-
if Some(*span) != err.span.primary_span() {
3532-
err.span_label(*span, "required by a bound introduced by this call");
3598+
let help = if let hir::Mutability::Mut = needs_mut
3599+
&& let Some(deref_mut_did) = tcx.lang_items().deref_mut_trait()
3600+
&& infcx
3601+
.type_implements_trait(deref_mut_did, iter::once(found_ty), param_env)
3602+
.must_apply_modulo_regions()
3603+
{
3604+
Some(("call `Option::as_deref_mut()` first", ".as_deref_mut()"))
3605+
} else if let hir::Mutability::Not = needs_mut {
3606+
Some(("call `Option::as_deref()` first", ".as_deref()"))
3607+
} else {
3608+
None
3609+
};
3610+
3611+
if let Some((msg, sugg)) = help {
3612+
err.span_suggestion_with_style(
3613+
expr.span.shrink_to_hi(),
3614+
msg,
3615+
sugg,
3616+
Applicability::MaybeIncorrect,
3617+
SuggestionStyle::ShowAlways
3618+
);
35333619
}
35343620
}
35353621
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
fn produces_string() -> Option<String> {
2+
Some("my cool string".to_owned())
3+
}
4+
5+
fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
6+
Some(())
7+
}
8+
9+
fn no_args() -> Option<()> {
10+
Some(())
11+
}
12+
13+
fn generic_ref<T>(_: &T) -> Option<()> {
14+
Some(())
15+
}
16+
17+
extern "C" fn takes_str_but_wrong_abi(_: &str) -> Option<()> {
18+
Some(())
19+
}
20+
21+
unsafe fn takes_str_but_unsafe(_: &str) -> Option<()> {
22+
Some(())
23+
}
24+
25+
struct TypeWithoutDeref;
26+
27+
fn main() {
28+
let _ = produces_string().and_then(takes_str_but_too_many_refs);
29+
//~^ ERROR type mismatch in function arguments
30+
let _ = produces_string().and_then(takes_str_but_wrong_abi);
31+
//~^ ERROR expected a `FnOnce<(String,)>` closure, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
32+
let _ = produces_string().and_then(takes_str_but_unsafe);
33+
//~^ ERROR expected a `FnOnce<(String,)>` closure, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
34+
let _ = produces_string().and_then(no_args);
35+
//~^ ERROR function is expected to take 1 argument, but it takes 0 arguments
36+
let _ = produces_string().and_then(generic_ref);
37+
//~^ ERROR type mismatch in function arguments
38+
let _ = Some(TypeWithoutDeref).and_then(takes_str_but_too_many_refs);
39+
//~^ ERROR type mismatch in function arguments
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
error[E0631]: type mismatch in function arguments
2+
--> $DIR/suggest-option-asderef-unfixable.rs:28:40
3+
|
4+
LL | fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
5+
| ------------------------------------------------------ found signature defined here
6+
...
7+
LL | let _ = produces_string().and_then(takes_str_but_too_many_refs);
8+
| -------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected due to this
9+
| |
10+
| required by a bound introduced by this call
11+
|
12+
= note: expected function signature `fn(String) -> _`
13+
found function signature `for<'a, 'b> fn(&'a &'b str) -> _`
14+
note: required by a bound in `Option::<T>::and_then`
15+
--> $SRC_DIR/core/src/option.rs:LL:COL
16+
17+
error[E0277]: expected a `FnOnce<(String,)>` closure, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
18+
--> $DIR/suggest-option-asderef-unfixable.rs:30:40
19+
|
20+
LL | let _ = produces_string().and_then(takes_str_but_wrong_abi);
21+
| -------- ^^^^^^^^^^^^^^^^^^^^^^^ expected an `FnOnce<(String,)>` closure, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
22+
| |
23+
| required by a bound introduced by this call
24+
|
25+
= help: the trait `FnOnce<(String,)>` is not implemented for fn item `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
26+
note: required by a bound in `Option::<T>::and_then`
27+
--> $SRC_DIR/core/src/option.rs:LL:COL
28+
29+
error[E0277]: expected a `FnOnce<(String,)>` closure, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
30+
--> $DIR/suggest-option-asderef-unfixable.rs:32:40
31+
|
32+
LL | let _ = produces_string().and_then(takes_str_but_unsafe);
33+
| -------- ^^^^^^^^^^^^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }`
34+
| |
35+
| required by a bound introduced by this call
36+
|
37+
= help: the trait `FnOnce<(String,)>` is not implemented for fn item `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}`
38+
= note: unsafe function cannot be called generically without an unsafe block
39+
note: required by a bound in `Option::<T>::and_then`
40+
--> $SRC_DIR/core/src/option.rs:LL:COL
41+
42+
error[E0593]: function is expected to take 1 argument, but it takes 0 arguments
43+
--> $DIR/suggest-option-asderef-unfixable.rs:34:40
44+
|
45+
LL | fn no_args() -> Option<()> {
46+
| -------------------------- takes 0 arguments
47+
...
48+
LL | let _ = produces_string().and_then(no_args);
49+
| -------- ^^^^^^^ expected function that takes 1 argument
50+
| |
51+
| required by a bound introduced by this call
52+
|
53+
note: required by a bound in `Option::<T>::and_then`
54+
--> $SRC_DIR/core/src/option.rs:LL:COL
55+
56+
error[E0631]: type mismatch in function arguments
57+
--> $DIR/suggest-option-asderef-unfixable.rs:36:40
58+
|
59+
LL | fn generic_ref<T>(_: &T) -> Option<()> {
60+
| -------------------------------------- found signature defined here
61+
...
62+
LL | let _ = produces_string().and_then(generic_ref);
63+
| -------- ^^^^^^^^^^^ expected due to this
64+
| |
65+
| required by a bound introduced by this call
66+
|
67+
= note: expected function signature `fn(String) -> _`
68+
found function signature `for<'a> fn(&'a _) -> _`
69+
note: required by a bound in `Option::<T>::and_then`
70+
--> $SRC_DIR/core/src/option.rs:LL:COL
71+
help: do not borrow the argument
72+
|
73+
LL - fn generic_ref<T>(_: &T) -> Option<()> {
74+
LL + fn generic_ref<T>(_: T) -> Option<()> {
75+
|
76+
77+
error[E0631]: type mismatch in function arguments
78+
--> $DIR/suggest-option-asderef-unfixable.rs:38:45
79+
|
80+
LL | fn takes_str_but_too_many_refs(_: &&str) -> Option<()> {
81+
| ------------------------------------------------------ found signature defined here
82+
...
83+
LL | let _ = Some(TypeWithoutDeref).and_then(takes_str_but_too_many_refs);
84+
| -------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected due to this
85+
| |
86+
| required by a bound introduced by this call
87+
|
88+
= note: expected function signature `fn(TypeWithoutDeref) -> _`
89+
found function signature `for<'a, 'b> fn(&'a &'b str) -> _`
90+
note: required by a bound in `Option::<T>::and_then`
91+
--> $SRC_DIR/core/src/option.rs:LL:COL
92+
93+
error: aborting due to 6 previous errors
94+
95+
Some errors have detailed explanations: E0277, E0593, E0631.
96+
For more information about an error, try `rustc --explain E0277`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// run-rustfix
2+
3+
fn produces_string() -> Option<String> {
4+
Some("my cool string".to_owned())
5+
}
6+
7+
fn takes_str(_: &str) -> Option<()> {
8+
Some(())
9+
}
10+
11+
fn takes_str_mut(_: &mut str) -> Option<()> {
12+
Some(())
13+
}
14+
15+
fn generic<T>(_: T) -> Option<()> {
16+
Some(())
17+
}
18+
19+
fn main() {
20+
let _: Option<()> = produces_string().as_deref().and_then(takes_str);
21+
//~^ ERROR type mismatch in function arguments
22+
//~| HELP call `Option::as_deref()` first
23+
let _: Option<Option<()>> = produces_string().as_deref().map(takes_str);
24+
//~^ ERROR type mismatch in function arguments
25+
//~| HELP call `Option::as_deref()` first
26+
let _: Option<Option<()>> = produces_string().as_deref_mut().map(takes_str_mut);
27+
//~^ ERROR type mismatch in function arguments
28+
//~| HELP call `Option::as_deref_mut()` first
29+
let _ = produces_string().and_then(generic);
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// run-rustfix
2+
3+
fn produces_string() -> Option<String> {
4+
Some("my cool string".to_owned())
5+
}
6+
7+
fn takes_str(_: &str) -> Option<()> {
8+
Some(())
9+
}
10+
11+
fn takes_str_mut(_: &mut str) -> Option<()> {
12+
Some(())
13+
}
14+
15+
fn generic<T>(_: T) -> Option<()> {
16+
Some(())
17+
}
18+
19+
fn main() {
20+
let _: Option<()> = produces_string().and_then(takes_str);
21+
//~^ ERROR type mismatch in function arguments
22+
//~| HELP call `Option::as_deref()` first
23+
let _: Option<Option<()>> = produces_string().map(takes_str);
24+
//~^ ERROR type mismatch in function arguments
25+
//~| HELP call `Option::as_deref()` first
26+
let _: Option<Option<()>> = produces_string().map(takes_str_mut);
27+
//~^ ERROR type mismatch in function arguments
28+
//~| HELP call `Option::as_deref_mut()` first
29+
let _ = produces_string().and_then(generic);
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
error[E0631]: type mismatch in function arguments
2+
--> $DIR/suggest-option-asderef.rs:20:52
3+
|
4+
LL | fn takes_str(_: &str) -> Option<()> {
5+
| ----------------------------------- found signature defined here
6+
...
7+
LL | let _: Option<()> = produces_string().and_then(takes_str);
8+
| -------- ^^^^^^^^^ expected due to this
9+
| |
10+
| required by a bound introduced by this call
11+
|
12+
= note: expected function signature `fn(String) -> _`
13+
found function signature `for<'a> fn(&'a str) -> _`
14+
note: required by a bound in `Option::<T>::and_then`
15+
--> $SRC_DIR/core/src/option.rs:LL:COL
16+
help: call `Option::as_deref()` first
17+
|
18+
LL | let _: Option<()> = produces_string().as_deref().and_then(takes_str);
19+
| +++++++++++
20+
21+
error[E0631]: type mismatch in function arguments
22+
--> $DIR/suggest-option-asderef.rs:23:55
23+
|
24+
LL | fn takes_str(_: &str) -> Option<()> {
25+
| ----------------------------------- found signature defined here
26+
...
27+
LL | let _: Option<Option<()>> = produces_string().map(takes_str);
28+
| --- ^^^^^^^^^ expected due to this
29+
| |
30+
| required by a bound introduced by this call
31+
|
32+
= note: expected function signature `fn(String) -> _`
33+
found function signature `for<'a> fn(&'a str) -> _`
34+
note: required by a bound in `Option::<T>::map`
35+
--> $SRC_DIR/core/src/option.rs:LL:COL
36+
help: call `Option::as_deref()` first
37+
|
38+
LL | let _: Option<Option<()>> = produces_string().as_deref().map(takes_str);
39+
| +++++++++++
40+
41+
error[E0631]: type mismatch in function arguments
42+
--> $DIR/suggest-option-asderef.rs:26:55
43+
|
44+
LL | fn takes_str_mut(_: &mut str) -> Option<()> {
45+
| ------------------------------------------- found signature defined here
46+
...
47+
LL | let _: Option<Option<()>> = produces_string().map(takes_str_mut);
48+
| --- ^^^^^^^^^^^^^ expected due to this
49+
| |
50+
| required by a bound introduced by this call
51+
|
52+
= note: expected function signature `fn(String) -> _`
53+
found function signature `for<'a> fn(&'a mut str) -> _`
54+
note: required by a bound in `Option::<T>::map`
55+
--> $SRC_DIR/core/src/option.rs:LL:COL
56+
help: call `Option::as_deref_mut()` first
57+
|
58+
LL | let _: Option<Option<()>> = produces_string().as_deref_mut().map(takes_str_mut);
59+
| +++++++++++++++
60+
61+
error: aborting due to 3 previous errors
62+
63+
For more information about this error, try `rustc --explain E0631`.

0 commit comments

Comments
 (0)