Rollup merge of #138126 - compiler-errors:rtn-for-sugg, r=oli-obk

Add an opt-out in pretty printing for RTN rendering

Today, we render RPITIT types like `impl Sized { T::method(..) }` when RTN is enabled. This is very useful for diagnostics, since it's often not clear what the `impl Sized` type means by itself, and it makes it clear that that's an RPITIT that can be bounded using RTN syntax. See #115624.

However, since we don't distinguish types that are rendered for the purposes of printing messages vs suggestions, this representation leaks into suggestions and turns into code that can't be parsed. This PR adds a new `with_types_for_suggestion! {}` and `with_types_for_signature! {}` options to the pretty printing architecture to make it clear that we're rendering a type for code suggestions.

This can be applied later as we find that we need it.
This commit is contained in:
Matthias Krüger 2025-03-13 17:44:04 +01:00 committed by GitHub
commit b5955e74e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 154 additions and 32 deletions

View File

@ -84,6 +84,7 @@ use rustc_infer::infer::{self, TyCtxtInferExt as _};
use rustc_infer::traits::ObligationCause;
use rustc_middle::query::Providers;
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::print::with_types_for_signature;
use rustc_middle::ty::{self, GenericArgs, GenericArgsRef, Ty, TyCtxt, TypingMode};
use rustc_middle::{bug, span_bug};
use rustc_session::parse::feature_err;
@ -240,11 +241,11 @@ fn missing_items_err(
(Vec::new(), Vec::new(), Vec::new());
for &trait_item in missing_items {
let snippet = suggestion_signature(
let snippet = with_types_for_signature!(suggestion_signature(
tcx,
trait_item,
tcx.impl_trait_ref(impl_def_id).unwrap().instantiate_identity(),
);
));
let code = format!("{padding}{snippet}\n{padding}");
if let Some(span) = tcx.hir().span_if_local(trait_item.def_id) {
missing_trait_item_label

View File

@ -63,6 +63,18 @@ thread_local! {
static FORCE_TRIMMED_PATH: Cell<bool> = const { Cell::new(false) };
static REDUCED_QUERIES: Cell<bool> = const { Cell::new(false) };
static NO_VISIBLE_PATH: Cell<bool> = const { Cell::new(false) };
static RTN_MODE: Cell<RtnMode> = const { Cell::new(RtnMode::ForDiagnostic) };
}
/// Rendering style for RTN types.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum RtnMode {
/// Print the RTN type as an impl trait with its path, i.e.e `impl Sized { T::method(..) }`.
ForDiagnostic,
/// Print the RTN type as an impl trait, i.e. `impl Sized`.
ForSignature,
/// Print the RTN type as a value path, i.e. `T::method(..): ...`.
ForSuggestion,
}
macro_rules! define_helper {
@ -124,6 +136,38 @@ define_helper!(
fn with_no_visible_paths(NoVisibleGuard, NO_VISIBLE_PATH);
);
#[must_use]
pub struct RtnModeHelper(RtnMode);
impl RtnModeHelper {
pub fn with(mode: RtnMode) -> RtnModeHelper {
RtnModeHelper(RTN_MODE.with(|c| c.replace(mode)))
}
}
impl Drop for RtnModeHelper {
fn drop(&mut self) {
RTN_MODE.with(|c| c.set(self.0))
}
}
/// Print types for the purposes of a suggestion.
///
/// Specifically, this will render RPITITs as `T::method(..)` which is suitable for
/// things like where-clauses.
pub macro with_types_for_suggestion($e:expr) {{
let _guard = $crate::ty::print::pretty::RtnModeHelper::with(RtnMode::ForSuggestion);
$e
}}
/// Print types for the purposes of a signature suggestion.
///
/// Specifically, this will render RPITITs as `impl Trait` rather than `T::method(..)`.
pub macro with_types_for_signature($e:expr) {{
let _guard = $crate::ty::print::pretty::RtnModeHelper::with(RtnMode::ForSignature);
$e
}}
/// Avoids running any queries during prints.
pub macro with_no_queries($e:expr) {{
$crate::ty::print::with_reduced_queries!($crate::ty::print::with_forced_impl_filename_line!(
@ -1223,22 +1267,6 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
}
}
if self.tcx().features().return_type_notation()
&& let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, .. }) =
self.tcx().opt_rpitit_info(def_id)
&& let ty::Alias(_, alias_ty) =
self.tcx().fn_sig(fn_def_id).skip_binder().output().skip_binder().kind()
&& alias_ty.def_id == def_id
&& let generics = self.tcx().generics_of(fn_def_id)
// FIXME(return_type_notation): We only support lifetime params for now.
&& generics.own_params.iter().all(|param| matches!(param.kind, ty::GenericParamDefKind::Lifetime))
{
let num_args = generics.count();
write!(self, " {{ ")?;
self.print_def_path(fn_def_id, &args[..num_args])?;
write!(self, "(..) }}")?;
}
Ok(())
}
@ -1306,6 +1334,46 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
)
}
fn pretty_print_rpitit(
&mut self,
def_id: DefId,
args: ty::GenericArgsRef<'tcx>,
) -> Result<(), PrintError> {
let fn_args = if self.tcx().features().return_type_notation()
&& let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, .. }) =
self.tcx().opt_rpitit_info(def_id)
&& let ty::Alias(_, alias_ty) =
self.tcx().fn_sig(fn_def_id).skip_binder().output().skip_binder().kind()
&& alias_ty.def_id == def_id
&& let generics = self.tcx().generics_of(fn_def_id)
// FIXME(return_type_notation): We only support lifetime params for now.
&& generics.own_params.iter().all(|param| matches!(param.kind, ty::GenericParamDefKind::Lifetime))
{
let num_args = generics.count();
Some((fn_def_id, &args[..num_args]))
} else {
None
};
match (fn_args, RTN_MODE.with(|c| c.get())) {
(Some((fn_def_id, fn_args)), RtnMode::ForDiagnostic) => {
self.pretty_print_opaque_impl_type(def_id, args)?;
write!(self, " {{ ")?;
self.print_def_path(fn_def_id, fn_args)?;
write!(self, "(..) }}")?;
}
(Some((fn_def_id, fn_args)), RtnMode::ForSuggestion) => {
self.print_def_path(fn_def_id, fn_args)?;
write!(self, "(..)")?;
}
_ => {
self.pretty_print_opaque_impl_type(def_id, args)?;
}
}
Ok(())
}
fn ty_infer_name(&self, _: ty::TyVid) -> Option<Symbol> {
None
}
@ -3123,21 +3191,20 @@ define_print! {
ty::AliasTerm<'tcx> {
match self.kind(cx.tcx()) {
ty::AliasTermKind::InherentTy => p!(pretty_print_inherent_projection(*self)),
ty::AliasTermKind::ProjectionTy
ty::AliasTermKind::ProjectionTy => {
if !(cx.should_print_verbose() || with_reduced_queries())
&& cx.tcx().is_impl_trait_in_trait(self.def_id)
{
p!(pretty_print_rpitit(self.def_id, self.args))
} else {
p!(print_def_path(self.def_id, self.args));
}
}
| ty::AliasTermKind::WeakTy
| ty::AliasTermKind::OpaqueTy
| ty::AliasTermKind::UnevaluatedConst
| ty::AliasTermKind::ProjectionConst => {
// If we're printing verbosely, or don't want to invoke queries
// (`is_impl_trait_in_trait`), then fall back to printing the def path.
// This is likely what you want if you're debugging the compiler anyways.
if !(cx.should_print_verbose() || with_reduced_queries())
&& cx.tcx().is_impl_trait_in_trait(self.def_id)
{
return cx.pretty_print_opaque_impl_type(self.def_id, self.args);
} else {
p!(print_def_path(self.def_id, self.args));
}
p!(print_def_path(self.def_id, self.args));
}
}
}

