Skip to content

Commit ca71995

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

File tree

11 files changed

+404
-16
lines changed

11 files changed

+404
-16
lines changed

compiler/rustc_hir_analysis/src/check/compare_impl_item.rs

+3
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,7 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>(
628628
}))),
629629
terr,
630630
false,
631+
None,
631632
);
632633
return Err(diag.emit());
633634
}
@@ -1055,6 +1056,7 @@ fn report_trait_method_mismatch<'tcx>(
10551056
}))),
10561057
terr,
10571058
false,
1059+
None,
10581060
);
10591061

10601062
diag.emit()
@@ -1852,6 +1854,7 @@ fn compare_const_predicate_entailment<'tcx>(
18521854
}))),
18531855
terr,
18541856
false,
1857+
None,
18551858
);
18561859
return Err(diag.emit());
18571860
};

compiler/rustc_hir_analysis/src/check/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ pub fn check_function_signature<'tcx>(
651651
}))),
652652
err,
653653
false,
654+
None,
654655
);
655656
return Err(diag.emit());
656657
}

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11191119
Some(self.param_env.and(trace.values)),
11201120
e,
11211121
true,
1122+
None,
11221123
);
11231124
}
11241125
}

compiler/rustc_hir_typeck/src/pat.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ 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, HirId, LangItem, Mutability, Pat, PatKind, is_range_literal,
15+
};
1416
use rustc_infer::infer;
1517
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
1618
use rustc_middle::{bug, span_bug};
@@ -94,10 +96,22 @@ struct PatInfo<'a, 'tcx> {
9496

9597
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
9698
fn pattern_cause(&self, ti: &TopInfo<'tcx>, cause_span: Span) -> ObligationCause<'tcx> {
99+
let needs_parens = ti
100+
.origin_expr
101+
.map(|expr| match expr.kind {
102+
// parenthesize if needed (Issue #46756)
103+
hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true,
104+
// parenthesize borrows of range literals (Issue #54505)
105+
_ if is_range_literal(expr) => true,
106+
_ => false,
107+
})
108+
.unwrap_or(false);
109+
97110
let code = ObligationCauseCode::Pattern {
98111
span: ti.span,
99112
root_ty: ti.expected,
100113
origin_expr: ti.origin_expr.is_some(),
114+
prefix_suggestion_parentheses: needs_parens,
101115
};
102116
self.cause(cause_span, code)
103117
}

compiler/rustc_middle/src/traits/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ pub enum ObligationCauseCode<'tcx> {
314314
root_ty: Ty<'tcx>,
315315
/// Whether the `Span` came from an expression or a type expression.
316316
origin_expr: bool,
317+
/// If the `Span` came from an expression, does that expression need to be wrapped in
318+
/// parentheses for a prefix suggestion (i.e., dereference) to be valid.
319+
prefix_suggestion_parentheses: bool,
317320
},
318321

319322
/// Computing common supertype in an if expression

compiler/rustc_passes/src/check_attr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2321,6 +2321,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
23212321
}))),
23222322
terr,
23232323
false,
2324+
None,
23242325
);
23252326
diag.emit();
23262327
self.abort.set(true);

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

+123-11
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ use rustc_hir::def_id::DefId;
5858
use rustc_hir::intravisit::Visitor;
5959
use rustc_hir::lang_items::LangItem;
6060
use rustc_hir::{self as hir};
61+
use rustc_infer::infer::DefineOpaqueTypes;
62+
use rustc_infer::traits::Obligation;
6163
use rustc_macros::extension;
6264
use rustc_middle::bug;
6365
use rustc_middle::dep_graph::DepContext;
6466
use rustc_middle::ty::error::{ExpectedFound, TypeError, TypeErrorToStringExt};
6567
use rustc_middle::ty::print::{PrintError, PrintTraitRefExt as _, with_forced_trimmed_paths};
6668
use rustc_middle::ty::{
67-
self, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
69+
self, List, ParamEnv, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
6870
TypeVisitableExt,
6971
};
7072
use rustc_span::{BytePos, DesugaringKind, Pos, Span, sym};
@@ -76,8 +78,10 @@ use crate::infer;
7678
use crate::infer::relate::{self, RelateResult, TypeRelation};
7779
use crate::infer::{InferCtxt, TypeTrace, ValuePairs};
7880
use crate::solve::deeply_normalize_for_diagnostics;
81+
use crate::traits::query::evaluate_obligation::InferCtxtExt;
7982
use crate::traits::{
8083
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
84+
ObligationCtxt,
8185
};
8286

