Skip to content

Commit af54d58

Browse files
More robust as_ref/as_deref suggestions
1 parent 522ae84 commit af54d58

File tree

12 files changed

+207
-152
lines changed

12 files changed

+207
-152
lines changed

compiler/rustc_hir_typeck/messages.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ hir_typeck_const_select_must_be_fn = this argument must be a function item
2525
2626
hir_typeck_convert_to_str = try converting the passed type into a `&str`
2727
28+
hir_typeck_convert_using_method = try using `{$sugg}` to convert `{$found}` to `{$expected}`
29+
2830
hir_typeck_ctor_is_private = tuple struct constructor `{$def}` is private
2931
3032
hir_typeck_expected_default_return_type = expected `()` because of default return type

compiler/rustc_hir_typeck/src/errors.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,18 @@ pub struct CtorIsPrivate {
327327
pub span: Span,
328328
pub def: String,
329329
}
330+
331+
#[derive(Subdiagnostic)]
332+
#[suggestion(
333+
hir_typeck_convert_using_method,
334+
code = "{sugg}",
335+
applicability = "machine-applicable",
336+
style = "verbose"
337+
)]
338+
pub struct SuggestConvertViaMethod<'tcx> {
339+
#[primary_span]
340+
pub span: Span,
341+
pub sugg: &'static str,
342+
pub expected: Ty<'tcx>,
343+
pub found: Ty<'tcx>,
344+
}

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

Lines changed: 137 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use super::FnCtxt;
22

