about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast/src/ast.rs16
-rw-r--r--compiler/rustc_hir/src/hir.rs14
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs22
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs5
-rw-r--r--src/test/ui/expr/if/bad-if-let-suggestion.rs24
-rw-r--r--src/test/ui/expr/if/bad-if-let-suggestion.stderr69
6 files changed, 142 insertions, 8 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index f2643d61fc8..e5b61d7000a 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -1278,6 +1278,22 @@ impl Expr {
             },
         )
     }
+
+    // To a first-order approximation, is this a pattern
+    pub fn is_approximately_pattern(&self) -> bool {
+        match &self.peel_parens().kind {
+            ExprKind::Box(_)
+            | ExprKind::Array(_)
+            | ExprKind::Call(_, _)
+            | ExprKind::Tup(_)
+            | ExprKind::Lit(_)
+            | ExprKind::Range(_, _, _)
+            | ExprKind::Underscore
+            | ExprKind::Path(_, _)
+            | ExprKind::Struct(_) => true,
+            _ => false,
+        }
+    }
 }
 
 /// Limit types of a range (inclusive or exclusive)
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 9c314f67651..2f5f271dc50 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -1813,6 +1813,20 @@ impl Expr<'_> {
             | ExprKind::Err => true,
         }
     }
+
+    // To a first-order approximation, is this a pattern
+    pub fn is_approximately_pattern(&self) -> bool {
+        match &self.kind {
+            ExprKind::Box(_)
+            | ExprKind::Array(_)
+            | ExprKind::Call(..)
+            | ExprKind::Tup(_)
+            | ExprKind::Lit(_)
+            | ExprKind::Path(_)
+            | ExprKind::Struct(..) => true,
+            _ => false,
+        }
+    }
 }
 
 /// Checks if the specified expression is a built-in range literal.
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index cb39eb5416b..5e52e9b40f0 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -265,13 +265,21 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
             );
         }
         match (source, self.diagnostic_metadata.in_if_condition) {
-            (PathSource::Expr(_), Some(Expr { span, kind: ExprKind::Assign(..), .. })) => {
-                err.span_suggestion_verbose(
-                    span.shrink_to_lo(),
-                    "you might have meant to use pattern matching",
-                    "let ".to_string(),
-                    Applicability::MaybeIncorrect,
-                );
+            (
+                PathSource::Expr(_),
+                Some(Expr { span: expr_span, kind: ExprKind::Assign(lhs, _, _), .. }),
+            ) => {
+                // Icky heuristic so we don't suggest:
+                // `if (i + 2) = 2` => `if let (i + 2) = 2` (approximately pattern)
+                // `if 2 = i` => `if let 2 = i` (lhs needs to contain error span)
+                if lhs.is_approximately_pattern() && lhs.span.contains(span) {
+                    err.span_suggestion_verbose(
+                        expr_span.shrink_to_lo(),
+                        "you might have meant to use pattern matching",
+                        "let ".to_string(),
+                        Applicability::MaybeIncorrect,
+                    );
+                }
             }
             _ => {}
         }
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index e3e0063c4ec..9f82bb67bd0 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -1035,7 +1035,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             } else {
                 (Applicability::MaybeIncorrect, false)
             };
