diff --git a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala index c55a8a0210be..375a75d0307f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala @@ -13,6 +13,7 @@ import scala.meta.pc.SymbolSearch import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Constants.* import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.* import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.Names.* import dotty.tools.dotc.core.StdNames.* @@ -221,12 +222,21 @@ object HoverProvider: findRefinement(parent) case _ => None - val refTpe = sel.typeOpt.widen.deepDealiasAndSimplify match - case r: RefinedType => Some(r) - case t: (TermRef | TypeProxy) => Some(t.termSymbol.info.deepDealiasAndSimplify) - case _ => None + def extractRefinements(t: Type): List[Type] = t match + case r: RefinedType => List(r) + case t: (TypeRef | AppliedType) => + // deepDealiasAndSimplify can succeed with no progress, so we have to avoid infinite loops + val t1 = t.deepDealiasAndSimplify + if t1 == t then Nil + else extractRefinements(t1) + case t: TermRef => extractRefinements(t.widen) + case t: TypeProxy => List(t.termSymbol.info.deepDealiasAndSimplify) + case AndType(l , r) => List(extractRefinements(l), extractRefinements(r)).flatten + case _ => Nil - refTpe.flatMap(findRefinement).asJava + val refTpe: List[Type] = extractRefinements(sel.typeOpt) + + refTpe.flatMap(findRefinement).headOption.asJava case _ => ju.Optional.empty().nn diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala index c483dc289b0e..60827f1e3590 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -851,3 +851,78 @@ class HoverTermSuite extends BaseHoverSuite: |""".stripMargin, "val thisIsAVeryLongName: Int".hover ) + + @Test def `intersection_of_selectable-1` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.a@@a + |""".stripMargin, + "val aa: Int".hover + ) + + @Test def `intersection_of_selectable-2` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val aa: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.a@@a + |""".stripMargin, + "val aa: Int & String".hover + ) + + @Test def `intersection_of_selectable-3` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type AB = A & B + | + |val ab: AB = Record().asInstanceOf[AB] + |val ab_a = ab.b@@b + |""".stripMargin, + "val bb: String".hover + ) + + @Test def `intersection_of_selectable-4` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type A = Record { val aa: Int } + |type B = Record { val bb: String } + |type C = Record { val cc: Float } + |type AB = A & B + |type ABC = AB & C + | + |val abc: ABC = Record().asInstanceOf[ABC] + |val abc_a = abc.a@@a + |""".stripMargin, + "val aa: Int".hover + ) + + @Test def `intersection_of_selectable-5` = + check( + """|class Record extends Selectable: + | def selectDynamic(name: String): Any = ??? + | + |type AL = List[Int] & Record { val aa: Int } + | + |val al: AL = ???.asInstanceOf[ABC] + |val al_a = al.a@@a + |""".stripMargin, + "val aa: Int".hover + )