Repository: rust-lang/rust
Pull request: Fix trailing comma in lifetime suggestion for empty angle brackets

Fixes rust-lang/rust#154600

When suggesting a lifetime parameter (e.g., `'a`) for a type like `Foo<>` (empty angle brackets), the compiler was incorrectly producing `Foo<'a, >` with a trailing comma.

## Root Cause

The `has_existing_params` check used `segment.args` to determine if generic parameters exist. However, for empty angle brackets (`<>`), the parser doesn't create any `args`, so `has_existing_params` was `false` — even though the angle brackets themselves exist.

This caused the suggestion logic to skip the comma+space suffix (`"'a, "`), but the insert position was still inside the angle brackets, leading to `Foo<'a, >` instead of `Foo<'a>`.

## Fix

Replace the `segment.args`-based check with a source text inspection using `span_to_snippet`. The new logic:
1. Finds the `<` character in the source text
2. Extracts the content between `<` and `>`
3. Checks if that content is non-empty (after trimming whitespace)

This correctly identifies `<>` as having no existing parameters, avoiding the trailing comma.

## Test

Added `tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs` covering the specific case of empty angle brackets with lifetime suggestions.

Changed files:
filename: compiler/rustc_resolve/src/late/diagnostics.rs
patch: @@ -4094,6 +4094,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
         };
 
         let mut spans_suggs: Vec<_> = Vec::new();
+        let source_map = self.r.tcx.sess.source_map();
         let build_sugg = |lt: MissingLifetime| match lt.kind {
             MissingLifetimeKind::Underscore => {
                 debug_assert_eq!(lt.count, 1);
@@ -4104,9 +4105,12 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
                 (lt.span.shrink_to_hi(), format!("{existing_name} "))
             }
             MissingLifetimeKind::Comma => {
-                let sugg: String = std::iter::repeat_n([existing_name.as_str(), ", "], lt.count)
-                    .flatten()
+                let sugg: String = std::iter::repeat_n(existing_name.as_str(), lt.count)
+                    .intersperse(", ")
                     .collect();
+                let is_empty_brackets =
+                    source_map.span_look_ahead(lt.span, ">", Some(50)).is_some();
+                let sugg = if is_empty_brackets { sugg } else { format!("{sugg}, ") };
                 (lt.span.shrink_to_hi(), sugg)
             }
             MissingLifetimeKind::Brackets => {

filename: tests/ui/generics/wrong-number-of-args.stderr
patch: @@ -28,8 +28,8 @@ LL |     type E = Ty<>;
    |
 help: consider introducing a named lifetime parameter
    |
-LL |     type E<'a> = Ty<'a, >;
-   |           ++++      +++
+LL |     type E<'a> = Ty<'a>;
+   |           ++++      ++
 
 error[E0106]: missing lifetime specifier
   --> $DIR/wrong-number-of-args.rs:120:22
@@ -55,12 +55,12 @@ LL |     type F = Box<dyn GenericLifetime<>>;
    |
 help: consider making the bound lifetime-generic with a new `'a` lifetime
    |
-LL |     type F = Box<dyn for<'a> GenericLifetime<'a, >>;
-   |                      +++++++                 +++
+LL |     type F = Box<dyn for<'a> GenericLifetime<'a>>;
+   |                      +++++++                 ++
 help: consider introducing a named lifetime parameter
    |
-LL |     type F<'a> = Box<dyn GenericLifetime<'a, >>;
-   |           ++++                           +++
+LL |     type F<'a> = Box<dyn GenericLifetime<'a>>;
+   |           ++++                           ++
 
 error[E0106]: missing lifetime specifier
   --> $DIR/wrong-number-of-args.rs:163:43

filename: tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs
patch: @@ -0,0 +1,18 @@
+// Regression test for <https://github.com/rust-lang/rust/issues/154600>.
+//
+// When suggesting lifetime parameters for empty angle brackets like `Foo<>`,
+// the suggestion should not include a trailing comma (e.g., `Foo<'a>` not `Foo<'a, >`).
+// When there are other generic arguments like `Foo<T>`, the trailing comma is needed
+// (e.g., `Foo<'a, T>`).
+
+#![crate_type = "lib"]
+
+struct Foo<'a>(&'a ());
+
+type A = Foo<>;
+//~^ ERROR missing lifetime specifier [E0106]
+
+struct Bar<'a, T>(&'a T);
+
+type B = Bar<u8>;
+//~^ ERROR missing lifetime specifier [E0106]

filename: tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.stderr
patch: @@ -0,0 +1,25 @@
+error[E0106]: missing lifetime specifier
+  --> $DIR/E0106-trailing-comma-in-lifetime-suggestion.rs:12:13
+   |
+LL | type A = Foo<>;
+   |             ^ expected named lifetime parameter
+   |
+help: consider introducing a named lifetime parameter
+   |
+LL | type A<'a> = Foo<'a>;
+   |       ++++       ++
+
+error[E0106]: missing lifetime specifier
+  --> $DIR/E0106-trailing-comma-in-lifetime-suggestion.rs:17:13
+   |
+LL | type B = Bar<u8>;
+   |             ^ expected named lifetime parameter
+   |
+help: consider introducing a named lifetime parameter
+   |
+LL | type B<'a> = Bar<'a, u8>;
+   |       ++++       +++
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0106`.