Merge pull request #21569 from ChayimFriedman2/parens-multi-impl-trait

fix: Cover more cases where we need parentheses in `&(impl Trait1 + Trait2)`
This commit is contained in:
Lukas Wirth 2026-02-05 11:18:56 +00:00 committed by GitHub
commit d2a00da092
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 119 additions and 109 deletions

View File

@ -12,7 +12,6 @@ use either::Either;
use hir_def::{
FindPathConfig, GenericDefId, GenericParamId, HasModule, LocalFieldId, Lookup, ModuleDefId,
ModuleId, TraitId,
db::DefDatabase,
expr_store::{ExpressionStore, path::Path},
find_path::{self, PrefixKind},
hir::generics::{TypeOrConstParamData, TypeParamProvenance, WherePredicate},
@ -100,6 +99,9 @@ pub struct HirFormatter<'a, 'db> {
display_kind: DisplayKind,
display_target: DisplayTarget,
bounds_formatting_ctx: BoundsFormattingCtx<'db>,
/// Whether formatting `impl Trait1 + Trait2` or `dyn Trait1 + Trait2` needs parentheses around it,
/// for example when formatting `&(impl Trait1 + Trait2)`.
trait_bounds_need_parens: bool,
}
// FIXME: To consider, ref and dyn trait lifetimes can be omitted if they are `'_`, path args should
@ -331,6 +333,7 @@ pub trait HirDisplay<'db> {
show_container_bounds: false,
display_lifetimes: DisplayLifetime::OnlyNamedOrStatic,
bounds_formatting_ctx: Default::default(),
trait_bounds_need_parens: false,
}) {
Ok(()) => {}
Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
@ -566,6 +569,7 @@ impl<'db, T: HirDisplay<'db>> HirDisplayWrapper<'_, 'db, T> {
show_container_bounds: self.show_container_bounds,
display_lifetimes: self.display_lifetimes,
bounds_formatting_ctx: Default::default(),
trait_bounds_need_parens: false,
})
}
@ -612,7 +616,11 @@ impl<'db, T: HirDisplay<'db> + Internable> HirDisplay<'db> for Interned<T> {
}
}
fn write_projection<'db>(f: &mut HirFormatter<'_, 'db>, alias: &AliasTy<'db>) -> Result {
fn write_projection<'db>(
f: &mut HirFormatter<'_, 'db>,
alias: &AliasTy<'db>,
needs_parens_if_multi: bool,
) -> Result {
if f.should_truncate() {
return write!(f, "{TYPE_HINT_TRUNCATION}");
}
@ -650,6 +658,7 @@ fn write_projection<'db>(f: &mut HirFormatter<'_, 'db>, alias: &AliasTy<'db>) ->
Either::Left(Ty::new_alias(f.interner, AliasTyKind::Projection, *alias)),
&bounds,
SizedByDefault::NotSized,
needs_parens_if_multi,
)
});
}
@ -1056,7 +1065,7 @@ impl<'db> HirDisplay<'db> for Ty<'db> {
return write!(f, "{TYPE_HINT_TRUNCATION}");
}
use TyKind;
let trait_bounds_need_parens = mem::replace(&mut f.trait_bounds_need_parens, false);
match self.kind() {
TyKind::Never => write!(f, "!")?,
TyKind::Str => write!(f, "str")?,
@ -1077,103 +1086,34 @@ impl<'db> HirDisplay<'db> for Ty<'db> {
c.hir_fmt(f)?;
write!(f, "]")?;
}
kind @ (TyKind::RawPtr(t, m) | TyKind::Ref(_, t, m)) => {
if let TyKind::Ref(l, _, _) = kind {
f.write_char('&')?;
if f.render_region(l) {
l.hir_fmt(f)?;
f.write_char(' ')?;
}
TyKind::Ref(l, t, m) => {
f.write_char('&')?;
if f.render_region(l) {
l.hir_fmt(f)?;
f.write_char(' ')?;
}
match m {
rustc_ast_ir::Mutability::Not => (),
rustc_ast_ir::Mutability::Mut => f.write_str("mut ")?,
}
f.trait_bounds_need_parens = true;
t.hir_fmt(f)?;
f.trait_bounds_need_parens = false;
}
TyKind::RawPtr(t, m) => {
write!(
f,
"*{}",
match m {
rustc_ast_ir::Mutability::Not => (),
rustc_ast_ir::Mutability::Mut => f.write_str("mut ")?,
rustc_ast_ir::Mutability::Not => "const ",
rustc_ast_ir::Mutability::Mut => "mut ",
}
} else {
write!(
f,
"*{}",
match m {
rustc_ast_ir::Mutability::Not => "const ",
rustc_ast_ir::Mutability::Mut => "mut ",
}
)?;
}
)?;
// FIXME: all this just to decide whether to use parentheses...
let (preds_to_print, has_impl_fn_pred) = match t.kind() {
TyKind::Dynamic(bounds, region) => {
let contains_impl_fn =
bounds.iter().any(|bound| match bound.skip_binder() {
ExistentialPredicate::Trait(trait_ref) => {
let trait_ = trait_ref.def_id.0;
fn_traits(f.lang_items()).any(|it| it == trait_)
}
_ => false,
});
let render_lifetime = f.render_region(region);
(bounds.len() + render_lifetime as usize, contains_impl_fn)
}
TyKind::Alias(AliasTyKind::Opaque, ty) => {
let opaque_ty_id = match ty.def_id {
SolverDefId::InternedOpaqueTyId(id) => id,
_ => unreachable!(),
};
let impl_trait_id = db.lookup_intern_impl_trait_id(opaque_ty_id);
if let ImplTraitId::ReturnTypeImplTrait(func, _) = impl_trait_id {
let data = impl_trait_id.predicates(db);
let bounds =
|| data.iter_instantiated_copied(f.interner, ty.args.as_slice());
let mut len = bounds().count();
// Don't count Sized but count when it absent
// (i.e. when explicit ?Sized bound is set).
let default_sized = SizedByDefault::Sized { anchor: func.krate(db) };
let sized_bounds = bounds()
.filter(|b| {
matches!(
b.kind().skip_binder(),
ClauseKind::Trait(trait_ref)
if default_sized.is_sized_trait(
trait_ref.def_id().0,
db,
),
)
})
.count();
match sized_bounds {
0 => len += 1,
_ => {
len = len.saturating_sub(sized_bounds);
}
}
let contains_impl_fn = bounds().any(|bound| {
if let ClauseKind::Trait(trait_ref) = bound.kind().skip_binder() {
let trait_ = trait_ref.def_id().0;
fn_traits(f.lang_items()).any(|it| it == trait_)
} else {
false
}
});
(len, contains_impl_fn)
} else {
(0, false)
}
}
_ => (0, false),
};
if has_impl_fn_pred && preds_to_print <= 2 {
return t.hir_fmt(f);
}
if preds_to_print > 1 {
write!(f, "(")?;
t.hir_fmt(f)?;
write!(f, ")")?;
} else {
t.hir_fmt(f)?;
}
f.trait_bounds_need_parens = true;
t.hir_fmt(f)?;
f.trait_bounds_need_parens = false;
}
TyKind::Tuple(tys) => {
if tys.len() == 1 {
@ -1328,7 +1268,9 @@ impl<'db> HirDisplay<'db> for Ty<'db> {
hir_fmt_generics(f, parameters.as_slice(), Some(def.def_id().0.into()), None)?;
}
TyKind::Alias(AliasTyKind::Projection, alias_ty) => write_projection(f, &alias_ty)?,
TyKind::Alias(AliasTyKind::Projection, alias_ty) => {
write_projection(f, &alias_ty, trait_bounds_need_parens)?
}
TyKind::Foreign(alias) => {
let type_alias = db.type_alias_signature(alias.0);
f.start_location_link(alias.0.into());
@ -1363,6 +1305,7 @@ impl<'db> HirDisplay<'db> for Ty<'db> {
Either::Left(*self),
&bounds,
SizedByDefault::Sized { anchor: krate },
trait_bounds_need_parens,
)?;
}
TyKind::Closure(id, substs) => {
@ -1525,6 +1468,7 @@ impl<'db> HirDisplay<'db> for Ty<'db> {
Either::Left(*self),
&bounds,
SizedByDefault::Sized { anchor: krate },
trait_bounds_need_parens,
)?;
}
},
@ -1567,6 +1511,7 @@ impl<'db> HirDisplay<'db> for Ty<'db> {
Either::Left(*self),
&bounds_to_display,
SizedByDefault::NotSized,
trait_bounds_need_parens,
)?;
}
TyKind::Error(_) => {
@ -1806,11 +1751,11 @@ pub enum SizedByDefault {
}
impl SizedByDefault {
fn is_sized_trait(self, trait_: TraitId, db: &dyn DefDatabase) -> bool {
fn is_sized_trait(self, trait_: TraitId, interner: DbInterner<'_>) -> bool {
match self {
Self::NotSized => false,
Self::Sized { anchor } => {
let sized_trait = hir_def::lang_item::lang_items(db, anchor).Sized;
Self::Sized { .. } => {
let sized_trait = interner.lang_items().Sized;
Some(trait_) == sized_trait
}
}
@ -1823,16 +1768,62 @@ pub fn write_bounds_like_dyn_trait_with_prefix<'db>(
this: Either<Ty<'db>, Region<'db>>,
predicates: &[Clause<'db>],
default_sized: SizedByDefault,
needs_parens_if_multi: bool,
) -> Result {
let needs_parens =
needs_parens_if_multi && trait_bounds_need_parens(f, this, predicates, default_sized);
if needs_parens {
write!(f, "(")?;
}
write!(f, "{prefix}")?;
if !predicates.is_empty()
|| predicates.is_empty() && matches!(default_sized, SizedByDefault::Sized { .. })
{
write!(f, " ")?;
write_bounds_like_dyn_trait(f, this, predicates, default_sized)
} else {
Ok(())
write_bounds_like_dyn_trait(f, this, predicates, default_sized)?;
}
if needs_parens {
write!(f, ")")?;
}
Ok(())
}
fn trait_bounds_need_parens<'db>(
f: &mut HirFormatter<'_, 'db>,
this: Either<Ty<'db>, Region<'db>>,
predicates: &[Clause<'db>],
default_sized: SizedByDefault,
) -> bool {
// This needs to be kept in sync with `write_bounds_like_dyn_trait()`.
let mut distinct_bounds = 0usize;
let mut is_sized = false;
for p in predicates {
match p.kind().skip_binder() {
ClauseKind::Trait(trait_ref) => {
let trait_ = trait_ref.def_id().0;
if default_sized.is_sized_trait(trait_, f.interner) {
is_sized = true;
if matches!(default_sized, SizedByDefault::Sized { .. }) {
// Don't print +Sized, but rather +?Sized if absent.
continue;
}
}
distinct_bounds += 1;
}
ClauseKind::TypeOutlives(to) if Either::Left(to.0) == this => distinct_bounds += 1,
ClauseKind::RegionOutlives(lo) if Either::Right(lo.0) == this => distinct_bounds += 1,
_ => {}
}
}
if let SizedByDefault::Sized { .. } = default_sized
&& !is_sized
{
distinct_bounds += 1;
}
distinct_bounds > 1
}
fn write_bounds_like_dyn_trait<'db>(
@ -1855,7 +1846,7 @@ fn write_bounds_like_dyn_trait<'db>(
match p.kind().skip_binder() {
ClauseKind::Trait(trait_ref) => {
let trait_ = trait_ref.def_id().0;
if default_sized.is_sized_trait(trait_, f.db) {
if default_sized.is_sized_trait(trait_, f.interner) {
is_sized = true;
if matches!(default_sized, SizedByDefault::Sized { .. }) {
// Don't print +Sized, but rather +?Sized if absent.

View File

@ -608,7 +608,7 @@ trait Foo {}
fn test(f: impl Foo, g: &(impl Foo + ?Sized)) {
let _: &dyn Foo = &f;
let _: &dyn Foo = g;
//^ expected &'? (dyn Foo + 'static), got &'? impl Foo + ?Sized
//^ expected &'? (dyn Foo + 'static), got &'? (impl Foo + ?Sized)
}
"#,
);

View File

@ -111,7 +111,7 @@ fn test(
b;
//^ impl Foo
c;
//^ &impl Foo + ?Sized
//^ &(impl Foo + ?Sized)
d;
//^ S<impl Foo>
ref_any;
@ -192,7 +192,7 @@ fn test(
b;
//^ fn(impl Foo) -> impl Foo
c;
} //^ fn(&impl Foo + ?Sized) -> &impl Foo + ?Sized
} //^ fn(&(impl Foo + ?Sized)) -> &(impl Foo + ?Sized)
"#,
);
}

View File

@ -4821,7 +4821,7 @@ fn allowed3(baz: impl Baz<Assoc = Qux<impl Foo>>) {}
431..433 '{}': ()
447..450 'baz': impl Baz<Assoc = impl Foo>
480..482 '{}': ()
500..503 'baz': impl Baz<Assoc = &'a impl Foo + 'a>
500..503 'baz': impl Baz<Assoc = &'a (impl Foo + 'a)>
544..546 '{}': ()
560..563 'baz': impl Baz<Assoc = Qux<impl Foo>>
598..600 '{}': ()

View File

@ -587,6 +587,7 @@ impl<'db> HirDisplay<'db> for TypeParam {
Either::Left(ty),
&predicates,
SizedByDefault::Sized { anchor: krate },
false,
);
}
},
@ -614,6 +615,7 @@ impl<'db> HirDisplay<'db> for TypeParam {
Either::Left(ty),
&predicates,
default_sized,
false,
)?;
}
Ok(())

View File

@ -1382,4 +1382,21 @@ fn f<'a>() {
"#]],
);
}
#[test]
fn ref_multi_trait_impl_trait() {
check_with_config(
InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
r#"
//- minicore: sized
trait Eq {}
trait Ord {}
fn foo(argument: &(impl Eq + Ord)) {
let x = argument;
// ^ &(impl Eq + Ord)
}
"#,
);
}
}