View File

@ -26,7 +26,7 @@ use rustc_middle::traits::IsConstable;
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::print::{
PrintPolyTraitPredicateExt as _, PrintPolyTraitRefExt, PrintTraitPredicateExt as _,
with_forced_trimmed_paths, with_no_trimmed_paths,
with_forced_trimmed_paths, with_no_trimmed_paths, with_types_for_suggestion,
};
use rustc_middle::ty::{
self, AdtKind, GenericArgs, InferTy, IsSuggestable, Ty, TyCtxt, TypeFoldable, TypeFolder,
@ -110,7 +110,7 @@ impl<'a, 'tcx> CoroutineData<'a, 'tcx> {
fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) {
(
generics.tail_span_for_predicate_suggestion(),
format!("{} {}", generics.add_where_or_trailing_comma(), pred),
with_types_for_suggestion!(format!("{} {}", generics.add_where_or_trailing_comma(), pred)),
)
}
@ -136,7 +136,8 @@ pub fn suggest_restriction<'tcx, G: EmissionGuarantee>(
if hir_generics.where_clause_span.from_expansion()
|| hir_generics.where_clause_span.desugaring_kind().is_some()
|| projection.is_some_and(|projection| {
tcx.is_impl_trait_in_trait(projection.def_id)
(tcx.is_impl_trait_in_trait(projection.def_id)
&& !tcx.features().return_type_notation())
|| tcx.lookup_stability(projection.def_id).is_some_and(|stab| stab.is_unstable())
})
{

View File

@ -15,6 +15,10 @@ note: required by a bound in `is_send`
|
LL | fn is_send(_: impl Send) {}
| ^^^^ required by this bound in `is_send`
help: consider further restricting the associated type
|
LL | >() where <T as Foo>::method(..): Send {
| ++++++++++++++++++++++++++++++++++
error: aborting due to 1 previous error

View File

@ -11,6 +11,10 @@ note: required by a bound in `needs_trait`
|
LL | fn needs_trait(_: impl Trait) {}
| ^^^^^ required by this bound in `needs_trait`
help: consider further restricting the associated type
|
LL | fn foo<T: Assoc>(t: T) where <T as Assoc>::method(..): Trait {
| +++++++++++++++++++++++++++++++++++++
error[E0277]: the trait bound `impl Sized { <T as Assoc>::method_with_lt(..) }: Trait` is not satisfied
--> $DIR/display.rs:16:17
@ -25,6 +29,10 @@ note: required by a bound in `needs_trait`
|
LL | fn needs_trait(_: impl Trait) {}
| ^^^^^ required by this bound in `needs_trait`
help: consider further restricting the associated type
|
LL | fn foo<T: Assoc>(t: T) where <T as Assoc>::method_with_lt(..): Trait {
| +++++++++++++++++++++++++++++++++++++++++++++
error[E0277]: the trait bound `impl Sized: Trait` is not satisfied
--> $DIR/display.rs:18:17

View File

@ -0,0 +1,15 @@
//@ run-rustfix
#![allow(unused)]
#![feature(return_type_notation)]
trait Foo {
fn missing() -> impl Sized;
}
impl Foo for () {
//~^ ERROR not all trait items implemented, missing: `missing`
fn missing() -> impl Sized { todo!() }
}
fn main() {}

View File

@ -0,0 +1,14 @@
//@ run-rustfix
#![allow(unused)]
#![feature(return_type_notation)]
trait Foo {
fn missing() -> impl Sized;
}
impl Foo for () {
//~^ ERROR not all trait items implemented, missing: `missing`
}
fn main() {}

View File

@ -0,0 +1,12 @@
error[E0046]: not all trait items implemented, missing: `missing`
--> $DIR/rendering.rs:10:1
|
LL | fn missing() -> impl Sized;
| --------------------------- `missing` from trait
...
LL | impl Foo for () {
| ^^^^^^^^^^^^^^^ missing `missing` in implementation
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0046`.