Skip to content

Shorten types more when only interesting part is lifetimes#158992

Open
estebank wants to merge 14 commits into
rust-lang:mainfrom
estebank:shorten-type-highlighted-lt
Open

Shorten types more when only interesting part is lifetimes#158992
estebank wants to merge 14 commits into
rust-lang:mainfrom
estebank:shorten-type-highlighted-lt

Conversation

@estebank

@estebank estebank commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

View all comments

We have a mechanims (Highlight) to change the name of inferred lifetimes in types so that we can refer to them by a number. Modify it so that it also avoids printing sub-parts of the type that are irrelevant to the situation at hand. The new behavior will print any sub part that has any lifetime (as they might be informative) instead of just the lifetimes being replaced. This was found to produce the best effect empirically in the test suite, striking a nice balance between shortening labels and being informative.

error: lifetime may not live long enough
  --> $DIR/wrong-closure-arg-suggestion-125325.rs:46:18
   |
LL |         take(|_| to_fn(|_| self.counter += 1));
   |              --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |              | |
   |              | return type of closure `aux::Map<{closure@...}>` contains a lifetime `'2`
   |              lifetime `'1` represents this closure's body
   |
   = note: closure implements `Fn`, so references to captured variables can't escape the closure
error: implementation of `FnOnce` is not general enough
  --> $DIR/higher-ranked-auto-trait-15.rs:20:5
   |
