diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 43b428c3fa..4e77e8be36 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -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 { } } -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, 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, 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. diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs index 36630ab587..438699b409 100644 --- a/crates/hir-ty/src/tests/coercion.rs +++ b/crates/hir-ty/src/tests/coercion.rs @@ -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) } "#, ); diff --git a/crates/hir-ty/src/tests/display_source_code.rs b/crates/hir-ty/src/tests/display_source_code.rs index dc3869930d..37da7fc875 100644 --- a/crates/hir-ty/src/tests/display_source_code.rs +++ b/crates/hir-ty/src/tests/display_source_code.rs @@ -111,7 +111,7 @@ fn test( b; //^ impl Foo c; - //^ &impl Foo + ?Sized + //^ &(impl Foo + ?Sized) d; //^ S 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) "#, ); } diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 9c65305712..cdf7b40003 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -4821,7 +4821,7 @@ fn allowed3(baz: impl Baz>) {} 431..433 '{}': () 447..450 'baz': impl Baz 480..482 '{}': () - 500..503 'baz': impl Baz + 500..503 'baz': impl Baz 544..546 '{}': () 560..563 'baz': impl Baz> 598..600 '{}': () diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 1f9af564c3..b4440dfa18 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -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(()) diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs index c74e3104c1..caf7cc714d 100644 --- a/crates/ide/src/inlay_hints/bind_pat.rs +++ b/crates/ide/src/inlay_hints/bind_pat.rs @@ -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) +} + "#, + ); + } }