From ce8961039e244b1e4e0fa02fc10d59a22abc9ea3 Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Thu, 4 Apr 2024 15:06:30 -0400
Subject: [PATCH] Some ordering and duplication checks

---
 compiler/rustc_hir_analysis/messages.ftl      |  6 ++
 .../rustc_hir_analysis/src/check/check.rs     | 57 ++++++++++++++-----
 .../src/errors/precise_captures.rs            | 22 ++++++-
 .../feature-gate-precise-capturing.rs         |  4 ++
 .../feature-gate-precise-capturing.stderr     | 13 +++++
 .../impl-trait/precise-capturing/ordering.rs  | 16 ++++++
 .../precise-capturing/ordering.stderr         | 37 ++++++++++++
 7 files changed, 140 insertions(+), 15 deletions(-)
 create mode 100644 tests/ui/feature-gates/feature-gate-precise-capturing.rs
 create mode 100644 tests/ui/feature-gates/feature-gate-precise-capturing.stderr
 create mode 100644 tests/ui/impl-trait/precise-capturing/ordering.rs
 create mode 100644 tests/ui/impl-trait/precise-capturing/ordering.stderr

diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl
index 81cf3067a1e..4ac2965bd5f 100644
--- a/compiler/rustc_hir_analysis/messages.ftl
+++ b/compiler/rustc_hir_analysis/messages.ftl
@@ -113,6 +113,9 @@ hir_analysis_drop_impl_on_wrong_item =
 
 hir_analysis_drop_impl_reservation = reservation `Drop` impls are not supported
 
+hir_analysis_duplicate_precise_capture = cannot capture parameter `{$name}` twice
+    .label = parameter captured again here
+
 hir_analysis_empty_specialization = specialization impl does not specialize any associated items
     .note = impl is a specialization of this impl
 
@@ -216,6 +219,9 @@ hir_analysis_late_bound_lifetime_in_apit = `impl Trait` can only mention lifetim
 hir_analysis_late_bound_type_in_apit = `impl Trait` can only mention type parameters from an fn or impl
     .label = type parameter declared here
 
+hir_analysis_lifetime_must_be_first = lifetime parameter `{$name}` must be listed before non-lifetime parameters
+    .label = move the lifetime before this parameter
+
 hir_analysis_lifetime_not_captured = `impl Trait` captures lifetime parameter, but it is not mentioned in `use<...>` precise captures list
     .label = lifetime captured due to being mentioned in the bounds of the `impl Trait`
     .param_label = this lifetime parameter is captured
diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs
index 592a8648f14..1e8cd50ca0d 100644
--- a/compiler/rustc_hir_analysis/src/check/check.rs
+++ b/compiler/rustc_hir_analysis/src/check/check.rs
@@ -4,7 +4,7 @@ use super::compare_impl_item::check_type_bounds;
 use super::compare_impl_item::{compare_impl_method, compare_impl_ty};
 use super::*;
 use rustc_attr as attr;
-use rustc_data_structures::unord::UnordSet;
+use rustc_data_structures::unord::{UnordMap, UnordSet};
 use rustc_errors::{codes::*, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{CtorKind, DefKind};
@@ -484,22 +484,51 @@ fn check_opaque_precise_captures<'tcx>(tcx: TyCtxt<'tcx>, opaque_def_id: LocalDe
     };
 
     let mut expected_captures = UnordSet::default();
