Auto merge of #114150 - clubby789:improve-option-ref-suggestion, r=WaffleLapkin

Refactor + improve diagnostics for `&mut T`/`T` mismatch inside Option/Result

Follow up to #114052. This also makes the diagnostics structured + translatable.

r? `@WaffleLapkin`
This commit is contained in:
bors 2023-07-29 13:15:56 +00:00
commit f9f674f2bc
7 changed files with 153 additions and 74 deletions

View File

@ -77,6 +77,10 @@ hir_typeck_note_edition_guide = for more on editions, read https://doc.rust-lang
hir_typeck_op_trait_generic_params = `{$method_name}` must not have any generic parameters hir_typeck_op_trait_generic_params = `{$method_name}` must not have any generic parameters
hir_typeck_option_result_asref = use `{$def_path}::as_ref` to convert `{$expected_ty}` to `{$expr_ty}`
hir_typeck_option_result_cloned = use `{$def_path}::cloned` to clone the value inside the `{$def_path}`
hir_typeck_option_result_copied = use `{$def_path}::copied` to copy the value inside the `{$def_path}`
hir_typeck_return_stmt_outside_of_fn_body = hir_typeck_return_stmt_outside_of_fn_body =
{$statement_kind} statement outside of function body {$statement_kind} statement outside of function body
.encl_body_label = the {$statement_kind} is part of this body... .encl_body_label = the {$statement_kind} is part of this body...

View File

@ -252,6 +252,45 @@ pub fn new() -> Self {
} }
} }
#[derive(Subdiagnostic)]
pub enum OptionResultRefMismatch<'tcx> {
#[suggestion(
hir_typeck_option_result_copied,
code = ".copied()",
style = "verbose",
applicability = "machine-applicable"
)]
Copied {
#[primary_span]
span: Span,
def_path: String,
},
#[suggestion(
hir_typeck_option_result_cloned,
code = ".cloned()",
style = "verbose",
applicability = "machine-applicable"
)]
Cloned {
#[primary_span]
span: Span,
def_path: String,
},
#[suggestion(
hir_typeck_option_result_asref,
code = ".as_ref()",
style = "verbose",
applicability = "machine-applicable"
)]
AsRef {
#[primary_span]
span: Span,
def_path: String,
expected_ty: Ty<'tcx>,
expr_ty: Ty<'tcx>,
},
}
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(hir_typeck_const_select_must_be_const)] #[diag(hir_typeck_const_select_must_be_const)]
#[help] #[help]

View File

