Skip to content

Commit 5fb18a6

Browse files
committed
Suggest using deref in patterns
Fixes #132784
1 parent 42b2496 commit 5fb18a6

File tree

9 files changed

+493
-42
lines changed

9 files changed

+493
-42
lines changed

compiler/rustc_hir_typeck/src/pat.rs

+42-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ use rustc_errors::{
1010
};
1111
use rustc_hir::def::{CtorKind, DefKind, Res};
1212
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
13-
use rustc_hir::{self as hir, BindingMode, ByRef, HirId, LangItem, Mutability, Pat, PatKind};
13+
use rustc_hir::{
14+
self as hir, BindingMode, ByRef, ExprKind, HirId, LangItem, Mutability, Pat, PatKind,
15+
is_range_literal,
16+
};
1417
use rustc_infer::infer;
18+
use rustc_middle::traits::PatternOriginExpr;
1519
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
1620
use rustc_middle::{bug, span_bug};
1721
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
@@ -94,10 +98,46 @@ struct PatInfo<'a, 'tcx> {
9498

9599
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
96100
fn pattern_cause(&self, ti: &TopInfo<'tcx>, cause_span: Span) -> ObligationCause<'tcx> {
101+
let needs_parens = ti
102+
.origin_expr
103+
.map(|expr| match expr.kind {
104+
// parenthesize if needed (Issue #46756)
105+
hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true,
106+
// parenthesize borrows of range literals (Issue #54505)
107+
_ if is_range_literal(expr) => true,
108+
_ => false,
109+
})
110+
.unwrap_or(false);
111+
112+
// If origin_expr exists, then expected represents the type of origin_expr.
113+
// If span also exists, then span == origin_expr.span (although it doesn't need to exist).
114+
// In that case, we can peel away references from both and treat them
115+
// as the same.
116+
let origin_expr_info = if let Some(origin_expr) = ti.origin_expr {
117+
let mut cur_expr = origin_expr;
118+
let mut count = 0;
119+
120+
// cur_ty may have more layers of references than cur_expr.
121+
// We can only make suggestions about cur_expr, however, so we'll
122+
// use that as our condition for stopping.
123+
while let ExprKind::AddrOf(.., inner) = &cur_expr.kind {
124+
cur_expr = inner;
125+
count += 1;
126+
}
127+
128+
Some(PatternOriginExpr {
129+
peeled_span: cur_expr.span,
130+
peeled_count: count,
131+
peeled_prefix_suggestion_parentheses: needs_parens,
132+
})
133+
} else {
134+
None
135+
};
136+
97137
let code = ObligationCauseCode::Pattern {
98138
span: ti.span,
99139
root_ty: ti.expected,
100-
origin_expr: ti.origin_expr.is_some(),
140+
origin_expr: origin_expr_info,
101141
};
102142
self.cause(cause_span, code)
103143
}

compiler/rustc_middle/src/traits/mod.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,8 @@ pub enum ObligationCauseCode<'tcx> {
312312
span: Option<Span>,
313313
/// The root expected type induced by a scrutinee or type expression.
314314
root_ty: Ty<'tcx>,
315-
/// Whether the `Span` came from an expression or a type expression.
316-
origin_expr: bool,
315+
/// Information about the `Span`, if it came from an expression, otherwise `None`.
316+
origin_expr: Option<PatternOriginExpr>,
317317
},
318318

319319
/// Computing common supertype in an if expression
@@ -526,6 +526,25 @@ pub struct MatchExpressionArmCause<'tcx> {
526526
pub tail_defines_return_position_impl_trait: Option<LocalDefId>,
527527
}
528528