3-
use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel, SuggestBoxing};
3+
use crate::errors::{
4+
AddReturnTypeSuggestion, ExpectedReturnTypeLabel, SuggestBoxing, SuggestConvertViaMethod,
5+
};
46
use crate::fluent_generated as fluent;
57
use crate::method::probe::{IsSuggestion, Mode, ProbeScope};
68
use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX};
@@ -275,6 +277,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
275277
expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
276278
) -> bool {
277279
let expr = expr.peel_blocks();
280+
let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id);
281+
278282
if let Some((suggestion, msg, applicability, verbose, annotation)) =
279283
self.suggest_deref_or_ref(expr, found, expected)
280284
{
@@ -325,9 +329,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
325329
}
326330
}
327331
return true;
328-
} else if self.suggest_else_fn_with_closure(err, expr, found, expected) {
332+
}
333+
334+
if self.suggest_else_fn_with_closure(err, expr, found, expected) {
329335
return true;
330-
} else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected))
336+
}
337+
338+
if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected))
331339
&& let ty::FnDef(def_id, ..) = *found.kind()
332340
&& let Some(sp) = self.tcx.hir().span_if_local(def_id)
333341
{
@@ -343,97 +351,142 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
343351
err.span_label(sp, format!("{descr} `{name}` defined here"));
344352
}
345353
return true;
346-
} else if self.suggest_cast(err, expr, found, expected, expected_ty_expr) {
354+
}
355+
356+
if self.suggest_cast(err, expr, found, expected, expected_ty_expr) {
347357
return true;
348-
} else {
349-
let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id);
350-
if !methods.is_empty() {
351-
let mut suggestions = methods.iter()
352-
.filter_map(|conversion_method| {
353-
let receiver_method_ident = expr.method_ident();
354-
if let Some(method_ident) = receiver_method_ident
355-
&& method_ident.name == conversion_method.name
356-
{
357-
return None // do not suggest code that is already there (#53348)
358-
}
358+
}
359359

360-
let method_call_list = [sym::to_vec, sym::to_string];
361-
let mut sugg = if let ExprKind::MethodCall(receiver_method, ..) = expr.kind
362-
&& receiver_method.ident.name == sym::clone
363-
&& method_call_list.contains(&conversion_method.name)
364-
// If receiver is `.clone()` and found type has one of those methods,
365-
// we guess that the user wants to convert from a slice type (`&[]` or `&str`)
366-
// to an owned type (`Vec` or `String`). These conversions clone internally,
367-
// so we remove the user's `clone` call.
368-
{
369-
vec![(
370-
receiver_method.ident.span,
371-
conversion_method.name.to_string()
372-
)]
373-
} else if expr.precedence().order()
374-
< ExprPrecedence::MethodCall.order()
375-
{
376-
vec![
377-
(expr.span.shrink_to_lo(), "(".to_string()),
378-
(expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)),
379-
]
380-
} else {
381-
vec![(expr.span.shrink_to_hi(), format!(".{}()", conversion_method.name))]
382-
};
383-
let struct_pat_shorthand_field = self.maybe_get_struct_pattern_shorthand_field(expr);
384-
if let Some(name) = struct_pat_shorthand_field {
385-
sugg.insert(
386-
0,
387-
(expr.span.shrink_to_lo(), format!("{}: ", name)),
388-
);
389-
}
390-
Some(sugg)
391-
})
392-
.peekable();
393-
if suggestions.peek().is_some() {
394-
err.multipart_suggestions(
395-
"try using a conversion method",
396-
suggestions,
397-
Applicability::MaybeIncorrect,
398-
);
399-
return true;
400-
}
401-
} else if let ty::Adt(found_adt, found_substs) = found.kind()
402-
&& self.tcx.is_diagnostic_item(sym::Option, found_adt.did())
403-
&& let ty::Adt(expected_adt, expected_substs) = expected.kind()
404-
&& self.tcx.is_diagnostic_item(sym::Option, expected_adt.did())
405-
&& let ty::Ref(_, inner_ty, _) = expected_substs.type_at(0).kind()
406-
&& inner_ty.is_str()
407-
{
408-
let ty = found_substs.type_at(0);
409-
let mut peeled = ty;
410-
let mut ref_cnt = 0;
411-
while let ty::Ref(_, inner, _) = peeled.kind() {
412-
peeled = *inner;
413-
ref_cnt += 1;
414-
}
415-
if let ty::Adt(adt, _) = peeled.kind()
416-
&& Some(adt.did()) == self.tcx.lang_items().string()
417-
{
418-
let sugg = if ref_cnt == 0 {
419-
".as_deref()"
360+
if !methods.is_empty() {
361+
let mut suggestions = methods
362+
.iter()
363+
.filter_map(|conversion_method| {
364+
let receiver_method_ident = expr.method_ident();
365+
if let Some(method_ident) = receiver_method_ident
366+
&& method_ident.name == conversion_method.name
367+
{
368+
return None // do not suggest code that is already there (#53348)
369+
}
370+
371+
let method_call_list = [sym::to_vec, sym::to_string];
372+
let mut sugg = if let ExprKind::MethodCall(receiver_method, ..) = expr.kind
373+
&& receiver_method.ident.name == sym::clone
374+
&& method_call_list.contains(&conversion_method.name)
375+
// If receiver is `.clone()` and found type has one of those methods,
376+
// we guess that the user wants to convert from a slice type (`&[]` or `&str`)
377+
// to an owned type (`Vec` or `String`). These conversions clone internally,
378+
// so we remove the user's `clone` call.
379+
{
380+
vec![(
381+
receiver_method.ident.span,
382+
conversion_method.name.to_string()
383+
)]
384+
} else if expr.precedence().order()
385+
< ExprPrecedence::MethodCall.order()
386+
{
387+
vec![
388+
(expr.span.shrink_to_lo(), "(".to_string()),
389+
(expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)),
390+
]
420391
} else {
421-
".map(|x| x.as_str())"
392+
vec![(expr.span.shrink_to_hi(), format!(".{}()", conversion_method.name))]
422393
};
423-
err.span_suggestion_verbose(
424-
expr.span.shrink_to_hi(),
425-
fluent::hir_typeck_convert_to_str,
426-
sugg,
427-
Applicability::MachineApplicable,
428-
);
429-
return true;
430-
}
394+
let struct_pat_shorthand_field =
395+
self.maybe_get_struct_pattern_shorthand_field(expr);
396+
if let Some(name) = struct_pat_shorthand_field {
397+
sugg.insert(0, (expr.span.shrink_to_lo(), format!("{}: ", name)));
398+
}
399+
Some(sugg)
400+
})
401+
.peekable();
402+
if suggestions.peek().is_some() {
403+
err.multipart_suggestions(
404+
"try using a conversion method",
405+
suggestions,
406+
Applicability::MaybeIncorrect,
407+
);
408+
return true;
409+
}
410+
}
411+
412+
if let Some((found_ty_inner, expected_ty_inner, error_tys)) =
413+
self.deconstruct_option_or_result(found, expected)
414+
&& let ty::Ref(_, peeled, hir::Mutability::Not) = *expected_ty_inner.kind()
415+
{
416+
// Check that given `Result<_, E>`, our expected ty is `Result<_, &E>`
417+
let error_tys_equate_as_ref = error_tys.map_or(true, |(found, expected)| {
418+
self.can_eq(self.param_env, self.tcx.mk_imm_ref(self.tcx.lifetimes.re_erased, found), expected)
419+
});
420+
// FIXME: This could/should be extended to suggest `as_mut` and `as_deref_mut`,
421+
// but those checks need to be a bit more delicate and the benefit is diminishing.
422+
if self.can_eq(self.param_env, found_ty_inner, peeled) && error_tys_equate_as_ref {
423+
err.subdiagnostic(SuggestConvertViaMethod {
424+
span: expr.span.shrink_to_hi(),
425+
sugg: ".as_ref()",
426+
expected,
427+
found,
428+
});
429+
return true;
430+
} else if let Some((deref_ty, _)) =
431+
self.autoderef(expr.span, found_ty_inner).silence_errors().nth(1)
432+
&& self.can_eq(self.param_env, deref_ty, peeled)
433+
&& error_tys_equate_as_ref
434+
{
435+
err.subdiagnostic(SuggestConvertViaMethod {
436+
span: expr.span.shrink_to_hi(),
437+
sugg: ".as_deref()",
438+
expected,
439+
found,
440+
});
441+
return true;
442+
} else if let ty::Adt(adt, _) = found_ty_inner.peel_refs().kind()
443+
&& Some(adt.did()) == self.tcx.lang_items().string()
444+
&& peeled.is_str()
445+
&& error_tys.map_or(true, |(found, expected)| {
446+
self.can_eq(self.param_env, found, expected)
447+
})
448+
{
449+
err.span_suggestion_verbose(
450+
expr.span.shrink_to_hi(),
451+
fluent::hir_typeck_convert_to_str,
452+
".map(|x| x.as_str())",
453+
Applicability::MachineApplicable,
454+
);
455+
return true;
431456
}
432457
}
433458

434459
false
435460
}
436461