@ -1,8 +1,6 @@
use super::FnCtxt; use super::FnCtxt;
use crate::errors::{ use crate::errors;
AddReturnTypeSuggestion, ExpectedReturnTypeLabel, SuggestBoxing, SuggestConvertViaMethod,
};
use crate::fluent_generated as fluent; use crate::fluent_generated as fluent;
use crate::method::probe::{IsSuggestion, Mode, ProbeScope}; use crate::method::probe::{IsSuggestion, Mode, ProbeScope};
use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX}; use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX};
@ -434,7 +432,7 @@ pub fn suggest_deref_ref_or_into(
// FIXME: This could/should be extended to suggest `as_mut` and `as_deref_mut`, // FIXME: This could/should be extended to suggest `as_mut` and `as_deref_mut`,
// but those checks need to be a bit more delicate and the benefit is diminishing. // but those checks need to be a bit more delicate and the benefit is diminishing.
if self.can_eq(self.param_env, found_ty_inner, peeled) && error_tys_equate_as_ref { if self.can_eq(self.param_env, found_ty_inner, peeled) && error_tys_equate_as_ref {
err.subdiagnostic(SuggestConvertViaMethod { err.subdiagnostic(errors::SuggestConvertViaMethod {
span: expr.span.shrink_to_hi(), span: expr.span.shrink_to_hi(),
sugg: ".as_ref()", sugg: ".as_ref()",
expected, expected,
@ -447,7 +445,7 @@ pub fn suggest_deref_ref_or_into(
&& self.can_eq(self.param_env, deref_ty, peeled) && self.can_eq(self.param_env, deref_ty, peeled)
&& error_tys_equate_as_ref && error_tys_equate_as_ref
{ {
err.subdiagnostic(SuggestConvertViaMethod { err.subdiagnostic(errors::SuggestConvertViaMethod {
span: expr.span.shrink_to_hi(), span: expr.span.shrink_to_hi(),
sugg: ".as_deref()", sugg: ".as_deref()",
expected, expected,
@ -521,7 +519,7 @@ pub(in super::super) fn suggest_boxing_when_appropriate(
if self.can_coerce(Ty::new_box(self.tcx, found), expected) { if self.can_coerce(Ty::new_box(self.tcx, found), expected) {
let suggest_boxing = match found.kind() { let suggest_boxing = match found.kind() {
ty::Tuple(tuple) if tuple.is_empty() => { ty::Tuple(tuple) if tuple.is_empty() => {
SuggestBoxing::Unit { start: span.shrink_to_lo(), end: span } errors::SuggestBoxing::Unit { start: span.shrink_to_lo(), end: span }
} }
ty::Generator(def_id, ..) ty::Generator(def_id, ..)
if matches!( if matches!(
@ -529,9 +527,12 @@ pub(in super::super) fn suggest_boxing_when_appropriate(
Some(GeneratorKind::Async(AsyncGeneratorKind::Closure)) Some(GeneratorKind::Async(AsyncGeneratorKind::Closure))
) => ) =>
{ {
SuggestBoxing::AsyncBody errors::SuggestBoxing::AsyncBody
} }
_ => SuggestBoxing::Other { start: span.shrink_to_lo(), end: span.shrink_to_hi() }, _ => errors::SuggestBoxing::Other {
start: span.shrink_to_lo(),
end: span.shrink_to_hi(),
},
}; };
err.subdiagnostic(suggest_boxing); err.subdiagnostic(suggest_boxing);
@ -756,23 +757,23 @@ pub(in super::super) fn suggest_missing_return_type(
match &fn_decl.output { match &fn_decl.output {
&hir::FnRetTy::DefaultReturn(span) if expected.is_unit() && !can_suggest => { &hir::FnRetTy::DefaultReturn(span) if expected.is_unit() && !can_suggest => {
// `fn main()` must return `()`, do not suggest changing return type // `fn main()` must return `()`, do not suggest changing return type
err.subdiagnostic(ExpectedReturnTypeLabel::Unit { span }); err.subdiagnostic(errors::ExpectedReturnTypeLabel::Unit { span });
return true; return true;
} }
&hir::FnRetTy::DefaultReturn(span) if expected.is_unit() => { &hir::FnRetTy::DefaultReturn(span) if expected.is_unit() => {
if let Some(found) = found.make_suggestable(self.tcx, false) { if let Some(found) = found.make_suggestable(self.tcx, false) {
err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: found.to_string() }); err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: found.to_string() });
return true; return true;
} else if let ty::Closure(_, args) = found.kind() } else if let ty::Closure(_, args) = found.kind()
// FIXME(compiler-errors): Get better at printing binders... // FIXME(compiler-errors): Get better at printing binders...
&& let closure = args.as_closure() && let closure = args.as_closure()
&& closure.sig().is_suggestable(self.tcx, false) && closure.sig().is_suggestable(self.tcx, false)
{ {
err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: closure.print_as_impl_trait().to_string() }); err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: closure.print_as_impl_trait().to_string() });
return true; return true;
} else { } else {
// FIXME: if `found` could be `impl Iterator` we should suggest that. // FIXME: if `found` could be `impl Iterator` we should suggest that.
err.subdiagnostic(AddReturnTypeSuggestion::MissingHere { span }); err.subdiagnostic(errors::AddReturnTypeSuggestion::MissingHere { span });
return true return true
} }
} }
@ -794,10 +795,10 @@ pub(in super::super) fn suggest_missing_return_type(
debug!(?found); debug!(?found);
if found.is_suggestable(self.tcx, false) { if found.is_suggestable(self.tcx, false) {
if term.span.is_empty() { if term.span.is_empty() {
err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: found.to_string() }); err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: found.to_string() });
return true; return true;
} else { } else {
err.subdiagnostic(ExpectedReturnTypeLabel::Other { span, expected }); err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span, expected });
} }
} }
} }
@ -813,7 +814,7 @@ pub(in super::super) fn suggest_missing_return_type(
let ty = self.normalize(span, ty); let ty = self.normalize(span, ty);
let ty = self.tcx.erase_late_bound_regions(ty); let ty = self.tcx.erase_late_bound_regions(ty);
if self.can_coerce(expected, ty) { if self.can_coerce(expected, ty) {
err.subdiagnostic(ExpectedReturnTypeLabel::Other { span, expected }); err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span, expected });
self.try_suggest_return_impl_trait(err, expected, ty, fn_id); self.try_suggest_return_impl_trait(err, expected, ty, fn_id);
return true; return true;
} }
@ -1103,65 +1104,46 @@ pub(crate) fn suggest_copied_cloned_or_as_ref(
return false; return false;
} }
let mut suggest_copied_cloned_or_as_ref = || { if Some(adt_def.did()) == self.tcx.get_diagnostic_item(sym::Result)
&& self.can_eq(self.param_env, args.type_at(1), expected_args.type_at(1))
|| Some(adt_def.did()) == self.tcx.get_diagnostic_item(sym::Option)
{
let expr_inner_ty = args.type_at(0); let expr_inner_ty = args.type_at(0);
let expected_inner_ty = expected_args.type_at(0); let expected_inner_ty = expected_args.type_at(0);
if let &ty::Ref(_, ty, hir::Mutability::Not) = expr_inner_ty.kind() if let &ty::Ref(_, ty, mutability) = expr_inner_ty.kind()
&& self.can_eq(self.param_env, ty, expected_inner_ty) && self.can_eq(self.param_env, ty, expected_inner_ty)
{
let def_path = self.tcx.def_path_str(adt_def.did());
if self.type_is_copy_modulo_regions(self.param_env, ty) {
diag.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"use `{def_path}::copied` to copy the value inside the `{def_path}`"
),
".copied()",
Applicability::MachineApplicable,
);
return true;
} else if let Some(expected_ty_expr) = expected_ty_expr {
diag.span_suggestion_verbose(
expected_ty_expr.span.shrink_to_hi(),
format!(
"use `{def_path}::as_ref()` to convert `{expected_ty}` to `{expr_ty}`"
),
".as_ref()",
Applicability::MachineApplicable,
);
return true;
} else if let Some(clone_did) = self.tcx.lang_items().clone_trait()
&& rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions(
self,
self.param_env,
ty,
clone_did,
)
{ {
diag.span_suggestion_verbose( let def_path = self.tcx.def_path_str(adt_def.did());
expr.span.shrink_to_hi(), let span = expr.span.shrink_to_hi();
format!( let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) {
"use `{def_path}::cloned` to clone the value inside the `{def_path}`" errors::OptionResultRefMismatch::Copied {
), span, def_path
".cloned()", }
Applicability::MachineApplicable, } else if let Some(expected_ty_expr) = expected_ty_expr
); // FIXME: suggest changes to both expressions to convert both to
// Option/Result<&T>
&& mutability.is_not()
{
errors::OptionResultRefMismatch::AsRef {
span: expected_ty_expr.span.shrink_to_hi(), expected_ty, expr_ty, def_path
}
} else if let Some(clone_did) = self.tcx.lang_items().clone_trait()
&& rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions(
self,
self.param_env,
ty,
clone_did,
)
{
errors::OptionResultRefMismatch::Cloned {
span, def_path
}
} else {
return false;
};
diag.subdiagnostic(subdiag);
return true; return true;
} }
}
false
};
if let Some(result_did) = self.tcx.get_diagnostic_item(sym::Result)
&& adt_def.did() == result_did
// Check that the error types are equal
&& self.can_eq(self.param_env, args.type_at(1), expected_args.type_at(1))
{
return suggest_copied_cloned_or_as_ref();
} else if let Some(option_did) = self.tcx.get_diagnostic_item(sym::Option)
&& adt_def.did() == option_did
{
return suggest_copied_cloned_or_as_ref();
} }
false false

View File

@ -6,7 +6,7 @@ LL | debug_assert_eq!(iter.next(), Some(value));
| |
= note: expected enum `Option<<I as Iterator>::Item>` = note: expected enum `Option<<I as Iterator>::Item>`
found enum `Option<&<I as Iterator>::Item>` found enum `Option<&<I as Iterator>::Item>`
help: use `Option::as_ref()` to convert `Option<<I as Iterator>::Item>` to `Option<&<I as Iterator>::Item>` help: use `Option::as_ref` to convert `Option<<I as Iterator>::Item>` to `Option<&<I as Iterator>::Item>`
| |
LL | debug_assert_eq!(iter.next().as_ref(), Some(value)); LL | debug_assert_eq!(iter.next().as_ref(), Some(value));
| +++++++++ | +++++++++

View File

@ -26,6 +26,20 @@ fn main() {
let y = Some(&s); let y = Some(&s);
println!("{}", x.as_ref() == y); println!("{}", x.as_ref() == y);
//~^ ERROR mismatched types //~^ ERROR mismatched types
//~| HELP use `Option::as_ref()` to convert `Option<String>` to `Option<&String>` //~| HELP use `Option::as_ref` to convert `Option<String>` to `Option<&String>`
let mut s = ();
let x = Some(s);
let y = Some(&mut s);
println!("{}", x == y.copied());
//~^ ERROR mismatched types
//~| HELP use `Option::copied` to copy the value inside the `Option`
let mut s = String::new();
let x = Some(s.clone());
let y = Some(&mut s);
println!("{}", x == y.cloned());
//~^ ERROR mismatched types
//~| HELP use `Option::cloned` to clone the value inside the `Option`
} }

