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_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 =
{$statement_kind} statement outside of function 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)]
#[diag(hir_typeck_const_select_must_be_const)]
#[help]

View File

@ -1,8 +1,6 @@
use super::FnCtxt;
use crate::errors::{
AddReturnTypeSuggestion, ExpectedReturnTypeLabel, SuggestBoxing, SuggestConvertViaMethod,
};
use crate::errors;
use crate::fluent_generated as fluent;
use crate::method::probe::{IsSuggestion, Mode, ProbeScope};
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`,
// 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 {
err.subdiagnostic(SuggestConvertViaMethod {
err.subdiagnostic(errors::SuggestConvertViaMethod {
span: expr.span.shrink_to_hi(),
sugg: ".as_ref()",
expected,
@ -447,7 +445,7 @@ pub fn suggest_deref_ref_or_into(
&& self.can_eq(self.param_env, deref_ty, peeled)
&& error_tys_equate_as_ref
{
err.subdiagnostic(SuggestConvertViaMethod {
err.subdiagnostic(errors::SuggestConvertViaMethod {
span: expr.span.shrink_to_hi(),
sugg: ".as_deref()",
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) {
let suggest_boxing = match found.kind() {
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, ..)
if matches!(
@ -529,9 +527,12 @@ pub(in super::super) fn suggest_boxing_when_appropriate(
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);
@ -756,23 +757,23 @@ pub(in super::super) fn suggest_missing_return_type(
match &fn_decl.output {
&hir::FnRetTy::DefaultReturn(span) if expected.is_unit() && !can_suggest => {
// `fn main()` must return `()`, do not suggest changing return type
err.subdiagnostic(ExpectedReturnTypeLabel::Unit { span });
err.subdiagnostic(errors::ExpectedReturnTypeLabel::Unit { span });
return true;
}
&hir::FnRetTy::DefaultReturn(span) if expected.is_unit() => {
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;
} else if let ty::Closure(_, args) = found.kind()
// FIXME(compiler-errors): Get better at printing binders...
&& let closure = args.as_closure()
&& 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;
} else {
// FIXME: if `found` could be `impl Iterator` we should suggest that.
err.subdiagnostic(AddReturnTypeSuggestion::MissingHere { span });
err.subdiagnostic(errors::AddReturnTypeSuggestion::MissingHere { span });
return true
}
}
@ -794,10 +795,10 @@ pub(in super::super) fn suggest_missing_return_type(
debug!(?found);
if found.is_suggestable(self.tcx, false) {
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;
} 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.tcx.erase_late_bound_regions(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);
return true;
}
@ -1103,65 +1104,46 @@ pub(crate) fn suggest_copied_cloned_or_as_ref(
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 expected_inner_ty = expected_args.type_at(0);
if let &ty::Ref(_, ty, hir::Mutability::Not) = expr_inner_ty.kind()
&& 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,
)
if let &ty::Ref(_, ty, mutability) = expr_inner_ty.kind()
&& self.can_eq(self.param_env, ty, expected_inner_ty)
{
diag.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"use `{def_path}::cloned` to clone the value inside the `{def_path}`"
),
".cloned()",
Applicability::MachineApplicable,
);
let def_path = self.tcx.def_path_str(adt_def.did());
let span = expr.span.shrink_to_hi();
let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) {
errors::OptionResultRefMismatch::Copied {
span, def_path
}
} 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;
}
}
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

View File

@ -6,7 +6,7 @@ LL | debug_assert_eq!(iter.next(), Some(value));
|
= note: expected 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));
| +++++++++

View File

@ -26,6 +26,20 @@ fn main() {
let y = Some(&s);
println!("{}", x.as_ref() == y);
//~^ 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);
println!("{}", x == y);
//~^ 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>`
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);
| +++++++++
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`.