462+
fn deconstruct_option_or_result(
463+
&self,
464+
found_ty: Ty<'tcx>,
465+
expected_ty: Ty<'tcx>,
466+
) -> Option<(Ty<'tcx>, Ty<'tcx>, Option<(Ty<'tcx>, Ty<'tcx>)>)> {
467+
let ty::Adt(found_adt, found_substs) = found_ty.peel_refs().kind() else {
468+
return None;
469+
};
470+
let ty::Adt(expected_adt, expected_substs) = expected_ty.kind() else {
471+
return None;
472+
};
473+
if self.tcx.is_diagnostic_item(sym::Option, found_adt.did())
474+
&& self.tcx.is_diagnostic_item(sym::Option, expected_adt.did())
475+
{
476+
Some((found_substs.type_at(0), expected_substs.type_at(0), None))
477+
} else if self.tcx.is_diagnostic_item(sym::Result, found_adt.did())
478+
&& self.tcx.is_diagnostic_item(sym::Result, expected_adt.did())
479+
{
480+
Some((
481+
found_substs.type_at(0),
482+
expected_substs.type_at(0),
483+
Some((found_substs.type_at(1), expected_substs.type_at(1))),
484+
))
485+
} else {
486+
None
487+
}
488+
}
489+
437490
/// When encountering the expected boxed value allocated in the stack, suggest allocating it
438491
/// in the heap by calling `Box::new()`.
439492
pub(in super::super) fn suggest_boxing_when_appropriate(

compiler/rustc_infer/messages.ftl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,6 @@ infer_ril_introduced_by = requirement introduced by this return type
278278
infer_ril_introduced_here = `'static` requirement introduced here
279279
infer_ril_static_introduced_by = "`'static` lifetime requirement introduced by the return type
280280
281-
infer_sarwa_option = you can convert from `&Option<T>` to `Option<&T>` using `.as_ref()`
282-
infer_sarwa_result = you can convert from `&Result<T, E>` to `Result<&T, &E>` using `.as_ref()`
283-
284281
infer_sbfrit_box_return_expr = if you change the return type to expect trait objects, box the returned expressions
285282
286283
infer_sbfrit_change_return_type = you could change the return type to be a boxed trait object

compiler/rustc_infer/src/errors/mod.rs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,30 +1246,6 @@ pub struct FnConsiderCasting {
12461246
pub casting: String,
12471247
}
12481248

1249-
#[derive(Subdiagnostic)]
1250-
pub enum SuggestAsRefWhereAppropriate<'a> {
1251-
#[suggestion(
1252-
infer_sarwa_option,
1253-
code = "{snippet}.as_ref()",
1254-
applicability = "machine-applicable"
1255-
)]
1256-
Option {
1257-
#[primary_span]
1258-
span: Span,
1259-
snippet: &'a str,
1260-
},
1261-
#[suggestion(
1262-
infer_sarwa_result,
1263-
code = "{snippet}.as_ref()",
1264-
applicability = "machine-applicable"
1265-
)]
1266-
Result {
1267-
#[primary_span]
1268-
span: Span,
1269-
snippet: &'a str,
1270-
},
1271-
}
1272-
12731249
#[derive(Subdiagnostic)]
12741250
pub enum SuggestAccessingField<'a> {
12751251
#[suggestion(