529+
/// Information about the origin expression of a pattern, relevant to diagnostics.
530+
/// Fields here refer to the scrutinee of a pattern.
531+
/// If the scrutinee isn't given in the diagnostic, then this won't exist.
532+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
533+
#[derive(TypeFoldable, TypeVisitable, HashStable, TyEncodable, TyDecodable)]
534+
pub struct PatternOriginExpr {
535+
/// A span representing the scrutinee expression, with all leading references
536+
/// peeled from the expression.
537+
/// Only references in the expression are peeled - if the expression refers to a variable
538+
/// whose type is a reference, then that reference is kept because it wasn't created
539+
/// in the expression.
540+
pub peeled_span: Span,
541+
/// The number of references that were peeled to produce `peeled_span`.
542+
pub peeled_count: usize,
543+
/// Does the peeled expression need to be wrapped in parentheses for
544+
/// a prefix suggestion (i.e., dereference) to be valid.
545+
pub peeled_prefix_suggestion_parentheses: bool,
546+
}
547+
529548
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
530549
#[derive(TypeFoldable, TypeVisitable, HashStable, TyEncodable, TyDecodable)]
531550
pub struct IfExpressionCause<'tcx> {

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

+94-21
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ use rustc_hir::{self as hir};
6161
use rustc_macros::extension;
6262
use rustc_middle::bug;
6363
use rustc_middle::dep_graph::DepContext;
64+
use rustc_middle::traits::PatternOriginExpr;
6465
use rustc_middle::ty::error::{ExpectedFound, TypeError, TypeErrorToStringExt};
6566
use rustc_middle::ty::print::{PrintError, PrintTraitRefExt as _, with_forced_trimmed_paths};
6667
use rustc_middle::ty::{
67-
self, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
68+
self, List, ParamEnv, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
6869
TypeVisitableExt,
6970
};
7071
use rustc_span::{BytePos, DesugaringKind, Pos, Span, sym};
@@ -74,7 +75,7 @@ use crate::error_reporting::TypeErrCtxt;
7475
use crate::errors::{ObligationCauseFailureCode, TypeErrorAdditionalDiags};
7576
use crate::infer;
7677
use crate::infer::relate::{self, RelateResult, TypeRelation};
77-
use crate::infer::{InferCtxt, TypeTrace, ValuePairs};
78+
use crate::infer::{InferCtxt, InferCtxtExt as _, TypeTrace, ValuePairs};
7879
use crate::solve::deeply_normalize_for_diagnostics;
7980
use crate::traits::{
8081
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
@@ -339,38 +340,71 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
339340
cause: &ObligationCause<'tcx>,
340341
exp_found: Option<ty::error::ExpectedFound<Ty<'tcx>>>,
341342
terr: TypeError<'tcx>,
343+
param_env: Option<ParamEnv<'tcx>>,
342344
) {
343345
match *cause.code() {
344-
ObligationCauseCode::Pattern { origin_expr: true, span: Some(span), root_ty } => {
345-
let ty = self.resolve_vars_if_possible(root_ty);
346-
if !matches!(ty.kind(), ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_)))
347-
{
346+
ObligationCauseCode::Pattern {
347+
origin_expr: Some(origin_expr),
348+
span: Some(span),
349+
root_ty,
350+
} => {
351+
let expected_ty = self.resolve_vars_if_possible(root_ty);
352+
if !matches!(
353+
expected_ty.kind(),
354+
ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_))
355+
) {
348356
// don't show type `_`
349357
if span.desugaring_kind() == Some(DesugaringKind::ForLoop)
350-
&& let ty::Adt(def, args) = ty.kind()
358+
&& let ty::Adt(def, args) = expected_ty.kind()
351359
&& Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option)
352360
{
353361
err.span_label(
354362
span,
355363
format!("this is an iterator with items of type `{}`", args.type_at(0)),
356364
);
357365
} else {
358-
err.span_label(span, format!("this expression has type `{ty}`"));
366+
err.span_label(span, format!("this expression has type `{expected_ty}`"));
359367
}
360368
}
361369
if let Some(ty::error::ExpectedFound { found, .. }) = exp_found
362-
&& ty.boxed_ty() == Some(found)
363-
&& let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
370+
&& let Ok(mut peeled_snippet) =
371+
self.tcx.sess.source_map().span_to_snippet(origin_expr.peeled_span)
364372
{
365-
err.span_suggestion(
366-
span,
367-
"consider dereferencing the boxed value",
368-
format!("*{snippet}"),
369-
Applicability::MachineApplicable,
370-
);
373+
// Parentheses are needed for cases like as casts.
374+
// We use the peeled_span for deref suggestions.
375+
// It's also safe to use for box, since box only triggers if there
376+
// wasn't a reference to begin with.
377+
if origin_expr.peeled_prefix_suggestion_parentheses {
378+
peeled_snippet = format!("({peeled_snippet})");
379+
}
380+
381+
// Try giving a box suggestion first, as it is a special case of the
382+
// deref suggestion.
383+
if expected_ty.boxed_ty() == Some(found) {
384+
err.span_suggestion_verbose(
385+
span,
386+
"consider dereferencing the boxed value",
387+
format!("*{peeled_snippet}"),
388+
Applicability::MachineApplicable,
389+
);
390+
} else if let Some(param_env) = param_env
391+
&& let Some(prefix) = self.should_deref_suggestion_on_mismatch(
392+
param_env,
393+
found,
394+
expected_ty,
395+
origin_expr,
396+
)
397+
{
398+
err.span_suggestion_verbose(
399+
span,
400+
"consider dereferencing to access the inner value using the Deref trait",
401+
format!("{prefix}{peeled_snippet}"),
402+
Applicability::MaybeIncorrect,
403+
);
404+
}
371405
}
372406
}
373-
ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => {
407+
ObligationCauseCode::Pattern { origin_expr: None, span: Some(span), .. } => {
374408
err.span_label(span, "expected due to this");
375409
}
376410
ObligationCauseCode::BlockTailExpression(
@@ -524,6 +558,45 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
524558
}
525559
}
526560

