about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-04-16 03:06:46 +0000
committerbors <bors@rust-lang.org>2023-04-16 03:06:46 +0000
commit2a711152615ad9294dc0e5ee6885c8e9bb8418a9 (patch)
tree65af8ebd96f84ea2af123065eb3d0faec38db7f4
parentc6fb7b9815aea87fb5ced1c683212871699c907c (diff)
parent90dc6fe71d6a245b175da6293a41c3156e955526 (diff)
downloadrust-2a711152615ad9294dc0e5ee6885c8e9bb8418a9.tar.gz
rust-2a711152615ad9294dc0e5ee6885c8e9bb8418a9.zip
Auto merge of #105888 - skyzh:skyzh/suggest-lifetime-closure, r=compiler-errors
suggest lifetime for closure parameter type when mismatch

This is a draft PR, will add test cases later and be ready for review.

This PR fixes https://github.com/rust-lang/rust/issues/105675 by adding a diagnostics suggestion. Also a partial fix to https://github.com/rust-lang/rust/issues/105528.

The following code will have a compile error now:

```
fn const_if_unit(input: bool) -> impl for<'a> FnOnce(&'a ()) -> usize {
    let x = |_| 1;
    x
}
```

Before this PR:

```
error[E0308]: mismatched types
 --> src/lib.rs:3:5
  |
3 |     x
  |     ^ one type is more general than the other
  |
  = note: expected trait `for<'a> FnOnce<(&'a (),)>`
             found trait `FnOnce<(&(),)>`
note: this closure does not fulfill the lifetime requirements
 --> src/lib.rs:2:13
  |
2 |     let x = |_| 1;
  |             ^^^

error: implementation of `FnOnce` is not general enough
 --> src/lib.rs:3:5
  |
3 |     x
  |     ^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 ()) -> usize` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `rust-test` due to 2 previous errors
```

After this PR:

```
error[E0308]: mismatched types
 --> src/lib.rs:3:5
  |
3 |     x
  |     ^ one type is more general than the other
  |
  = note: expected trait `for<'a> FnOnce<(&'a (),)>`
             found trait `FnOnce<(&(),)>`
note: this closure does not fulfill the lifetime requirements
 --> src/lib.rs:2:13
  |
2 |     let x = |_| 1;
  |             ^^^
help: consider changing the type of the closure parameters
  |
2 |     let x = |_: &_| 1;
  |             ~~~~~~~

error: implementation of `FnOnce` is not general enough
 --> src/lib.rs:3:5
  |
3 |     x
  |     ^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 ()) -> usize` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `rust-test` due to 2 previous errors
```

After applying the suggestion, it compiles. The suggestion might not always be correct as the generation procedure of that suggestion is quite simple...
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs1
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/suggest.rs80
-rw-r--r--tests/ui/lifetimes/issue-105675.rs14
-rw-r--r--tests/ui/lifetimes/issue-105675.stderr109
-rw-r--r--tests/ui/lifetimes/issue-79187-2.stderr4
-rw-r--r--tests/ui/lifetimes/issue-79187.stderr4
-rw-r--r--tests/ui/mismatched_types/closure-mismatch.rs3
-rw-r--r--tests/ui/mismatched_types/closure-mismatch.stderr38
8 files changed, 250 insertions, 3 deletions
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 9e5f6d107d1..992b07db154 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -1927,6 +1927,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         {
             let span = self.tcx.def_span(def_id);
             diag.span_note(span, "this closure does not fulfill the lifetime requirements");
+            self.suggest_for_all_lifetime_closure(span, self.tcx.hir().get_by_def_id(def_id), &exp_found, diag);
         }
 
         // It reads better to have the error origin as the final
diff --git a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
index b5aeca12a1f..4dc5fc451dd 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs
@@ -1,14 +1,14 @@
 use hir::def::CtorKind;
 use hir::intravisit::{walk_expr, walk_stmt, Visitor};
 use rustc_data_structures::fx::FxIndexSet;
-use rustc_errors::Diagnostic;
+use rustc_errors::{Applicability, Diagnostic};
 use rustc_hir as hir;
 use rustc_middle::traits::{
     IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
     StatementAsExpression,
 };
 use rustc_middle::ty::print::with_no_trimmed_paths;
-use rustc_middle::ty::{self as ty, IsSuggestable, Ty, TypeVisitableExt};
+use rustc_middle::ty::{self as ty, GenericArgKind, IsSuggestable, Ty, TypeVisitableExt};
 use rustc_span::{sym, BytePos, Span};
 use rustc_target::abi::FieldIdx;
 
@@ -536,6 +536,82 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         }
         None
     }
+
+    /// For "one type is more general than the other" errors on closures, suggest changing the lifetime
+    /// of the parameters to accept all lifetimes.
+    pub(super) fn suggest_for_all_lifetime_closure(
+        &self,
+        span: Span,
+        hir: hir::Node<'_>,
+        exp_found: &ty::error::ExpectedFound<ty::PolyTraitRef<'tcx>>,
+        diag: &mut Diagnostic,
+    ) {
+        // 0. Extract fn_decl from hir
+        let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(hir::Closure { body, fn_decl, .. }), .. }) = hir else { return; };
+        let hir::Body { params, .. } = self.tcx.hir().body(*body);
+
+        // 1. Get the substs of the closure.
+        // 2. Assume exp_found is FnOnce / FnMut / Fn, we can extract function parameters from [1].
+        let Some(expected) = exp_found.expected.skip_binder().substs.get(1) else { return; };
+        let Some(found) = exp_found.found.skip_binder().substs.get(1) else { return; };
+        let expected = expected.unpack();
+        let found = found.unpack();
+        // 3. Extract the tuple type from Fn trait and suggest the change.
+        if let GenericArgKind::Type(expected) = expected &&
+            let GenericArgKind::Type(found) = found &&
+            let ty::Tuple(expected) = expected.kind() &&
+            let ty::Tuple(found)= found.kind() &&
+            expected.len() == found.len() {
+            let mut suggestion = "|".to_string();
+            let mut is_first = true;
+            let mut has_suggestion = false;
+
+            for (((expected, found), param_hir), arg_hir) in expected.iter()
+                .zip(found.iter())
+                .zip(params.iter())
+                .zip(fn_decl.inputs.iter()) {
+                if is_first {
+                    is_first = false;
+                } else {
+                    suggestion += ", ";
+                }
+
+                if let ty::Ref(expected_region, _, _) = expected.kind() &&
+                    let ty::Ref(found_region, _, _) = found.kind() &&
+                    expected_region.is_late_bound() &&
+                    !found_region.is_late_bound() &&
+                    let hir::TyKind::Infer = arg_hir.kind {
+                    // If the expected region is late bound, the found region is not, and users are asking compiler
+                    // to infer the type, we can suggest adding `: &_`.
+                    if param_hir.pat.span == param_hir.ty_span {
+                        // for `|x|`, `|_|`, `|x: impl Foo|`
+                        let Ok(pat) = self.tcx.sess.source_map().span_to_snippet(param_hir.pat.span) else { return; };
+                        suggestion += &format!("{}: &_", pat);
+                    } else {
+                        // for `|x: ty|`, `|_: ty|`
+                        let Ok(pat) = self.tcx.sess.source_map().span_to_snippet(param_hir.pat.span) else { return; };
+                        let Ok(ty) = self.tcx.sess.source_map().span_to_snippet(param_hir.ty_span) else { return; };
+                        suggestion += &format!("{}: &{}", pat, ty);
+                    }
+                    has_suggestion = true;
+                } else {
+                    let Ok(arg) = self.tcx.sess.source_map().span_to_snippet(param_hir.span) else { return; };
+                    // Otherwise, keep it as-is.
+                    suggestion += &arg;
+                }
+            }
+            suggestion += "|";
+
+            if has_suggestion {
+                diag.span_suggestion_verbose(
+                    span,
+                    "consider specifying the type of the closure parameters",
+                    suggestion,
+                    Applicability::MaybeIncorrect,
+                );
+            }
+        }
+    }
 }
 
 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
