Skip to content

Commit 6d2ba95

Browse files
committed
suggest Option::as_deref(_mut)
1 parent 69fef92 commit 6d2ba95

File tree

6 files changed

+355
-9
lines changed

6 files changed

+355
-9
lines changed

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

+96-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;
@@ -27,6 +27,7 @@ use rustc_infer::infer::error_reporting::TypeErrCtxt;
2727
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
2828
use rustc_infer::infer::{DefineOpaqueTypes, InferOk, LateBoundRegionConversionTime};
2929
use rustc_middle::hir::map;
30+
use rustc_middle::query::Key;
3031
use rustc_middle::ty::error::TypeError::{self, Sorts};
3132
use rustc_middle::ty::{
3233
self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind,
@@ -357,6 +358,15 @@ pub trait TypeErrCtxtExt<'tcx> {
357358
err: &mut Diagnostic,
358359
trait_pred: ty::PolyTraitPredicate<'tcx>,
359360
);
361+
362+
fn suggest_option_method_if_applicable(
363+
&self,
364+
failed_pred: ty::Predicate<'tcx>,
365+
param_env: ty::ParamEnv<'tcx>,
366+
err: &mut Diagnostic,
367+
expr: &hir::Expr<'_>,
368+
);
369+
360370
fn note_function_argument_obligation(
361371
&self,
362372
body_id: LocalDefId,
@@ -3660,15 +3670,92 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
36603670
err.replace_span_with(path.ident.span, true);
36613671
}
36623672
}
3663-
if let Some(Node::Expr(hir::Expr {
3664-
kind:
3665-
hir::ExprKind::Call(hir::Expr { span, .. }, _)
3666-
| hir::ExprKind::MethodCall(hir::PathSegment { ident: Ident { span, .. }, .. }, ..),
3667-
..
3668-
})) = hir.find(call_hir_id)
3673+
3674+
if let Some(Node::Expr(expr)) = hir.find(call_hir_id) {
3675+
if let hir::ExprKind::Call(hir::Expr { span, .. }, _)
3676+
| hir::ExprKind::MethodCall(
3677+
hir::PathSegment { ident: Ident { span, .. }, .. },
3678+
..,
3679+
) = expr.kind
3680+
{
3681+
if Some(*span) != err.span.primary_span() {
3682+
err.span_label(*span, "required by a bound introduced by this call");
3683+
}
3684+
}
3685+
3686+
if let hir::ExprKind::MethodCall(_, expr, ..) = expr.kind {
3687+
self.suggest_option_method_if_applicable(failed_pred, param_env, err, expr);
3688+
}
3689+
}
3690+
}
3691+
3692+
fn suggest_option_method_if_applicable(
3693+
&self,
3694+
failed_pred: ty::Predicate<'tcx>,
3695+
param_env: ty::ParamEnv<'tcx>,
3696+
err: &mut Diagnostic,
3697+
expr: &hir::Expr<'_>,
3698+
) {
3699+
let tcx = self.tcx;
3700+
let infcx = self.infcx;
3701+
let Some(typeck_results) = self.typeck_results.as_ref() else { return };
3702+
3703+
// Make sure we're dealing with the `Option` type.
3704+
let Some(ty_adt_did) = typeck_results.expr_ty_adjusted(expr).ty_adt_id() else { return };
3705+
if !tcx.is_diagnostic_item(sym::Option, ty_adt_did) {
3706+
return;
3707+
}
3708+
3709+
// Given the predicate `fn(&T): FnOnce<(U,)>`, extract `fn(&T)` and `(U,)`,
3710+
// then suggest `Option::as_deref(_mut)` if `U` can deref to `T`
3711+
if let ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { trait_ref, .. }))
3712+
= failed_pred.kind().skip_binder()
3713+
&& tcx.is_fn_trait(trait_ref.def_id)
3714+
&& let [self_ty, found_ty] = trait_ref.substs.as_slice()
3715+
&& let Some(fn_ty) = self_ty.as_type().filter(|ty| ty.is_fn())
3716+
&& let fn_sig @ ty::FnSig {
3717+
abi: abi::Abi::Rust,
3718+
c_variadic: false,
3719+
unsafety: hir::Unsafety::Normal,
3720+
..
3721+
} = fn_ty.fn_sig(tcx).skip_binder()
3722+
3723+
// Extract first param of fn sig with peeled refs, e.g. `fn(&T)` -> `T`
3724+
&& let Some(&ty::Ref(_, target_ty, needs_mut)) = fn_sig.inputs().first().map(|t| t.kind())
3725+
&& !target_ty.has_escaping_bound_vars()
3726+
3727+
// Extract first tuple element out of fn trait, e.g. `FnOnce<(U,)>` -> `U`
3728+
&& let Some(ty::Tuple(tys)) = found_ty.as_type().map(Ty::kind)
3729+
&& let &[found_ty] = tys.as_slice()
3730+
&& !found_ty.has_escaping_bound_vars()
3731+
3732+
// Extract `<U as Deref>::Target` assoc type and check that it is `T`
3733+
&& let Some(deref_target_did) = tcx.lang_items().deref_target()
3734+
&& let projection = tcx.mk_projection(deref_target_did, tcx.mk_substs(&[ty::GenericArg::from(found_ty)]))
3735+
&& let Ok(deref_target) = tcx.try_normalize_erasing_regions(param_env, projection)
3736+
&& deref_target == target_ty
36693737
{
3670-
if Some(*span) != err.span.primary_span() {
3671-
err.span_label(*span, "required by a bound introduced by this call");
3738+
let help = if let hir::Mutability::Mut = needs_mut
3739+
&& let Some(deref_mut_did) = tcx.lang_items().deref_mut_trait()
3740+
&& infcx
3741+
.type_implements_trait(deref_mut_did, iter::once(found_ty), param_env)
3742+
.must_apply_modulo_regions()
3743+
{
3744+
Some(("call `Option::as_deref_mut()` first", ".as_deref_mut()"))
3745+
} else if let hir::Mutability::Not = needs_mut {
3746+
Some(("call `Option::as_deref()` first", ".as_deref()"))
3747+
} else {
3748+
None
3749+
};
3750+
3751+
if let Some((msg, sugg)) = help {
3752+
err.span_suggestion_with_style(
3753+
expr.span.shrink_to_hi(),
3754+
msg,
3755+
sugg,
3756+
Applicability::MaybeIncorrect,
3757+
SuggestionStyle::ShowAlways
3758+
);
36723759
}
36733760
}
36743761
}
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)