Skip to content

Commit db77788

Browse files
authored
Rollup merge of #132939 - uellenberg:suggest-deref, r=oli-obk
Suggest using deref in patterns Fixes #132784 This changes the following code: ```rs use std::sync::Arc; fn main() { let mut x = Arc::new(Some(1)); match x { Some(_) => {} None => {} } } ``` to output ```rs error[E0308]: mismatched types --> src/main.rs:5:9 | LL | match x { | - this expression has type `Arc<Option<{integer}>>` ... LL | Some(_) => {} | ^^^^^^^ expected `Arc<Option<{integer}>>`, found `Option<_>` | = note: expected struct `Arc<Option<{integer}>>` found enum `Option<_>` help: consider dereferencing to access the inner value using the Deref trait | LL | match *x { | ~~ ``` instead of ```rs error[E0308]: mismatched types --> src/main.rs:5:9 | 4 | match x { | - this expression has type `Arc<Option<{integer}>>` 5 | Some(_) => {} | ^^^^^^^ expected `Arc<Option<{integer}>>`, found `Option<_>` | = note: expected struct `Arc<Option<{integer}>>` found enum `Option<_>` ``` This makes it more obvious that a Deref is available, and gives a suggestion on how to use it in order to fix the issue at hand.
2 parents 0aeaa5e + 831f454 commit db77788

File tree

12 files changed

+544
-64
lines changed

12 files changed

+544
-64
lines changed

compiler/rustc_hir/src/hir.rs

+16
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,22 @@ pub fn is_range_literal(expr: &Expr<'_>) -> bool {
20252025
}
20262026
}
20272027

2028+
/// Checks if the specified expression needs parentheses for prefix
2029+
/// or postfix suggestions to be valid.
2030+
/// For example, `a + b` requires parentheses to suggest `&(a + b)`,
2031+
/// but just `a` does not.
2032+
/// Similarly, `(a + b).c()` also requires parentheses.
2033+
/// This should not be used for other types of suggestions.
2034+
pub fn expr_needs_parens(expr: &Expr<'_>) -> bool {
2035+
match expr.kind {
2036+
// parenthesize if needed (Issue #46756)
2037+
ExprKind::Cast(_, _) | ExprKind::Binary(_, _, _) => true,
2038+
// parenthesize borrows of range literals (Issue #54505)
2039+
_ if is_range_literal(expr) => true,
2040+
_ => false,
2041+
}
2042+
}
2043+
20282044
#[derive(Debug, Clone, Copy, HashStable_Generic)]
20292045
pub enum ExprKind<'hir> {
20302046
/// Allow anonymous constants from an inline `const` block

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

+3-14
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
1010
use rustc_hir::lang_items::LangItem;
1111
use rustc_hir::{
1212
Arm, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, GenericBound, HirId,
13-
Node, Path, QPath, Stmt, StmtKind, TyKind, WherePredicateKind,
13+
Node, Path, QPath, Stmt, StmtKind, TyKind, WherePredicateKind, expr_needs_parens,
1414
};
1515
use rustc_hir_analysis::collect::suggest_impl_trait;
1616
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
@@ -35,7 +35,6 @@ use tracing::{debug, instrument};
3535

3636
use super::FnCtxt;
3737
use crate::fn_ctxt::rustc_span::BytePos;
38-
use crate::hir::is_range_literal;
3938
use crate::method::probe;
4039
use crate::method::probe::{IsSuggestion, Mode, ProbeScope};
4140
use crate::{errors, fluent_generated as fluent};
@@ -2648,7 +2647,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26482647
}
26492648

26502649
let make_sugg = |expr: &Expr<'_>, span: Span, sugg: &str| {
2651-
if self.needs_parentheses(expr) {
2650+
if expr_needs_parens(expr) {
26522651
(
26532652
vec![
26542653
(span.shrink_to_lo(), format!("{prefix}{sugg}(")),
@@ -2861,7 +2860,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
28612860
return None;
28622861
}
28632862

2864-
if self.needs_parentheses(expr) {
2863+
if expr_needs_parens(expr) {
28652864
return Some((
28662865
vec![
28672866
(span, format!("{suggestion}(")),
@@ -2902,16 +2901,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
29022901
false
29032902
}
29042903

2905-
fn needs_parentheses(&self, expr: &hir::Expr<'_>) -> bool {
2906-
match expr.kind {
2907-
// parenthesize if needed (Issue #46756)
2908-
hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true,
2909-
// parenthesize borrows of range literals (Issue #54505)
2910-
_ if is_range_literal(expr) => true,
2911-
_ => false,
2912-
}
2913-
}
2914-
29152904
pub(crate) fn suggest_cast(
29162905
&self,
29172906
err: &mut Diag<'_>,

compiler/rustc_hir_typeck/src/pat.rs

+28-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+
expr_needs_parens,
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,32 @@ 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+
// If origin_expr exists, then expected represents the type of origin_expr.
102+
// If span also exists, then span == origin_expr.span (although it doesn't need to exist).
103+
// In that case, we can peel away references from both and treat them
104+
// as the same.
105+
let origin_expr_info = ti.origin_expr.map(|mut cur_expr| {
106+
let mut count = 0;
107+
108+
// cur_ty may have more layers of references than cur_expr.
109+
// We can only make suggestions about cur_expr, however, so we'll
110+
// use that as our condition for stopping.
111+
while let ExprKind::AddrOf(.., inner) = &cur_expr.kind {
112+
cur_expr = inner;
113+
count += 1;
114+
}
115+
116+
PatternOriginExpr {
117+
peeled_span: cur_expr.span,
118+
peeled_count: count,
119+
peeled_prefix_suggestion_parentheses: expr_needs_parens(cur_expr),
120+
}
121+
});
122+
97123
let code = ObligationCauseCode::Pattern {
98124
span: ti.span,
99125
root_ty: ti.expected,
100-
origin_expr: ti.origin_expr.is_some(),
126+
origin_expr: origin_expr_info,
101127
};
102128
self.cause(cause_span, code)
103129
}

compiler/rustc_middle/src/traits/mod.rs

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

323323
/// Computing common supertype in an if expression
@@ -530,6 +530,25 @@ pub struct MatchExpressionArmCause<'tcx> {
530530
pub tail_defines_return_position_impl_trait: Option<LocalDefId>,
531531
}
532532

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

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

+94-21
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,11 @@ use rustc_hir::{self as hir};
6363
use rustc_macros::extension;
6464
use rustc_middle::bug;
6565
use rustc_middle::dep_graph::DepContext;
66+
use rustc_middle::traits::PatternOriginExpr;
6667
use rustc_middle::ty::error::{ExpectedFound, TypeError, TypeErrorToStringExt};
6768
use rustc_middle::ty::print::{PrintError, PrintTraitRefExt as _, with_forced_trimmed_paths};
6869
use rustc_middle::ty::{
69-
self, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
70+
self, List, ParamEnv, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
7071
TypeVisitableExt,
7172
};
7273
use rustc_span::def_id::LOCAL_CRATE;
@@ -77,7 +78,7 @@ use crate::error_reporting::TypeErrCtxt;
7778
use crate::errors::{ObligationCauseFailureCode, TypeErrorAdditionalDiags};
7879
use crate::infer;
7980
use crate::infer::relate::{self, RelateResult, TypeRelation};
80-
use crate::infer::{InferCtxt, TypeTrace, ValuePairs};
81+
use crate::infer::{InferCtxt, InferCtxtExt as _, TypeTrace, ValuePairs};
8182
use crate::solve::deeply_normalize_for_diagnostics;
8283
use crate::traits::{
8384
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
@@ -433,38 +434,71 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
433434
cause: &ObligationCause<'tcx>,
434435
exp_found: Option<ty::error::ExpectedFound<Ty<'tcx>>>,
435436
terr: TypeError<'tcx>,
437+
param_env: Option<ParamEnv<'tcx>>,
436438
) {
437439
match *cause.code() {
438-
ObligationCauseCode::Pattern { origin_expr: true, span: Some(span), root_ty } => {
439-
let ty = self.resolve_vars_if_possible(root_ty);
440-
if !matches!(ty.kind(), ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_)))
441-
{
440+
ObligationCauseCode::Pattern {
441+
origin_expr: Some(origin_expr),
442+
span: Some(span),
443+
root_ty,
444+
} => {
445+
let expected_ty = self.resolve_vars_if_possible(root_ty);
446+
if !matches!(
447+
expected_ty.kind(),
448+
ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_))
449+
) {
442450
// don't show type `_`
443451
if span.desugaring_kind() == Some(DesugaringKind::ForLoop)
444-
&& let ty::Adt(def, args) = ty.kind()
452+
&& let ty::Adt(def, args) = expected_ty.kind()
445453
&& Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option)
446454
{
447455
err.span_label(
448456
span,
449457
format!("this is an iterator with items of type `{}`", args.type_at(0)),
450458
);
451459
} else {
452-
err.span_label(span, format!("this expression has type `{ty}`"));
460+
err.span_label(span, format!("this expression has type `{expected_ty}`"));
453461
}
454462
}
455463
if let Some(ty::error::ExpectedFound { found, .. }) = exp_found
456-
&& ty.boxed_ty() == Some(found)
457-
&& let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
464+
&& let Ok(mut peeled_snippet) =
465+
self.tcx.sess.source_map().span_to_snippet(origin_expr.peeled_span)
458466
{
459-
err.span_suggestion(
460-
span,
461-
"consider dereferencing the boxed value",
462-
format!("*{snippet}"),
463-
Applicability::MachineApplicable,
464-
);
467+
// Parentheses are needed for cases like as casts.
468+
// We use the peeled_span for deref suggestions.
469+
// It's also safe to use for box, since box only triggers if there
470+
// wasn't a reference to begin with.
471+
if origin_expr.peeled_prefix_suggestion_parentheses {
472+
peeled_snippet = format!("({peeled_snippet})");
473+
}
474+
475+
// Try giving a box suggestion first, as it is a special case of the
476+
// deref suggestion.
477+
if expected_ty.boxed_ty() == Some(found) {
478+
err.span_suggestion_verbose(
479+
span,
480+
"consider dereferencing the boxed value",
481+
format!("*{peeled_snippet}"),
482+
Applicability::MachineApplicable,
483+
);
484+
} else if let Some(param_env) = param_env
485+
&& let Some(prefix) = self.should_deref_suggestion_on_mismatch(
486+
param_env,
487+
found,
488+
expected_ty,
489+
origin_expr,
490+
)
491+
{
492+
err.span_suggestion_verbose(
493+
span,
494+
"consider dereferencing to access the inner value using the Deref trait",
495+
format!("{prefix}{peeled_snippet}"),
496+
Applicability::MaybeIncorrect,
497+
);
498+
}
465499
}
466500
}
467-
ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => {
501+
ObligationCauseCode::Pattern { origin_expr: None, span: Some(span), .. } => {
468502
err.span_label(span, "expected due to this");
469503
}
470504
ObligationCauseCode::BlockTailExpression(
@@ -618,6 +652,45 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
618652
}
619653
}
620654

655+
/// Determines whether deref_to == <deref_from as Deref>::Target, and if so,
656+
/// returns a prefix that should be added to deref_from as a suggestion.
657+
fn should_deref_suggestion_on_mismatch(
658+
&self,
659+
param_env: ParamEnv<'tcx>,
660+
deref_to: Ty<'tcx>,
661+
deref_from: Ty<'tcx>,
662+
origin_expr: PatternOriginExpr,
663+
) -> Option<String> {
664+
// origin_expr contains stripped away versions of our expression.
665+
// We'll want to use that to avoid suggesting things like *&x.
666+
// However, the type that we have access to hasn't been stripped away,
667+
// so we need to ignore the first n dereferences, where n is the number
668+
// that's been stripped away in origin_expr.
669+
670+
// Find a way to autoderef from deref_from to deref_to.
671+
let Some((num_derefs, (after_deref_ty, _))) = (self.autoderef_steps)(deref_from)
672+
.into_iter()
673+
.enumerate()
674+
.find(|(_, (ty, _))| self.infcx.can_eq(param_env, *ty, deref_to))
675+
else {
676+
return None;
677+
};
678+
679+
if num_derefs <= origin_expr.peeled_count {
680+
return None;
681+
}
682+
683+
let deref_part = "*".repeat(num_derefs - origin_expr.peeled_count);
684+
685+
// If the user used a reference in the original expression, they probably
686+
// want the suggestion to still give a reference.
687+
if deref_from.is_ref() && !after_deref_ty.is_ref() {
688+
Some(format!("&{deref_part}"))
689+
} else {
690+
Some(deref_part)
691+
}
692+
}
693+
621694
/// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value`
622695
/// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and
623696
/// populate `other_value` with `other_ty`.
@@ -1406,8 +1479,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
14061479
Variable(ty::error::ExpectedFound<Ty<'a>>),
14071480
Fixed(&'static str),
14081481
}
1409-
let (expected_found, exp_found, is_simple_error, values) = match values {
1410-
None => (None, Mismatch::Fixed("type"), false, None),
1482+
let (expected_found, exp_found, is_simple_error, values, param_env) = match values {
1483+
None => (None, Mismatch::Fixed("type"), false, None, None),
14111484
Some(ty::ParamEnvAnd { param_env, value: values }) => {
14121485
let mut values = self.resolve_vars_if_possible(values);
14131486
if self.next_trait_solver() {
@@ -1459,7 +1532,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
14591532
diag.downgrade_to_delayed_bug();
14601533
return;
14611534
};
1462-
(Some(vals), exp_found, is_simple_error, Some(values))
1535+
(Some(vals), exp_found, is_simple_error, Some(values), Some(param_env))
14631536
}
14641537
};
14651538

@@ -1791,7 +1864,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
17911864

17921865
// It reads better to have the error origin as the final
17931866
// thing.
1794-
self.note_error_origin(diag, cause, exp_found, terr);
1867+
self.note_error_origin(diag, cause, exp_found, terr, param_env);
17951868

17961869
debug!(?diag);
17971870
}

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
}

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

+3-8
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ use rustc_hir::def_id::DefId;
2020
use rustc_hir::intravisit::Visitor;
2121
use rustc_hir::lang_items::LangItem;
2222
use rustc_hir::{
23-
CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, HirId, Node, is_range_literal,
23+
CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, HirId, Node, expr_needs_parens,
24+
is_range_literal,
2425
};
2526
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferCtxt, InferOk};
2627
use rustc_middle::hir::map;
@@ -1391,13 +1392,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
13911392
let Some(expr) = expr_finder.result else {
13921393
return false;
13931394
};
1394-
let needs_parens = match expr.kind {
1395-
// parenthesize if needed (Issue #46756)
1396-
hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true,
1397-
// parenthesize borrows of range literals (Issue #54505)
1398-
_ if is_range_literal(expr) => true,
1399-
_ => false,
1400-
};
1395+
let needs_parens = expr_needs_parens(expr);
14011396

14021397
let span = if needs_parens { span } else { span.shrink_to_lo() };
14031398
let suggestions = if !needs_parens {

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

0 commit comments

Comments
 (0)