8387
mod note_and_explain;
@@ -339,9 +343,15 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
339343
cause: &ObligationCause<'tcx>,
340344
exp_found: Option<ty::error::ExpectedFound<Ty<'tcx>>>,
341345
terr: TypeError<'tcx>,
346+
param_env: Option<ParamEnv<'tcx>>,
342347
) {
343348
match *cause.code() {
344-
ObligationCauseCode::Pattern { origin_expr: true, span: Some(span), root_ty } => {
349+
ObligationCauseCode::Pattern {
350+
origin_expr: true,
351+
span: Some(span),
352+
root_ty,
353+
prefix_suggestion_parentheses,
354+
} => {
345355
let ty = self.resolve_vars_if_possible(root_ty);
346356
if !matches!(ty.kind(), ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_)))
347357
{
@@ -359,15 +369,35 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
359369
}
360370
}
361371
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)
372+
&& let Ok(mut snippet) = self.tcx.sess.source_map().span_to_snippet(span)
364373
{
365-
err.span_suggestion(
366-
span,
367-
"consider dereferencing the boxed value",
368-
format!("*{snippet}"),
369-
Applicability::MachineApplicable,
370-
);
374+
// Parentheses are needed for cases like as casts.
375+
snippet = if prefix_suggestion_parentheses {
376+
format!("({snippet})")
377+
} else {
378+
snippet
379+
};
380+
381+
// Try giving a box suggestion first, as it is a special case of the
382+
// deref suggestion.
383+
if ty.boxed_ty() == Some(found) {
384+
err.span_suggestion(
385+
span,
386+
"consider dereferencing the boxed value",
387+
format!("*{snippet}"),
388+
Applicability::MachineApplicable,
389+
);
390+
} else if let Some(param_env) = param_env
391+
&& let Some(prefix) =
392+
self.should_deref_suggestion_on_mismatch(cause, param_env, found, ty)
393+
{
394+
err.span_suggestion(
395+
span,
396+
"consider dereferencing to access the inner value",
397+
format!("{prefix}{snippet}"),
398+
Applicability::MaybeIncorrect,
399+
);
400+
}
371401
}
372402
}
373403
ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => {
@@ -524,6 +554,86 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
524554
}
525555
}
526556