compiler/rustc_infer/src/infer/error_reporting/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1897,7 +1897,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
18971897

18981898
if should_suggest_fixes {
18991899
self.suggest_tuple_pattern(cause, &exp_found, diag);
1900-
self.suggest_as_ref_where_appropriate(span, &exp_found, diag);
19011900
self.suggest_accessing_field_where_appropriate(cause, &exp_found, diag);
19021901
self.suggest_await_on_expect_found(cause, span, &exp_found, diag);
19031902
self.suggest_function_pointers(cause, span, &exp_found, diag);

compiler/rustc_infer/src/infer/error_reporting/suggest.rs

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use rustc_span::{sym, BytePos, Span};
1313

1414
use crate::errors::{
1515
ConsiderAddingAwait, FnConsiderCasting, FnItemsAreDistinct, FnUniqTypes,
16-
FunctionPointerSuggestion, SuggestAccessingField, SuggestAsRefWhereAppropriate,
17-
SuggestBoxingForReturnImplTrait, SuggestRemoveSemiOrReturnBinding, SuggestTuplePatternMany,
18-
SuggestTuplePatternOne, TypeErrorAdditionalDiags,
16+
FunctionPointerSuggestion, SuggestAccessingField, SuggestBoxingForReturnImplTrait,
17+
SuggestRemoveSemiOrReturnBinding, SuggestTuplePatternMany, SuggestTuplePatternOne,
18+
TypeErrorAdditionalDiags,
1919
};
2020

2121
use super::TypeErrCtxt;
@@ -289,27 +289,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
289289
}
290290
}
291291

292-
/// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
293-
/// suggests it.
294-
pub(super) fn suggest_as_ref_where_appropriate(
295-
&self,
296-
span: Span,
297-
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
298-
diag: &mut Diagnostic,
299-
) {
300-
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
301-
&& let Some(msg) = self.should_suggest_as_ref_kind(exp_found.expected, exp_found.found)
302-
{
303-
// HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
304-
let snippet = snippet.trim_start_matches('&');
305-
let subdiag = match msg {
306-
SuggestAsRefKind::Option => SuggestAsRefWhereAppropriate::Option { span, snippet },
307-
SuggestAsRefKind::Result => SuggestAsRefWhereAppropriate::Result { span, snippet },
308-
};
309-
diag.subdiagnostic(subdiag);
310-
}
311-
}
312-
313292
pub(super) fn suggest_function_pointers(
314293
&self,
315294
cause: &ObligationCause<'tcx>,

0 commit comments

Comments
 (0)