diff --git a/tests/ui/lifetimes/issue-105675.rs b/tests/ui/lifetimes/issue-105675.rs
new file mode 100644
index 00000000000..58d8be8b65f
--- /dev/null
+++ b/tests/ui/lifetimes/issue-105675.rs
@@ -0,0 +1,14 @@
+fn thing(x: impl FnOnce(&u32, &u32, u32)) {}
+
+fn main() {
+    let f = | _ , y: &u32 , z | ();
+    thing(f);
+    //~^ ERROR mismatched types
+    //~^^ ERROR mismatched types
+    let f = | x, y: _  , z: u32 | ();
+    thing(f);
+    //~^ ERROR mismatched types
+    //~^^ ERROR mismatched types
+    //~^^^ ERROR implementation of `FnOnce` is not general enough
+    //~^^^^ ERROR implementation of `FnOnce` is not general enough
+}
diff --git a/tests/ui/lifetimes/issue-105675.stderr b/tests/ui/lifetimes/issue-105675.stderr
new file mode 100644
index 00000000000..66415f72bcb
--- /dev/null
+++ b/tests/ui/lifetimes/issue-105675.stderr
@@ -0,0 +1,109 @@
+error[E0308]: mismatched types
+  --> $DIR/issue-105675.rs:5:5
+   |
+LL |     thing(f);
+   |     ^^^^^^^^ one type is more general than the other
+   |
+   = note: expected trait `for<'a, 'b> FnOnce<(&'a u32, &'b u32, u32)>`
+              found trait `for<'a> FnOnce<(&u32, &'a u32, u32)>`
+note: this closure does not fulfill the lifetime requirements
+  --> $DIR/issue-105675.rs:4:13
+   |
+LL |     let f = | _ , y: &u32 , z | ();
+   |             ^^^^^^^^^^^^^^^^^^^
+note: the lifetime requirement is introduced here
+  --> $DIR/issue-105675.rs:1:18
+   |
+LL | fn thing(x: impl FnOnce(&u32, &u32, u32)) {}
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^
+help: consider specifying the type of the closure parameters
+   |
+LL |     let f = |_: &_, y: &u32, z| ();
+   |             ~~~~~~~~~~~~~~~~~~~
+
+error[E0308]: mismatched types
+  --> $DIR/issue-105675.rs:5:5
+   |
+LL |     thing(f);
+   |     ^^^^^^^^ one type is more general than the other
+   |
+   = note: expected trait `for<'a, 'b> FnOnce<(&'a u32, &'b u32, u32)>`
+              found trait `for<'a> FnOnce<(&u32, &'a u32, u32)>`
+note: this closure does not fulfill the lifetime requirements
+  --> $DIR/issue-105675.rs:4:13
+   |
+LL |     let f = | _ , y: &u32 , z | ();
+   |             ^^^^^^^^^^^^^^^^^^^
+note: the lifetime requirement is introduced here
+  --> $DIR/issue-105675.rs:1:18
+   |
+LL | fn thing(x: impl FnOnce(&u32, &u32, u32)) {}
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0308]: mismatched types
+  --> $DIR/issue-105675.rs:9:5
+   |
+LL |     thing(f);
+   |     ^^^^^^^^ one type is more general than the other
+   |
+   = note: expected trait `for<'a, 'b> FnOnce<(&'a u32, &'b u32, u32)>`
+              found trait `FnOnce<(&u32, &u32, u32)>`
+note: this closure does not fulfill the lifetime requirements
+  --> $DIR/issue-105675.rs:8:13
+   |
+LL |     let f = | x, y: _  , z: u32 | ();
+   |             ^^^^^^^^^^^^^^^^^^^^^
+note: the lifetime requirement is introduced here
+  --> $DIR/issue-105675.rs:1:18
+   |
+LL | fn thing(x: impl FnOnce(&u32, &u32, u32)) {}
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^
+help: consider specifying the type of the closure parameters
+   |
+LL |     let f = |x: &_, y: &_, z: u32| ();
+   |             ~~~~~~~~~~~~~~~~~~~~~~
+
+error[E0308]: mismatched types
+  --> $DIR/issue-105675.rs:9:5
+   |
+LL |     thing(f);
+   |     ^^^^^^^^ one type is more general than the other
+   |
+   = note: expected trait `for<'a, 'b> FnOnce<(&'a u32, &'b u32, u32)>`
+              found trait `FnOnce<(&u32, &u32, u32)>`
+note: this closure does not fulfill the lifetime requirements
+  --> $DIR/issue-105675.rs:8:13
+   |
+LL |     let f = | x, y: _  , z: u32 | ();
+   |             ^^^^^^^^^^^^^^^^^^^^^
+note: the lifetime requirement is introduced here
+  --> $DIR/issue-105675.rs:1:18
+   |
+LL | fn thing(x: impl FnOnce(&u32, &u32, u32)) {}
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^
+help: consider specifying the type of the closure parameters
+   |
+LL |     let f = |x: &_, y: &_, z: u32| ();
+   |             ~~~~~~~~~~~~~~~~~~~~~~
+
+error: implementation of `FnOnce` is not general enough
+  --> $DIR/issue-105675.rs:9:5
+   |
+LL |     thing(f);
+   |     ^^^^^^^^ implementation of `FnOnce` is not general enough
+   |
+   = note: closure with signature `fn(&'2 u32, &u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`...
+   = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2`
+
+error: implementation of `FnOnce` is not general enough
+  --> $DIR/issue-105675.rs:9:5
+   |
+LL |     thing(f);
+   |     ^^^^^^^^ implementation of `FnOnce` is not general enough
+   |
+   = note: closure with signature `fn(&u32, &'2 u32, u32)` must implement `FnOnce<(&u32, &'1 u32, u32)>`, for any lifetime `'1`...
+   = note: ...but it actually implements `FnOnce<(&u32, &'2 u32, u32)>`, for some specific lifetime `'2`
+
+error: aborting due to 6 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/lifetimes/issue-79187-2.stderr b/tests/ui/lifetimes/issue-79187-2.stderr
index c5f654b37bf..75fd87b3fe9 100644
--- a/tests/ui/lifetimes/issue-79187-2.stderr
+++ b/tests/ui/lifetimes/issue-79187-2.stderr
@@ -43,6 +43,10 @@ note: the lifetime requirement is introduced here
    |
 LL | fn take_foo(_: impl Foo) {}
    |                     ^^^
+help: consider specifying the type of the closure parameters
+   |
+LL |     take_foo(|a: &_| a);
+   |              ~~~~~~~
 
 error[E0308]: mismatched types
   --> $DIR/issue-79187-2.rs:11:5
diff --git a/tests/ui/lifetimes/issue-79187.stderr b/tests/ui/lifetimes/issue-79187.stderr
index ee6e7b89d5f..209f2b7b739 100644
--- a/tests/ui/lifetimes/issue-79187.stderr
+++ b/tests/ui/lifetimes/issue-79187.stderr
@@ -16,6 +16,10 @@ note: the lifetime requirement is introduced here
    |
 LL | fn thing(x: impl FnOnce(&u32)) {}
    |                  ^^^^^^^^^^^^
+help: consider specifying the type of the closure parameters
+   |
+LL |     let f = |_: &_| ();
+   |             ~~~~~~~
 
 error: implementation of `FnOnce` is not general enough
   --> $DIR/issue-79187.rs:5:5
diff --git a/tests/ui/mismatched_types/closure-mismatch.rs b/tests/ui/mismatched_types/closure-mismatch.rs
index b0644e79611..4eb33497c39 100644
--- a/tests/ui/mismatched_types/closure-mismatch.rs
+++ b/tests/ui/mismatched_types/closure-mismatch.rs
@@ -8,4 +8,7 @@ fn main() {
     baz(|_| ());
     //~^ ERROR implementation of `FnOnce` is not general enough
     //~| ERROR mismatched types
+    baz(|x| ());
+    //~^ ERROR implementation of `FnOnce` is not general enough
+    //~| ERROR mismatched types
 }
diff --git a/tests/ui/mismatched_types/closure-mismatch.stderr b/tests/ui/mismatched_types/closure-mismatch.stderr
index a7ef8fa0892..c5b8270ba84 100644
--- a/tests/ui/mismatched_types/closure-mismatch.stderr
+++ b/tests/ui/mismatched_types/closure-mismatch.stderr
@@ -25,7 +25,43 @@ note: the lifetime requirement is introduced here
    |
 LL | fn baz<T: Foo>(_: T) {}
    |           ^^^
+help: consider specifying the type of the closure parameters
+   |
+LL |     baz(|_: &_| ());
+   |         ~~~~~~~
+
+error: implementation of `FnOnce` is not general enough
+  --> $DIR/closure-mismatch.rs:11:5
+   |
+LL |     baz(|x| ());
+   |     ^^^^^^^^^^^ implementation of `FnOnce` is not general enough
+   |
+   = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`...
+   = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2`
+
+error[E0308]: mismatched types
+  --> $DIR/closure-mismatch.rs:11:5
+   |
+LL |     baz(|x| ());
+   |     ^^^^^^^^^^^ one type is more general than the other
+   |
+   = note: expected trait `for<'a> Fn<(&'a (),)>`
+              found trait `Fn<(&(),)>`
+note: this closure does not fulfill the lifetime requirements
+  --> $DIR/closure-mismatch.rs:11:9
+   |
+LL |     baz(|x| ());
+   |         ^^^
+note: the lifetime requirement is introduced here
+  --> $DIR/closure-mismatch.rs:5:11
+   |
+LL | fn baz<T: Foo>(_: T) {}
+   |           ^^^
+help: consider specifying the type of the closure parameters
+   |
+LL |     baz(|x: &_| ());
+   |         ~~~~~~~
 
-error: aborting due to 2 previous errors
+error: aborting due to 4 previous errors
 
 For more information about this error, try `rustc --explain E0308`.