View File

@ -26,6 +26,20 @@ fn main() {
let y = Some(&s); let y = Some(&s);
println!("{}", x == y); println!("{}", x == y);
//~^ ERROR mismatched types //~^ ERROR mismatched types
//~| HELP use `Option::as_ref()` to convert `Option<String>` to `Option<&String>` //~| HELP use `Option::as_ref` to convert `Option<String>` to `Option<&String>`
let mut s = ();
let x = Some(s);
let y = Some(&mut s);
println!("{}", x == y);
//~^ ERROR mismatched types
//~| HELP use `Option::copied` to copy the value inside the `Option`
let mut s = String::new();
let x = Some(s.clone());
let y = Some(&mut s);
println!("{}", x == y);
//~^ ERROR mismatched types
//~| HELP use `Option::cloned` to clone the value inside the `Option`
} }

View File

@ -86,11 +86,37 @@ LL | println!("{}", x == y);
| |
= note: expected enum `Option<String>` = note: expected enum `Option<String>`
found enum `Option<&String>` found enum `Option<&String>`
help: use `Option::as_ref()` to convert `Option<String>` to `Option<&String>` help: use `Option::as_ref` to convert `Option<String>` to `Option<&String>`
| |
LL | println!("{}", x.as_ref() == y); LL | println!("{}", x.as_ref() == y);
| +++++++++ | +++++++++
error: aborting due to 5 previous errors error[E0308]: mismatched types
--> $DIR/copied-and-cloned.rs:35:25
|
LL | println!("{}", x == y);
| ^ expected `Option<()>`, found `Option<&mut ()>`
|
= note: expected enum `Option<()>`
found enum `Option<&mut ()>`
help: use `Option::copied` to copy the value inside the `Option`
|
LL | println!("{}", x == y.copied());
| +++++++++
error[E0308]: mismatched types
--> $DIR/copied-and-cloned.rs:42:25
|
LL | println!("{}", x == y);
| ^ expected `Option<String>`, found `Option<&mut String>`
|
= note: expected enum `Option<String>`
found enum `Option<&mut String>`
help: use `Option::cloned` to clone the value inside the `Option`
|
LL | println!("{}", x == y.cloned());
| +++++++++
error: aborting due to 7 previous errors
For more information about this error, try `rustc --explain E0308`. For more information about this error, try `rustc --explain E0308`.