561+
/// Determines whether deref_to == <deref_from as Deref>::Target, and if so,
562+
/// returns a prefix that should be added to deref_from as a suggestion.
563+
fn should_deref_suggestion_on_mismatch(
564+
&self,
565+
param_env: ParamEnv<'tcx>,
566+
deref_to: Ty<'tcx>,
567+
deref_from: Ty<'tcx>,
568+
origin_expr: PatternOriginExpr,
569+
) -> Option<String> {
570+
// origin_expr contains stripped away versions of our expression.
571+
// We'll want to use that to avoid suggesting things like *&x.
572+
// However, the type that we have access to hasn't been stripped away,
573+
// so we need to ignore the first n dereferences, where n is the number
574+
// that's been stripped away in origin_expr.
575+
576+
// Find a way to autoderef from deraf_from to deref_to.
577+
let Some((num_derefs, (after_deref_ty, _))) = (self.autoderef_steps)(deref_from)
578+
.into_iter()
579+
.enumerate()
580+
.find(|(_, (ty, _))| self.infcx.can_eq(param_env, *ty, deref_to))
581+
else {
582+
return None;
583+
};
584+
585+
if num_derefs <= origin_expr.peeled_count {
586+
return None;
587+
}
588+
589+
let deref_part = "*".repeat(num_derefs - origin_expr.peeled_count);
590+
591+
// If the user used a reference in the original expression, they probably
592+
// want the suggestion to still give a reference.
593+
if deref_from.is_ref() && !after_deref_ty.is_ref() {
594+
Some(format!("&{deref_part}"))
595+
} else {
596+
Some(deref_part)
597+
}
598+
}
599+
527600
/// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value`
528601
/// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and
529602
/// populate `other_value` with `other_ty`.
@@ -1312,8 +1385,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
13121385
Variable(ty::error::ExpectedFound<Ty<'a>>),
13131386
Fixed(&'static str),
13141387
}
1315-
let (expected_found, exp_found, is_simple_error, values) = match values {
1316-
None => (None, Mismatch::Fixed("type"), false, None),
1388+
let (expected_found, exp_found, is_simple_error, values, param_env) = match values {
1389+
None => (None, Mismatch::Fixed("type"), false, None, None),
13171390
Some(ty::ParamEnvAnd { param_env, value: values }) => {
13181391
let mut values = self.resolve_vars_if_possible(values);
13191392
if self.next_trait_solver() {
@@ -1365,7 +1438,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
13651438
diag.downgrade_to_delayed_bug();
13661439
return;
13671440
};
1368-
(Some(vals), exp_found, is_simple_error, Some(values))
1441+
(Some(vals), exp_found, is_simple_error, Some(values), Some(param_env))
13691442
}
13701443
};
13711444

@@ -1693,7 +1766,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
16931766

16941767
// It reads better to have the error origin as the final
16951768
// thing.
1696-
self.note_error_origin(diag, cause, exp_found, terr);
1769+
self.note_error_origin(diag, cause, exp_found, terr, param_env);
16971770

16981771
debug!(?diag);
16991772
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
210210
(Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
211211
{
212212
ObligationCauseCode::Pattern { span: Some(then_span), origin_expr, .. } => {
213-
origin_expr.then_some(ConsiderAddingAwait::FutureSugg {
213+
origin_expr.is_some().then_some(ConsiderAddingAwait::FutureSugg {
214214
span: then_span.shrink_to_hi(),
215215
})
216216
}

tests/ui/closures/2229_closure_analysis/issue-118144.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ LL | V(x) = func_arg;
55
| ^^^^ -------- this expression has type `&mut V`
66
| |
77
| expected `&mut V`, found `V`
8+
|
9+
help: consider dereferencing to access the inner value using the Deref trait
10+
|
11+
LL | V(x) = &*func_arg;
12+
| ~~~~~~~~~~
813

914
error: aborting due to 1 previous error
1015

tests/ui/issues/issue-57741-dereference-boxed-value/issue-57741.stderr

+20-16
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,61 @@ error[E0308]: mismatched types
22
--> $DIR/issue-57741.rs:20:9
33
|
44
LL | let y = match x {
5-
| -
6-
| |
7-
| this expression has type `Box<T>`
8-
| help: consider dereferencing the boxed value: `*x`
5+
| - this expression has type `Box<T>`
96
LL | T::A(a) | T::B(a) => a,
107
| ^^^^^^^ expected `Box<T>`, found `T`
118
|
129
= note: expected struct `Box<T>`
1310
found enum `T`
11+
help: consider dereferencing the boxed value
12+
|
13+
LL | let y = match *x {
14+
| ~~
1415

1516
error[E0308]: mismatched types
1617
--> $DIR/issue-57741.rs:20:19
1718
|
1819
LL | let y = match x {
19-
| -
20-
| |
21-
| this expression has type `Box<T>`
22-
| help: consider dereferencing the boxed value: `*x`
20+
| - this expression has type `Box<T>`
2321
LL | T::A(a) | T::B(a) => a,
2422
| ^^^^^^^ expected `Box<T>`, found `T`
2523
|
2624
= note: expected struct `Box<T>`
2725
found enum `T`
26+
help: consider dereferencing the boxed value
27+
|
28+
LL | let y = match *x {
29+
| ~~
2830

2931
error[E0308]: mismatched types
3032
--> $DIR/issue-57741.rs:27:9
3133
|
3234
LL | let y = match x {
33-
| -
34-
| |
35-
| this expression has type `Box<S>`
36-
| help: consider dereferencing the boxed value: `*x`
35+
| - this expression has type `Box<S>`
3736
LL | S::A { a } | S::B { b: a } => a,
3837
| ^^^^^^^^^^ expected `Box<S>`, found `S`
3938
|
4039
= note: expected struct `Box<S>`
4140
found enum `S`
41+
help: consider dereferencing the boxed value
42+
|
43+
LL | let y = match *x {
44+
| ~~
4245

4346
error[E0308]: mismatched types
4447
--> $DIR/issue-57741.rs:27:22
4548
|
4649
LL | let y = match x {
47-
| -
48-
| |
49-
| this expression has type `Box<S>`
50-
| help: consider dereferencing the boxed value: `*x`
50+
| - this expression has type `Box<S>`
5151
LL | S::A { a } | S::B { b: a } => a,
5252
| ^^^^^^^^^^^^^ expected `Box<S>`, found `S`
5353
|
5454
= note: expected struct `Box<S>`
5555
found enum `S`
56+
help: consider dereferencing the boxed value
57+
|
58+
LL | let y = match *x {
59+
| ~~
5660

5761
error: aborting due to 4 previous errors
5862

0 commit comments

Comments
 (0)