LL |     require_send(future);
   |     ^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'0 _) -> Iter<'_, i32>` must implement `FnOnce<(&'1 Vec<i32>,)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(&Vec<i32>,)>`

Made this change after noticing that the first of the errors above had a corresponding .stderr file with lines longer than 150 columns by using rg ".. \| .{150}" -g *.stderr.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 9, 2026
@rustbot

rustbot commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

r? @mejrs

rustbot has assigned @mejrs.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 75 candidates
  • Random selection from 18 candidates

Comment on lines +10 to +11
= note: expected signature `fn(ValueRef<'1>) -> Result<(&'2 str, &'1 &'2 str), ...>`
found signature `fn(ValueRef<'1>) -> Result<(&'1 str, &'1 &'1 str), ...>`

@estebank estebank Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not using the "highlighted expected/found" we use for E0308... It'll likely require extending that logic to also learn to replace lifetime names in the same way that Highlight does.

View changes since the review

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@mejrs mejrs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks mostly okay to me. I have some thoughts.

@rustbot author

View changes since this review

write!(self, "{coroutine_kind}")?;
if coroutine_kind.is_fn_like() {
if self.should_truncate() {
write!(self, "@...}}")?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of printing @.... Here and elsewhere we end up printing something like {async closure@...}, {coroutine@...} and I think if shown out of context that'll be confusing.

Instead consider trimming the paths leading up to the file, so e.g. {async closure@dir/path/to/the/file.rs:LL:CC: LL:CC} becomes {async closure@file.rs:LL:CC: LL:CC}.

Or maybe we should just say {async closure}? 🤔

In any case most users will have module names much shorter than our test file's names, so I think the ui test output isn't really representative of what users get in the wild.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could swear that we have some logic doing that, writing only the filename if the path is too long and we're shortening things... I need to check more closely where we do that.

I'm trying out this (making the output {async closure}) and the one below (using _ instead of ...) and will push on its own commit to show what it looks like.

| ^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
|
= note: closure with signature `fn(&'0 Vec<i32>) -> std::slice::Iter<'_, i32>` must implement `FnOnce<(&'1 Vec<i32>,)>`, for any two lifetimes `'0` and `'1`...
= note: closure with signature `fn(&'0 ...) -> Iter<'_, i32>` must implement `FnOnce<(&'1 Vec<i32>,)>`, for any two lifetimes `'0` and `'1`...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like an improvement to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed Vec<i32> or the std::slice?

@mejrs mejrs Jul 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fn(&'0 ...) part. ... in function signatures is a type (sort of) so this is an unacceptable regression in my eyes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the change to &'0 _ change the equation for you?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still a bit weird, but I like it better.

Comment thread tests/ui/traits/self-without-lifetime-constraint.stderr Outdated
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 11, 2026
@rustbot

This comment was marked as outdated.

@rustbot

This comment was marked as off-topic.

@rustbot rustbot added the WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) label Jul 12, 2026
@estebank

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jul 12, 2026
@estebank estebank force-pushed the shorten-type-highlighted-lt branch 3 times, most recently from 95cd4a6 to fe43871 Compare July 12, 2026 19:58
@rust-log-analyzer

This comment has been minimized.

@mejrs mejrs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it does look better to me. I'll have to think it over a bit. What do you think?

View changes since this review

@@ -1,15 +1,15 @@
error[E0369]: cannot add `(..., ..., ..., ...)` to `(..., ..., ..., ...)`
error[E0369]: cannot add `((_, _, _, _), _, _, _)` to `((_, _, _, _), _, _, _)`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting, I think I like this better as well.

@workingjubilee workingjubilee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of these do seem like improvements but I dunno about the closure type changes. Is it possible for these errors to ever be split across files, or located in such a large file, in such a way that the initial error line doesn't usefully indicate what I'm looking at? e.g. files with 5000 lines with 10 similar large closures that fill the screen.

But if we do want them, then I'm not sure we shouldn't just cut even more?

View changes since this review

@@ -15,7 +15,7 @@ error: lifetime may not live long enough
LL | let x = async move || { &s };
| ------------- ^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of async closure `{async closure body@$DIR/not-lending.rs:16:31: 16:37}` contains a lifetime `'2`
| | return type of async closure `{async closure body}` contains a lifetime `'2`

@workingjubilee workingjubilee Jul 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this {async closure body} doing anything here? The previous variant indicated the type's location in a useful way, but here "async closure" is a complete idea, and we're possibly just wasting our breath on saying "the async closure that, btw, is an async closure..." Really, rustc? You don't say?

An alternative way to reduce the number of characters here would just be

return type of `{async closure@$DIR/not-lending.rs:16:31:16:37}` contains a lifetime `'2`

This removes about 16 characters that at least seem to me like no one could benefit from... about a 5th of the amount of content that can easily be scanned on a line if one adheres to the "3 alphabets" rule... without reducing the actual information transmitted. If the body is important, then we can keep that and still save a fair amount "for free".

For why not remove the in-file location: I'm worried that we're potentially losing the span info here, and relying on the end of the closure being easily findable from the start.

@workingjubilee workingjubilee Jul 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, pragmatically, even in a more conservative version, we could save more space by not including column numbers and mmmaybe intelligently collapsing them if they're on a single line? If someone puts multiple closures on a single line I do feel comfortable saying "...don't do that, though? or at least not if you want good diagnostics..." Sometimes generated code looks like that but formatters exist. That could look like...

return type of `{async closure body@$DIR/not-lending.rs:16}` contains a lifetime `'2`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make a distinction between a top-level closure-like type that got shortened and one that is in a type parameter. It is still useful to see Result<{closure}, Error>, specially when it is highly nested. In these errors, the most important bit of information is the lifetimes involved, not the types themselves (because those already type checked so they are right).

We might be able to check "is the span label this name going to be used in pointing at the closure itself?" to completely avoid the type...

I want to point out that in type errors where the distinction of closures is more important, on the expected/found note we do print the closure's span's fs path, so the information might not be included in a span label.

I'm not happy with the rendering of closure-likes in general, but we can argue about that elsewhere :)

Calling these "async closure body return type" is weird indeed. I'm intrigued why we did that in the first place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, looking at this more closely I realize what's going on:

The message is talking about "the return type for the closure being pointed at, which is {...}, contains lifetime 'x". That returned type is the evaluated type for the closure's body. So if we have |()| (), it would be (), but if we have |()| || (), it would be {closure@...}. This is confusing and removing the path is certainly a problem. But this gets even worse with async closures, because those get desugared to something like |_| async {} , so the return type of the outer closure is the async body. That's why we see these bizarre output. We need to fix an outright bug on these diagnostics when it comes to async closures.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wild.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partly addressed the above in the last commit: we now present the right type, but the primary span used in these errors is incorrect, it should be pointing at the tail expression of the async closure, instead of the whole body of the closure.

Comment thread tests/ui/borrowck/issue-53432-nested-closure-outlives-borrowed-value.stderr Outdated
@estebank estebank force-pushed the shorten-type-highlighted-lt branch from 0e61216 to b399d1a Compare July 13, 2026 17:07
@rustbot

This comment has been minimized.

Comment on lines +7 to +18
@@ -15,7 +15,7 @@ error: lifetime may not live long enough
LL | let x = async move || { &s };
| ------------- ^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of async closure `{async closure body@$DIR/not-lending.rs:16:31: 16:37}` contains a lifetime `'2`
| | return type of async closure is &'2 String

@workingjubilee workingjubilee Jul 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...Wow, yeah, my eyes had just kind of glazed over and I didn't really think about how this was... kinda babble? I'm glad I raised the point of "this should either keep more or cut more". This is enormously clearer.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need to fix the Span to point at &s instead of { &s }, but I waded through that for a bit and I think I won't be fixing it myself anytime soon. 😬

@rust-bors

This comment has been minimized.

We have a mechanims (`Highlight`) to change the name of inferred lifetimes in types so that we can refer to them by a number. Modify it so that it also avoids printing sub-parts of the type that are irrelevant to the situation at hand. The new behavior will print any sub part that has *any* lifetime (as they might be informative) instead of *just* the lifetimes being replaced. This was found to produce the best effect empirically in the test suite, striking a nice balance between shortening labels and being informative.

```
error: lifetime may not live long enough
  --> $DIR/wrong-closure-arg-suggestion-125325.rs:46:18
   |
LL |         take(|_| to_fn(|_| self.counter += 1));
   |              --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |              | |
   |              | return type of closure `aux::Map<{closure@...}>` contains a lifetime `'2`
   |              lifetime `'1` represents this closure's body
   |
   = note: closure implements `Fn`, so references to captured variables can't escape the closure
```
```
error: implementation of `FnOnce` is not general enough
  --> $DIR/higher-ranked-auto-trait-15.rs:20:5
   |
LL |     require_send(future);
   |     ^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'0 ...) -> std::slice::Iter<'_, i32>` must implement `FnOnce<(&'1 Vec<i32>,)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(&Vec<i32>,)>`
```

Made this change after noticing that the first of the errors above had a corresponding `.stderr` file with lines longer than 150 columns.
estebank added 10 commits July 15, 2026 16:03
```
error[E0700]: hidden type for `impl Iterator<Item = u32>` captures lifetime that does not appear in bounds
  --> $DIR/static-return-lifetime-infered.rs:11:9
   |
LL |     fn iter_values<'a>(&'a self) -> impl Iterator<Item=u32> {
   |                    --               ----------------------- opaque type defined here
   |                    |
   |                    hidden type `Map<std::slice::Iter<'a, ...>, {closure@...}>` captures the lifetime `'a` as defined here
LL |         self.x.iter().map(|a| a.0)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^
```
@estebank estebank force-pushed the shorten-type-highlighted-lt branch from 8310ea8 to 2836709 Compare July 15, 2026 16:36
@rustbot

rustbot commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

estebank added 3 commits July 15, 2026 16:37
…n the *root* type

This means that when we encounter a closure, we never hide it's path from the rendering, but if the closure is in a type parameter, it will get shortened. This should be fine for most lifetime errors to be understandable.
@estebank estebank force-pushed the shorten-type-highlighted-lt branch from 2836709 to c6f5692 Compare July 15, 2026 16:37
| hidden type `Map<std::slice::Iter<'a, (u32, u32)>, {closure@$DIR/static-return-lifetime-infered.rs:11:27: 11:30}>` captures the lifetime `'a` as defined here
| hidden type `Map<Iter<'a, _>, {closure@...}>` captures the lifetime `'a` as defined here
LL | self.x.iter().map(|a| a.0)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

@estebank estebank Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to improve would be making this primary span mention "this type captures lifetime 'a".

View changes since the review

@mejrs mejrs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this, happy to approve it. Sorry/not sorry for sending you on this goose chase doing related changes 😆

Thanks to you and @workingjubilee for your work here. I'll give @workingjubilee some time to look over these changes.

View changes since this review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants