Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/diagnostics/region_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ impl RegionName {
)) => {
diag.span_label(
*span,
format!("lifetime `{self}` appears in the type {type_name}"),
format!("lifetime `{self}` appears in the type `{type_name}`"),
);
}
RegionNameSource::AnonRegionFromOutput(
Expand Down
72 changes: 68 additions & 4 deletions compiler/rustc_middle/src/ty/print/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ pub struct RegionHighlightMode<'tcx> {
/// instead of the ordinary behavior.
highlight_regions: [Option<(ty::Region<'tcx>, usize)>; 3],

/// If set to `true`, types that include regions will always be included in the output, while
/// other types will be free to be trimmed.
pub keep_regions: bool,

/// If enabled, when printing a "free region" that originated from
/// the given `ty::BoundRegionKind`, print it as "`'1`". Free regions that would ordinarily
/// have names print as normal.
Expand All @@ -208,6 +212,7 @@ impl<'tcx> RegionHighlightMode<'tcx> {
region: Option<ty::Region<'tcx>>,
number: Option<usize>,
) {
self.keep_regions = true;
if let Some(k) = region
&& let Some(n) = number
{
Expand All @@ -223,6 +228,7 @@ impl<'tcx> RegionHighlightMode<'tcx> {
bug!("can only highlight {} placeholders at a time", num_slots,)
});
*first_avail_slot = Some((region, number));
self.keep_regions = true;
}

/// Convenience wrapper for `highlighting_region`.
Expand Down Expand Up @@ -2330,12 +2336,70 @@ impl<'tcx> Printer<'tcx> for FmtPrinter<'_, 'tcx> {
}

fn print_type(&mut self, ty: Ty<'tcx>) -> Result<(), PrintError> {
let has_regions = self.region_highlight_mode.keep_regions
&& ty.has_type_flags(ty::TypeFlags::HAS_REGIONS);
match ty.kind() {
ty::Tuple(tys) if tys.len() == 0 && self.should_truncate() => {
ty::Tuple(tys) if tys.len() == 0 => {
// Don't truncate `()`.
self.pretty_print_type(ty)
}

ty::Adt(def, args)
if self.should_truncate()
&& args.consts().count() < 2
&& args.types().count() < 2
&& {
// We ensure that if there's at most a single type parameter and that type
// *doesn't* have any parameters, to avoid printing all the names in cases
// like `Foo<Foo<Foo<Foo<...>>>`, instead truncating those always to
// `Foo<...>`.
if let Some(arg) = args.types().next() {
if let ty::Adt(_, arg_args) = arg.kind() {
if arg_args.consts().next().is_none()
&& arg_args.types().next().is_none()
{
// Single param type with no type or const parameters:
// `Foo<Bar<'a>>`.
true
} else {
// Single param type with multiple type or const parameters:
// `Foo<Bar<Baz, Qux>>`. We don't want to recurse into those,
// we'll replace the whole thing with `...`.
false
}
} else {
// Single type param that *isn't* a type with parameters, like a
// primitive: `Foo<i32>`.
true
}
} else {
// No type param: `Foo`.
true
}
}
&& self.tcx.item_name(def.did()).as_str().len() < 7 =>
{
// Don't fully truncate types that have "short names" and at most one type or const
// param. We do use the short path for them (only item name instead of full path).
with_forced_trimmed_paths!(self.pretty_print_type(ty))
}

ty::Alias(_, alias)
if self.should_truncate()
&& let ty::AliasTyKind::Opaque { def_id } = alias.kind
&& self.region_highlight_mode.keep_regions
&& self
.tcx
.explicit_item_bounds(def_id)
.iter_instantiated_copied(self.tcx, alias.args)
.map(Unnormalized::skip_norm_wip)
.any(|(value, _)| value.has_bound_vars()) =>
{
// `<impl for<'a> Trait as Trait>`
self.printed_type_count += 1;
self.pretty_print_type(ty)
}

ty::Adt(..)
| ty::Foreign(_)
| ty::Pat(..)
Expand All @@ -2345,23 +2409,23 @@ impl<'tcx> Printer<'tcx> for FmtPrinter<'_, 'tcx> {
| ty::FnPtr(..)
| ty::UnsafeBinder(..)
| ty::Dynamic(..)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(..)
| ty::CoroutineWitness(..)
| ty::Tuple(_)
| ty::Alias(..)
| ty::Param(_)
| ty::Bound(..)
| ty::Placeholder(_)
| ty::Error(_)
if self.should_truncate() =>
if self.should_truncate() && !has_regions =>
{
// We only truncate types that we know are likely to be much longer than 3 chars.
// There's no point in replacing `i32` or `!`.
write!(self, "_")?;
Ok(())
}
ty::Ref(..) if self.should_truncate() && has_regions => self.pretty_print_type(ty),
ty::Closure(..) => self.pretty_print_type(ty),
_ => {
self.printed_type_count += 1;
self.pretty_print_type(ty)
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_trait_selection/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1203,9 +1203,9 @@ impl Subdiagnostic for ConsiderBorrowingParamHelp {
#[diag("`impl` item signature doesn't match `trait` item signature")]
pub(crate) struct TraitImplDiff {
#[primary_span]
#[label("found `{$found}`")]
#[label("found `{$found_short}`")]
pub sp: Span,
#[label("expected `{$expected}`")]
#[label("expected `{$expected_short}`")]
pub trait_sp: Span,
#[note(
"expected signature `{$expected}`
Expand All @@ -1218,6 +1218,8 @@ pub(crate) struct TraitImplDiff {
"verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output"
)]
pub rel_help: bool,
pub expected_short: String,
pub found_short: String,
pub expected: String,
pub found: String,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rustc_errors::{Applicability, Diag, IntoDiagArg};
use rustc_hir as hir;
use rustc_hir::def::Namespace;
use rustc_hir::def_id::{CRATE_DEF_ID, DefId};
use rustc_hir::limit::Limit;
use rustc_middle::bug;
use rustc_middle::ty::error::ExpectedFound;
use rustc_middle::ty::print::{FmtPrinter, Print, PrintTraitRefExt as _, RegionHighlightMode};
Expand All @@ -29,7 +30,7 @@ pub(crate) struct Highlighted<'tcx, T> {

impl<'tcx, T> IntoDiagArg for Highlighted<'tcx, T>
where
T: for<'a> Print<FmtPrinter<'a, 'tcx>>,
T: for<'a> Print<FmtPrinter<'a, 'tcx>> + Copy,
{
fn into_diag_arg(self, _: &mut Option<std::path::PathBuf>) -> rustc_errors::DiagArgValue {
rustc_errors::DiagArgValue::Str(self.to_string().into())
Expand All @@ -44,14 +45,28 @@ impl<'tcx, T> Highlighted<'tcx, T> {

impl<'tcx, T> fmt::Display for Highlighted<'tcx, T>
where
T: for<'a> Print<FmtPrinter<'a, 'tcx>>,
T: for<'a> Print<FmtPrinter<'a, 'tcx>> + Copy,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut p = ty::print::FmtPrinter::new(self.tcx, self.ns);
p.region_highlight_mode = self.highlight;

self.value.print(&mut p)?;
f.write_str(&p.into_buffer())
let b = p.into_buffer();
if b.len() <= 40 || !self.highlight.keep_regions || self.tcx.sess.opts.verbose {
// This is a short enough type that can be safely be printed to the user, or we aren't
// showing the type with a particular interest in its lifetimes.
f.write_str(&b)?;
} else {
// We are highlighting lifetimes in the output, we will print out the smallest possible
// portion of the type while keeping the lifetimes visible.
let mut p = FmtPrinter::new_with_limit(self.tcx, self.ns, Limit(0));
p.region_highlight_mode = self.highlight;
self.value.print(&mut p).expect("could not print type");
let x = p.into_buffer();
f.write_str(&x)?;
}
Ok(())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,27 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
}

let tcx = self.cx.tcx;
let expected_highlight = HighlightBuilder::build(tcx, expected);
let mut expected_highlight = HighlightBuilder::build(tcx, expected);
let expected_short = Highlighted {
highlight: expected_highlight,
ns: Namespace::TypeNS,
tcx,
value: expected,
}
.to_string();
expected_highlight.keep_regions = false;
let expected = Highlighted {
highlight: expected_highlight,
ns: Namespace::TypeNS,
tcx,
value: expected,
}
.to_string();
let found_highlight = HighlightBuilder::build(tcx, found);
let mut found_highlight = HighlightBuilder::build(tcx, found);
let found_short =
Highlighted { highlight: found_highlight, ns: Namespace::TypeNS, tcx, value: found }
.to_string();
found_highlight.keep_regions = false;
let found =
Highlighted { highlight: found_highlight, ns: Namespace::TypeNS, tcx, value: found }
.to_string();
Expand Down Expand Up @@ -121,6 +133,8 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
rel_help: visitor.types.is_empty(),
expected,
found,
expected_short,
found_short,
};

let mut diag = self.tcx().dcx().create_err(diag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::{
Applicability, Diag, E0309, E0310, E0311, E0803, Subdiagnostic, msg, struct_span_code_err,
};
use rustc_hir::def::DefKind;
use rustc_hir::def::{DefKind, Namespace};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::Visitor;
use rustc_hir::{self as hir, ParamName};
use rustc_middle::bug;
use rustc_middle::traits::ObligationCauseCode;
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::print::RegionHighlightMode;
use rustc_middle::ty::{
self, IsSuggestable, Region, Ty, TyCtxt, TypeVisitableExt as _, Upcast as _,
};
Expand All @@ -25,6 +26,7 @@ use crate::diagnostics::{
};
use crate::error_reporting::TypeErrCtxt;
use crate::error_reporting::infer::ObligationCauseExt;
use crate::error_reporting::infer::nice_region_error::placeholder_error::Highlighted;
use crate::infer::region_constraints::GenericKind;
use crate::infer::{
BoundRegionConversionTime, InferCtxt, RegionResolutionError, RegionVariableOrigin,
Expand Down Expand Up @@ -1286,6 +1288,9 @@ pub fn unexpected_hidden_region_diagnostic<'a, 'tcx>(
),
opaque_ty_span: tcx.def_span(opaque_ty_key.def_id),
});
let mut highlight = RegionHighlightMode::default();
highlight.keep_regions = true;
let hidden_ty = Highlighted { highlight, ns: Namespace::TypeNS, tcx, value: hidden_ty };

// Explain the region we are capturing.
match hidden_region.kind() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ LL | | Next(&Buffered(Map(Empty(PhantomData), ready::<&()>), FuturesOrde
LL | | });
| |______^ implementation of `FnOnce` is not general enough
|
= note: `fn(&'0 ()) -> std::future::Ready<&'0 ()> {std::future::ready::<&'0 ()>}` must implement `FnOnce<(&'1 (),)>`, for any two lifetimes `'0` and `'1`...
= note: `fn(&'0 ()) -> Ready<&'0 ()> {std::future::ready::<&'0 ()>}` must implement `FnOnce<(&'1 (),)>`, for any two lifetimes `'0` and `'1`...
= note: ...but it actually implements `FnOnce<(&(),)>`

error: implementation of `FnOnce` is not general enough
Expand All @@ -17,7 +17,7 @@ LL | | Next(&Buffered(Map(Empty(PhantomData), ready::<&()>), FuturesOrde
LL | | });
| |______^ implementation of `FnOnce` is not general enough
|
= note: `fn(&'0 ()) -> std::future::Ready<&'0 ()> {std::future::ready::<&'0 ()>}` must implement `FnOnce<(&'1 (),)>`, for any two lifetimes `'0` and `'1`...
= note: `fn(&'0 ()) -> Ready<&'0 ()> {std::future::ready::<&'0 ()>}` must implement `FnOnce<(&'1 (),)>`, for any two lifetimes `'0` and `'1`...
Comment thread
workingjubilee marked this conversation as resolved.
= note: ...but it actually implements `FnOnce<(&(),)>`
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error: implementation of `FnOnce` is not general enough
LL | require_send(future);
| ^^^^^^^^^^^^^^^^^^^^ 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`...
= note: ...but it actually implements `FnOnce<(&Vec<i32>,)>`
help: consider adding an explicit type annotation to the closure's argument
|
Expand All @@ -17,7 +17,7 @@ error: implementation of `FnOnce` is not general enough
LL | require_send(future);
| ^^^^^^^^^^^^^^^^^^^^ 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`...
= note: ...but it actually implements `FnOnce<(&Vec<i32>,)>`
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
help: consider adding an explicit type annotation to the closure's argument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ error: lifetime may not live long enough
LL | move |()| s.chars().map(|c| format!("{}{}", c, s))
| --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure `Map<Chars<'_>, {closure@$DIR/issue-95079-missing-move-in-nested-closure.rs:11:29: 11:32}>` contains a lifetime `'2`
| | return type of closure `Map<Chars<'_>, {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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ LL | | x.to_string();
LL | | });
| |______^ implementation of `Foo` is not general enough
|
= note: `Wrap<{closure@$DIR/obligation-with-leaking-placeholders.rs:18:15: 18:18}>` must implement `Foo<'0>`, for any lifetime `'0`...
= note: `Wrap<{closure@...}>` must implement `Foo<'0>`, for any lifetime `'0`...
= note: ...but it actually implements `Foo<'1>`, for some specific lifetime `'1`

error: aborting due to 1 previous error
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/closures/wrong-closure-arg-suggestion-125325.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ error: lifetime may not live long enough
LL | take(|_| to_fn(|_| self.counter += 1));
| --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure `aux::Map<{closure@$DIR/wrong-closure-arg-suggestion-125325.rs:46:24: 46:27}>` contains a lifetime `'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
Expand Down Expand Up @@ -77,7 +77,7 @@ error: lifetime may not live long enough
LL | P.flat_map(|_| P.map(|_| self.counter += 1));
| --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure `aux::Map<{closure@$DIR/wrong-closure-arg-suggestion-125325.rs:54:30: 54:33}>` contains a lifetime `'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
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/codegen/overflow-during-mono.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ error[E0275]: overflow evaluating the requirement `for<'a> {closure@$DIR/overflo
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "64"]` attribute to your crate (`overflow_during_mono`)
= note: required for `Filter<IntoIter<i32, 11>, {closure@overflow-during-mono.rs:14:41}>` to implement `Iterator`
= note: 31 redundant requirements hidden
= note: required for `Filter<Filter<Filter<Filter<Filter<Filter<_, _>, _>, _>, _>, _>, _>` to implement `Iterator`
= note: required for `Filter<Filter<Filter<Filter<Filter<Filter<_, _>, _>, _>, _>, _>, _>` to implement `IntoIterator`
= note: required for `Filter<Filter<Filter<_, {closure@...}>, {closure@...}>, {closure@...}>` to implement `Iterator`
= note: required for `Filter<Filter<Filter<_, {closure@...}>, {closure@...}>, {closure@...}>` to implement `IntoIterator`
= note: the full name for the type has been written to '$TEST_BUILD_DIR/overflow-during-mono.long-type-$LONG_TYPE_HASH.txt'
= note: consider using `--verbose` to print the full type name to the console

Expand Down
4 changes: 2 additions & 2 deletions tests/ui/impl-trait/static-return-lifetime-infered.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error[E0700]: hidden type for `impl Iterator<Item = u32>` captures lifetime that
LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
| ----- ----------------------- opaque type defined here
| |
| hidden type `Map<std::slice::Iter<'_, (u32, u32)>, {closure@$DIR/static-return-lifetime-infered.rs:7:27: 7:30}>` captures the anonymous lifetime defined here
| hidden type `Map<Iter<'_, _>, {closure@...}>` captures the anonymous lifetime defined here
LL | self.x.iter().map(|a| a.0)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
Expand All @@ -19,7 +19,7 @@ error[E0700]: hidden type for `impl Iterator<Item = u32>` captures lifetime that
LL | fn iter_values<'a>(&'a self) -> impl Iterator<Item=u32> {
| -- ----------------------- opaque type defined here
| |
| 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

|
Expand Down
Loading
Loading