-            if !lhs.is_syntactic_place_expr() && !matches!(lhs.kind, hir::ExprKind::Lit(_)) {
+            if !lhs.is_syntactic_place_expr()
+                && lhs.is_approximately_pattern()
+                && !matches!(lhs.kind, hir::ExprKind::Lit(_))
+            {
                 // Do not suggest `if let x = y` as `==` is way more likely to be the intention.
                 let hir = self.tcx.hir();
                 if let hir::Node::Expr(hir::Expr { kind: ExprKind::If { .. }, .. }) =
diff --git a/src/test/ui/expr/if/bad-if-let-suggestion.rs b/src/test/ui/expr/if/bad-if-let-suggestion.rs
new file mode 100644
index 00000000000..a8b2a283039
--- /dev/null
+++ b/src/test/ui/expr/if/bad-if-let-suggestion.rs
@@ -0,0 +1,24 @@
+// FIXME(compiler-errors): This really should suggest `let` on the RHS of the
+// `&&` operator, but that's kinda hard to do because of precedence.
+// Instead, for now we just make sure not to suggest `if let let`.
+fn a() {
+    if let x = 1 && i = 2 {}
+    //~^ ERROR cannot find value `i` in this scope
+    //~| ERROR `let` expressions in this position are unstable
+    //~| ERROR mismatched types
+    //~| ERROR `let` expressions are not supported here
+}
+
+fn b() {
+    if (i + j) = i {}
+    //~^ ERROR cannot find value `i` in this scope
+    //~| ERROR cannot find value `i` in this scope
+    //~| ERROR cannot find value `j` in this scope
+}
+
+fn c() {
+    if x[0] = 1 {}
+    //~^ ERROR cannot find value `x` in this scope
+}
+
+fn main() {}
diff --git a/src/test/ui/expr/if/bad-if-let-suggestion.stderr b/src/test/ui/expr/if/bad-if-let-suggestion.stderr
new file mode 100644
index 00000000000..60d286fedf5
--- /dev/null
+++ b/src/test/ui/expr/if/bad-if-let-suggestion.stderr
@@ -0,0 +1,69 @@
+error: `let` expressions are not supported here
+  --> $DIR/bad-if-let-suggestion.rs:5:8
+   |
+LL |     if let x = 1 && i = 2 {}
+   |        ^^^^^^^^^
+   |
+   = note: only supported directly in conditions of `if` and `while` expressions
+
+error[E0425]: cannot find value `i` in this scope
+  --> $DIR/bad-if-let-suggestion.rs:5:21
+   |
+LL |     if let x = 1 && i = 2 {}
+   |                     ^ not found in this scope
+
+error[E0425]: cannot find value `i` in this scope
+  --> $DIR/bad-if-let-suggestion.rs:13:9
+   |
+LL | fn a() {
+   | ------ similarly named function `a` defined here
+...
+LL |     if (i + j) = i {}
+   |         ^ help: a function with a similar name exists: `a`
+
+error[E0425]: cannot find value `j` in this scope
+  --> $DIR/bad-if-let-suggestion.rs:13:13
+   |
+LL | fn a() {
+   | ------ similarly named function `a` defined here
+...
+LL |     if (i + j) = i {}
+   |             ^ help: a function with a similar name exists: `a`
+
+error[E0425]: cannot find value `i` in this scope
+  --> $DIR/bad-if-let-suggestion.rs:13:18
+   |
+LL | fn a() {
+   | ------ similarly named function `a` defined here
+...
+LL |     if (i + j) = i {}
+   |                  ^ help: a function with a similar name exists: `a`
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/bad-if-let-suggestion.rs:20:8
+   |
+LL | fn a() {
+   | ------ similarly named function `a` defined here
+...
+LL |     if x[0] = 1 {}
+   |        ^ help: a function with a similar name exists: `a`
+
+error[E0658]: `let` expressions in this position are unstable
+  --> $DIR/bad-if-let-suggestion.rs:5:8
+   |
+LL |     if let x = 1 && i = 2 {}
+   |        ^^^^^^^^^
+   |
+   = note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
+   = help: add `#![feature(let_chains)]` to the crate attributes to enable
+
+error[E0308]: mismatched types
+  --> $DIR/bad-if-let-suggestion.rs:5:8
+   |
+LL |     if let x = 1 && i = 2 {}
+   |        ^^^^^^^^^^^^^^^^^^ expected `bool`, found `()`
+
+error: aborting due to 8 previous errors
+
+Some errors have detailed explanations: E0308, E0425, E0658.
+For more information about an error, try `rustc --explain E0308`.