557+
/// Determines whether deref_to == <deref_from as Deref>::Target, and if so,
558+
/// returns a prefix that should be added to deref_from as a suggestion.
559+
fn should_deref_suggestion_on_mismatch(
560+
&self,
561+
cause: &ObligationCause<'tcx>,
562+
param_env: ParamEnv<'tcx>,
563+
deref_to: Ty<'tcx>,
564+
deref_from: Ty<'tcx>,
565+
) -> Option<String> {
566+
let tcx = self.infcx.tcx;
567+
568+
let deref_trait = tcx.lang_items().deref_trait()?;
569+
let deref_mut_trait = tcx.lang_items().deref_mut_trait()?;
570+
let deref_target = tcx.lang_items().deref_target()?;
571+
572+
// Count the number of references in deref_from, since we need
573+
// that plus 1 to invoke Deref.
574+
// This is also needed to ensure we aren't checking for Deref on a reference.
575+
let mut deref_from_cur = deref_from;
576+
// Extra required dereference to invoke Deref.
577+
let mut num_refs = 1;
578+
while let ty::Ref(_, inner_ty, _) = deref_from_cur.kind() {
579+
deref_from_cur = *inner_ty;
580+
num_refs += 1;
581+
}
582+
583+
// <deref_from_cur as Deref>
584+
let trait_ref = ty::TraitRef::new(tcx, deref_trait, [deref_from_cur]);
585+
let obligation =
586+
Obligation::new(tcx, cause.clone(), param_env, ty::Binder::dummy(trait_ref));
587+
if !self.infcx.predicate_may_hold(&obligation) {
588+
return None;
589+
}
590+
591+
let ocx = ObligationCtxt::new(self.infcx);
592+
593+
// <deref_from_cur as Deref>::Target
594+
let target = Ty::new_projection(tcx, deref_target, [deref_from_cur]);
595+
596+
let target_normalized = ocx.structurally_normalize(cause, param_env, target).ok()?;
597+
let expected_normalized = ocx.structurally_normalize(cause, param_env, deref_to).ok()?;
598+
599+
// Determine whether target_normalized == expected_normalized with inference.
600+
let types_eq = self.infcx.probe(|_| {
601+
// FIXME: I don't know what DefineOpaqueTypes should be used here.
602+
self.infcx
603+
.at(cause, param_env)
604+
.eq(DefineOpaqueTypes::No, expected_normalized, target_normalized)
605+
.is_ok()
606+
});
607+
608+
if !types_eq {
609+
return None;
610+
}
611+
612+
let deref_part = "*".repeat(num_refs);
613+
614+
// FIXME: Edge case: how should this handle double references?
615+
// Currently, they're dereferenced correctly, but only the
616+
// outer reference is added back after the dereferences.
617+
618+
// Check if we have DerefMut, as it's required to create a mut reference.
619+
let trait_ref_deref_mut = ty::TraitRef::new(tcx, deref_mut_trait, [deref_from_cur]);
620+
let obligation_deref_mut =
621+
Obligation::new(tcx, cause.clone(), param_env, ty::Binder::dummy(trait_ref_deref_mut));
622+
let has_deref_mut = self.infcx.predicate_may_hold(&obligation_deref_mut);
623+
624+
// Try to give a suggestion with the same type of reference as the original code.
625+
// For example, if deref_from was a reference, then make the suggestion a reference as well.
626+
let valid_mutability = deref_from
627+
.ref_mutability()
628+
.map(|v| if has_deref_mut { v } else { hir::Mutability::Not });
629+
630+
match valid_mutability {
631+
Some(hir::Mutability::Mut) => Some(format!("&mut {deref_part}")),
632+
Some(hir::Mutability::Not) => Some(format!("&{deref_part}")),
633+
None => Some(deref_part),
634+
}
635+
}
636+
527637
/// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value`
528638
/// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and
529639
/// populate `other_value` with `other_ty`.
@@ -1209,6 +1319,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
12091319
mut values: Option<ty::ParamEnvAnd<'tcx, ValuePairs<'tcx>>>,
12101320
terr: TypeError<'tcx>,
12111321
prefer_label: bool,
1322+
param_env: Option<ty::ParamEnv<'tcx>>,
12121323
) {
12131324
let span = cause.span;
12141325

@@ -1693,7 +1804,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
16931804

16941805
// It reads better to have the error origin as the final
16951806
// thing.
1696-
self.note_error_origin(diag, cause, exp_found, terr);
1807+
self.note_error_origin(diag, cause, exp_found, terr, param_env);
16971808

16981809
debug!(?diag);
16991810
}
@@ -1868,6 +1979,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
18681979
Some(param_env.and(trace.values)),
18691980
terr,
18701981
false,
1982+
Some(param_env),
18711983
);
18721984
diag
18731985
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
727727
None,
728728
TypeError::Sorts(ty::error::ExpectedFound::new(true, expected_ty, ct_ty)),
729729
false,
730+
None,
730731
);
731732
diag
732733
}
@@ -1456,6 +1457,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
14561457
}),
14571458
err,
14581459
false,
1460+
None,
14591461
);
14601462
self.note_obligation_cause(&mut diag, obligation);
14611463
diag.emit()

