Unnamed repository; edit this file 'description' to name the repository.
Fix stack overflow on projection display
As it turns out, displaying projections can always cause an infinite loop, even when not recursing into a type param's predicates. This does regress the previous test of infinite loops a bit, which is unfortunate. Maybe we should have a more fine-grained tracking?
Chayim Refael Friedman 2 weeks ago
parent 8534be9 · commit 539c556
-rw-r--r--crates/hir-ty/src/display.rs192
-rw-r--r--crates/ide-completion/src/tests/expression.rs56
-rw-r--r--crates/ide/src/inlay_hints/bind_pat.rs2
3 files changed, 151 insertions, 99 deletions
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index 6bc55bc0e4..41cbb33651 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -10,8 +10,8 @@ use std::{
use base_db::{Crate, FxIndexMap};
use either::Either;
use hir_def::{
- ExpressionStoreOwnerId, FindPathConfig, GenericDefId, GenericParamId, HasModule, LocalFieldId,
- Lookup, ModuleDefId, ModuleId, TraitId,
+ ExpressionStoreOwnerId, FindPathConfig, GenericDefId, GenericParamId, HasModule,
+ ItemContainerId, LocalFieldId, Lookup, ModuleDefId, ModuleId, TraitId,
expr_store::{ExpressionStore, path::Path},
find_path::{self, PrefixKind},
hir::{
@@ -134,7 +134,15 @@ pub struct HirFormatter<'a, 'db> {
display_lifetimes: DisplayLifetime,
display_kind: DisplayKind,
display_target: DisplayTarget,
- bounds_formatting_ctx: BoundsFormattingCtx<'db>,
+ /// We can have recursive bounds like the following case:
+ /// ```ignore
+ /// where
+ /// T: Foo,
+ /// T::FooAssoc: Baz<<T::FooAssoc as Bar>::BarAssoc> + Bar
+ /// ```
+ /// So, record the projection types met while formatting bounds and
+ /// prevent recursing into their bounds to avoid infinite loops.
+ currently_formatting_bounds: FxHashSet<AliasTy<'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,
@@ -154,34 +162,6 @@ pub enum DisplayLifetime {
Never,
}
-#[derive(Default)]
-enum BoundsFormattingCtx<'db> {
- Entered {
- /// We can have recursive bounds like the following case:
- /// ```ignore
- /// where
- /// T: Foo,
- /// T::FooAssoc: Baz<<T::FooAssoc as Bar>::BarAssoc> + Bar
- /// ```
- /// So, record the projection types met while formatting bounds and
- //. prevent recursing into their bounds to avoid infinite loops.
- projection_tys_met: FxHashSet<AliasTy<'db>>,
- },
- #[default]
- Exited,
-}
-
-impl<'db> BoundsFormattingCtx<'db> {
- fn contains(&self, proj: &AliasTy<'db>) -> bool {
- match self {
- BoundsFormattingCtx::Entered { projection_tys_met } => {
- projection_tys_met.contains(proj)
- }
- BoundsFormattingCtx::Exited => false,
- }
- }
-}
-
impl<'db> HirFormatter<'_, 'db> {
pub fn start_location_link(&mut self, location: ModuleDefId) {
self.fmt.start_location_link(location);
@@ -195,26 +175,42 @@ impl<'db> HirFormatter<'_, 'db> {
self.fmt.end_location_link();
}
- fn format_bounds_with<T, F: FnOnce(&mut Self) -> T>(
+ fn format_bounds_with<F: FnOnce(&mut Self) -> Result>(
&mut self,
target: AliasTy<'db>,
format_bounds: F,
- ) -> T {
- match self.bounds_formatting_ctx {
- BoundsFormattingCtx::Entered { ref mut projection_tys_met } => {
- projection_tys_met.insert(target);
- format_bounds(self)
- }
- BoundsFormattingCtx::Exited => {
- let mut projection_tys_met = FxHashSet::default();
- projection_tys_met.insert(target);
- self.bounds_formatting_ctx = BoundsFormattingCtx::Entered { projection_tys_met };
- let res = format_bounds(self);
- // Since we want to prevent only the infinite recursions in bounds formatting
- // and do not want to skip formatting of other separate bounds, clear context
- // when exiting the formatting of outermost bounds
- self.bounds_formatting_ctx = BoundsFormattingCtx::Exited;
- res
+ ) -> Result {
+ if self.currently_formatting_bounds.insert(target) {
+ let result = format_bounds(self);
+ self.currently_formatting_bounds.remove(&target);
+ result
+ } else {
+ if self.display_kind.is_source_code() {
+ Err(HirDisplayError::DisplaySourceCodeError(DisplaySourceCodeError::Cycle))
+ } else {
+ match target.kind {
+ AliasTyKind::Projection { def_id } => {
+ let def_id = def_id.expect_type_alias();
+ let ItemContainerId::TraitId(trait_) = def_id.loc(self.db).container else {
+ panic!("expected an assoc type");
+ };
+ let trait_name = &TraitSignature::of(self.db, trait_).name;
+ let assoc_type_name = &TypeAliasSignature::of(self.db, def_id).name;
+ write!(
+ self,
+ "<… as {}>::{}",
+ trait_name.display(self.db, self.edition()),
+ assoc_type_name.display(self.db, self.edition()),
+ )?;
+ if target.args.len() > 1 {
+ self.write_str("<…>")?;
+ }
+ Ok(())
+ }
+ AliasTyKind::Inherent { .. }
+ | AliasTyKind::Opaque { .. }
+ | AliasTyKind::Free { .. } => self.write_str("…"),
+ }
}
}
}
@@ -368,7 +364,7 @@ pub trait HirDisplay<'db> {
display_kind: DisplayKind::SourceCode { target_module_id: module_id, allow_opaque },
show_container_bounds: false,
display_lifetimes: DisplayLifetime::OnlyNamedOrStatic,
- bounds_formatting_ctx: Default::default(),
+ currently_formatting_bounds: Default::default(),
trait_bounds_need_parens: false,
}) {
Ok(()) => {}
@@ -544,6 +540,7 @@ pub enum DisplaySourceCodeError {
PathNotFound,
Coroutine,
OpaqueType,
+ Cycle,
}
pub enum HirDisplayError {
@@ -604,7 +601,7 @@ impl<'db, T: HirDisplay<'db>> HirDisplayWrapper<'_, 'db, T> {
closure_style: self.closure_style,
show_container_bounds: self.show_container_bounds,
display_lifetimes: self.display_lifetimes,
- bounds_formatting_ctx: Default::default(),
+ currently_formatting_bounds: Default::default(),
trait_bounds_need_parens: false,
})
}
@@ -657,62 +654,61 @@ fn write_projection<'db>(
alias: &AliasTy<'db>,
needs_parens_if_multi: bool,
) -> Result {
- if f.should_truncate() {
- return write!(f, "{TYPE_HINT_TRUNCATION}");
- }
- let trait_ref = alias.trait_ref(f.interner);
- let self_ty = trait_ref.self_ty();
-
- // if we are projection on a type parameter, check if the projection target has bounds
- // itself, if so, we render them directly as `impl Bound` instead of the less useful
- // `<Param as Trait>::Assoc`
- if !f.display_kind.is_source_code()
- && let TyKind::Param(param) = self_ty.kind()
- && !f.bounds_formatting_ctx.contains(alias)
- {
- // FIXME: We shouldn't use `param.id`, it should be removed. We should know the
- // `GenericDefId` from the formatted type (store it inside the `HirFormatter`).
- let bounds = GenericPredicates::query_all(f.db, param.id.parent())
- .iter_identity()
- .filter(|wc| {
- let ty = match wc.kind().skip_binder() {
- ClauseKind::Trait(tr) => tr.self_ty(),
- ClauseKind::TypeOutlives(t) => t.0,
- _ => return false,
- };
- let TyKind::Alias(a) = ty.kind() else {
- return false;
- };
- a == *alias
- })
- .collect::<Vec<_>>();
- if !bounds.is_empty() {
- return f.format_bounds_with(*alias, |f| {
- write_bounds_like_dyn_trait_with_prefix(
+ f.format_bounds_with(*alias, |f| {
+ if f.should_truncate() {
+ return write!(f, "{TYPE_HINT_TRUNCATION}");
+ }
+ let trait_ref = alias.trait_ref(f.interner);
+ let self_ty = trait_ref.self_ty();
+
+ // if we are projection on a type parameter, check if the projection target has bounds
+ // itself, if so, we render them directly as `impl Bound` instead of the less useful
+ // `<Param as Trait>::Assoc`
+ if !f.display_kind.is_source_code()
+ && let TyKind::Param(param) = self_ty.kind()
+ {
+ // FIXME: We shouldn't use `param.id`, it should be removed. We should know the
+ // `GenericDefId` from the formatted type (store it inside the `HirFormatter`).
+ let bounds = GenericPredicates::query_all(f.db, param.id.parent())
+ .iter_identity()
+ .filter(|wc| {
+ let ty = match wc.kind().skip_binder() {
+ ClauseKind::Trait(tr) => tr.self_ty(),
+ ClauseKind::TypeOutlives(t) => t.0,
+ _ => return false,
+ };
+ let TyKind::Alias(a) = ty.kind() else {
+ return false;
+ };
+ a == *alias
+ })
+ .collect::<Vec<_>>();
+ if !bounds.is_empty() {
+ return write_bounds_like_dyn_trait_with_prefix(
f,
"impl",
Either::Left(Ty::new_alias(f.interner, *alias)),
&bounds,
SizedByDefault::NotSized,
needs_parens_if_multi,
- )
- });
+ );
+ }
}
- }
- write!(f, "<")?;
- self_ty.hir_fmt(f)?;
- write!(f, " as ")?;
- trait_ref.hir_fmt(f)?;
- write!(
- f,
- ">::{}",
- TypeAliasSignature::of(f.db, alias.kind.def_id().expect_type_alias())
- .name
- .display(f.db, f.edition())
- )?;
- let proj_params = &alias.args.as_slice()[trait_ref.args.len()..];
- hir_fmt_generics(f, proj_params, None, None)
+ write!(f, "<")?;
+ self_ty.hir_fmt(f)?;
+ write!(f, " as ")?;
+ trait_ref.hir_fmt(f)?;
+ write!(
+ f,
+ ">::{}",
+ TypeAliasSignature::of(f.db, alias.kind.def_id().expect_type_alias())
+ .name
+ .display(f.db, f.edition())
+ )?;
+ let proj_params = &alias.args.as_slice()[trait_ref.args.len()..];
+ hir_fmt_generics(f, proj_params, None, None)
+ })
}
impl<'db> HirDisplay<'db> for GenericArg<'db> {
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index f6da07a6f2..91852bb0f5 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -3829,3 +3829,59 @@ fn baz(v: impl Bar) {
"#]],
);
}
+
+#[test]
+fn regression_21697() {
+ check(
+ r#"
+trait SuperTrait {
+ type AssocTy;
+}
+
+trait SubTrait<T = <Self as SuperTrait>::AssocTy>: SuperTrait {}
+
+fn tryme(param: impl SubTrait) {
+ param$0
+}
+ "#,
+ expect![[r#"
+ fn tryme(…) fn(impl SubTrait<<impl SubTrait<<… as SuperTrait>::AssocTy> + ?Sized as SuperTrait>::AssocTy> + ?Sized)
+ lc param impl SubTrait<<impl SubTrait<<… as SuperTrait>::AssocTy> + ?Sized as SuperTrait>::AssocTy> + ?Sized
+ tt SubTrait
+ tt SuperTrait
+ bt u32 u32
+ kw async
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw impl for
+ kw let
+ kw letm
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ );
+}
diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs
index f194bb183e..57b723cbd8 100644
--- a/crates/ide/src/inlay_hints/bind_pat.rs
+++ b/crates/ide/src/inlay_hints/bind_pat.rs
@@ -1336,7 +1336,7 @@ where
{
fn f(&self) {
let x = self.field.foo();
- //^ impl Baz<<<T as Foo>::Assoc as Bar>::Target> + Bar
+ //^ impl Baz<<<… as Foo>::Assoc as Bar>::Target> + Bar
}
}
"#,