+    let mut seen_params = UnordMap::default();
+    let mut prev_non_lifetime_param = None;
     for arg in precise_capturing_args {
-        match *arg {
-            hir::PreciseCapturingArg::Lifetime(&hir::Lifetime { hir_id, .. })
-            | hir::PreciseCapturingArg::Param(hir::PreciseCapturingNonLifetimeArg {
-                hir_id, ..
-            }) => match tcx.named_bound_var(hir_id) {
-                Some(ResolvedArg::EarlyBound(def_id)) => {
-                    expected_captures.insert(def_id);
+        let (hir_id, ident) = match *arg {
+            hir::PreciseCapturingArg::Param(hir::PreciseCapturingNonLifetimeArg {
+                hir_id,
+                ident,
+                ..
+            }) => {
+                if prev_non_lifetime_param.is_none() {
+                    prev_non_lifetime_param = Some(ident);
                 }
-                _ => {
-                    tcx.dcx().span_delayed_bug(
-                        tcx.hir().span(hir_id),
-                        "parameter should have been resolved",
-                    );
+                (hir_id, ident)
+            }
+            hir::PreciseCapturingArg::Lifetime(&hir::Lifetime { hir_id, ident, .. }) => {
+                if let Some(prev_non_lifetime_param) = prev_non_lifetime_param {
+                    tcx.dcx().emit_err(errors::LifetimesMustBeFirst {
+                        lifetime_span: ident.span,
+                        name: ident.name,
+                        other_span: prev_non_lifetime_param.span,
+                    });
                 }
-            },
+                (hir_id, ident)
+            }
+        };
+
+        let ident = ident.normalize_to_macros_2_0();
+        if let Some(span) = seen_params.insert(ident, ident.span) {
+            tcx.dcx().emit_err(errors::DuplicatePreciseCapture {
+                name: ident.name,
+                first_span: span,
+                second_span: ident.span,
+            });
+        }
+
+        match tcx.named_bound_var(hir_id) {
+            Some(ResolvedArg::EarlyBound(def_id)) => {
+                expected_captures.insert(def_id);
+            }
+            _ => {
+                tcx.dcx().span_delayed_bug(
+                    tcx.hir().span(hir_id),
+                    "parameter should have been resolved",
+                );
+            }
         }
     }
 
diff --git a/compiler/rustc_hir_analysis/src/errors/precise_captures.rs b/compiler/rustc_hir_analysis/src/errors/precise_captures.rs
index 3b22437abb2..e2eb9c72bf2 100644
--- a/compiler/rustc_hir_analysis/src/errors/precise_captures.rs
+++ b/compiler/rustc_hir_analysis/src/errors/precise_captures.rs
@@ -1,5 +1,5 @@
 use rustc_macros::Diagnostic;
-use rustc_span::Span;
+use rustc_span::{Span, Symbol};
 
 #[derive(Diagnostic)]
 #[diag(hir_analysis_param_not_captured)]
@@ -31,3 +31,23 @@ pub struct BadPreciseCapture {
     pub kind: &'static str,
     pub found: String,
 }
+
+#[derive(Diagnostic)]
+#[diag(hir_analysis_duplicate_precise_capture)]
+pub struct DuplicatePreciseCapture {
+    #[primary_span]
+    pub first_span: Span,
+    pub name: Symbol,
+    #[label]
+    pub second_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(hir_analysis_lifetime_must_be_first)]
+pub struct LifetimesMustBeFirst {
+    #[primary_span]
+    pub lifetime_span: Span,
+    pub name: Symbol,
+    #[label]
+    pub other_span: Span,
+}
diff --git a/tests/ui/feature-gates/feature-gate-precise-capturing.rs b/tests/ui/feature-gates/feature-gate-precise-capturing.rs
new file mode 100644
index 00000000000..0c3b4977623
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-precise-capturing.rs
@@ -0,0 +1,4 @@
+fn hello() -> impl use<> Sized {}
+//~^ ERROR precise captures on `impl Trait` are experimental
+
+fn main() {}
diff --git a/tests/ui/feature-gates/feature-gate-precise-capturing.stderr b/tests/ui/feature-gates/feature-gate-precise-capturing.stderr
new file mode 100644
index 00000000000..102b39148f9
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-precise-capturing.stderr
@@ -0,0 +1,13 @@
+error[E0658]: precise captures on `impl Trait` are experimental
+  --> $DIR/feature-gate-precise-capturing.rs:1:20
+   |
+LL | fn hello() -> impl use<> Sized {}
+   |                    ^^^
+   |
+   = note: see issue #123432 <https://github.com/rust-lang/rust/issues/123432> for more information
+   = help: add `#![feature(precise_capturing)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/impl-trait/precise-capturing/ordering.rs b/tests/ui/impl-trait/precise-capturing/ordering.rs
new file mode 100644
index 00000000000..2bace798c57
--- /dev/null
+++ b/tests/ui/impl-trait/precise-capturing/ordering.rs
@@ -0,0 +1,16 @@
+#![feature(precise_capturing)]
+//~^ WARN the feature `precise_capturing` is incomplete
+
+fn lt<'a>() -> impl use<'a, 'a> Sized {}
+//~^ ERROR cannot capture parameter `'a` twice
+
+fn ty<T>() -> impl use<T, T> Sized {}
+//~^ ERROR cannot capture parameter `T` twice
+
+fn ct<const N: usize>() -> impl use<N, N> Sized {}
+//~^ ERROR cannot capture parameter `N` twice
+
+fn ordering<'a, T>() -> impl use<T, 'a> Sized {}
+//~^ ERROR lifetime parameter `'a` must be listed before non-lifetime parameters
+
+fn main() {}
diff --git a/tests/ui/impl-trait/precise-capturing/ordering.stderr b/tests/ui/impl-trait/precise-capturing/ordering.stderr
new file mode 100644
index 00000000000..3f545108df5
--- /dev/null
+++ b/tests/ui/impl-trait/precise-capturing/ordering.stderr
@@ -0,0 +1,37 @@
+warning: the feature `precise_capturing` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/ordering.rs:1:12
+   |
+LL | #![feature(precise_capturing)]
+   |            ^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #123432 <https://github.com/rust-lang/rust/issues/123432> for more information
+   = note: `#[warn(incomplete_features)]` on by default
+
+error: cannot capture parameter `'a` twice
+  --> $DIR/ordering.rs:4:25
+   |
+LL | fn lt<'a>() -> impl use<'a, 'a> Sized {}
+   |                         ^^  -- parameter captured again here
+
+error: cannot capture parameter `T` twice
+  --> $DIR/ordering.rs:7:24
+   |
+LL | fn ty<T>() -> impl use<T, T> Sized {}
+   |                        ^  - parameter captured again here
+
+error: cannot capture parameter `N` twice
+  --> $DIR/ordering.rs:10:37
+   |
+LL | fn ct<const N: usize>() -> impl use<N, N> Sized {}
+   |                                     ^  - parameter captured again here
+
+error: lifetime parameter `'a` must be listed before non-lifetime parameters
+  --> $DIR/ordering.rs:13:37
+   |
+LL | fn ordering<'a, T>() -> impl use<T, 'a> Sized {}
+   |                                  -  ^^
+   |                                  |
+   |                                  move the lifetime before this parameter
+
+error: aborting due to 4 previous errors; 1 warning emitted
+