tests/ui/let-else/let-else-deref-coercion.stderr

+8-4
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ error[E0308]: mismatched types
22
--> $DIR/let-else-deref-coercion.rs:37:13
33
|
44
LL | let Bar::Present(z) = self else {
5-
| ^^^^^^^^^^^^^^^ ---- this expression has type `&mut Foo`
6-
| |
5+
| ^^^^^^^^^^^^^^^ ----
6+
| | |
7+
| | this expression has type `&mut Foo`
8+
| | help: consider dereferencing to access the inner value: `&mut **self`
79
| expected `Foo`, found `Bar`
810

911
error[E0308]: mismatched types
1012
--> $DIR/let-else-deref-coercion.rs:68:13
1113
|
1214
LL | let Bar(z) = x;
13-
| ^^^^^^ - this expression has type `&mut irrefutable::Foo`
14-
| |
15+
| ^^^^^^ -
16+
| | |
17+
| | this expression has type `&mut irrefutable::Foo`
18+
| | help: consider dereferencing to access the inner value: `&mut **x`
1519
| expected `Foo`, found `Bar`
1620

1721
error: aborting due to 2 previous errors
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use std::sync::Arc;
2+
fn main() {
3+
let mut x = Arc::new(Some(1));
4+
match x {
5+
//~^ HELP consider dereferencing to access the inner value
6+
//~| HELP consider dereferencing to access the inner value
7+
Some(_) => {}
8+
//~^ ERROR mismatched types
9+
None => {}
10+
//~^ ERROR mismatched types
11+
}
12+
13+
let mut y = Box::new(Some(1));
14+
match y {
15+
//~^ HELP consider dereferencing to access the inner value
16+
//~| HELP consider dereferencing to access the inner value
17+
Some(_) => {}
18+
//~^ ERROR mismatched types
19+
None => {}
20+
//~^ ERROR mismatched types
21+
}
22+
23+
let mut z = Arc::new(Some(1));
24+
match z as Arc<Option<i32>> {
25+
//~^ HELP consider dereferencing to access the inner value
26+
//~| HELP consider dereferencing to access the inner value
27+
Some(_) => {}
28+
//~^ ERROR mismatched types
29+
None => {}
30+
//~^ ERROR mismatched types
31+
}
32+
33+
let z_const: &Arc<Option<i32>> = &z;
34+
match z_const {
35+
//~^ HELP consider dereferencing to access the inner value
36+
//~| HELP consider dereferencing to access the inner value
37+
Some(_) => {}
38+
//~^ ERROR mismatched types
39+
None => {}
40+
//~^ ERROR mismatched types
41+
}
42+
43+
// Normal reference because Arc doesn't implement DerefMut.
44+
let z_mut: &mut Arc<Option<i32>> = &mut z;
45+
match z_mut {
46+
//~^ HELP consider dereferencing to access the inner value
47+
//~| HELP consider dereferencing to access the inner value
48+
Some(_) => {}
49+
//~^ ERROR mismatched types
50+
None => {}
51+
//~^ ERROR mismatched types
52+
}
53+
54+
// Mutable reference because Box does implement DerefMut.
55+
let y_mut: &mut Box<Option<i32>> = &mut y;
56+
match y_mut {
57+
//~^ HELP consider dereferencing to access the inner value
58+
//~| HELP consider dereferencing to access the inner value
59+
Some(_) => {}
60+
//~^ ERROR mismatched types
61+
None => {}
62+
//~^ ERROR mismatched types
63+
}
64+
}

0 commit comments

Comments
 (0)