diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index f4e3bc8c9c10..40a351f2354d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -54,7 +54,7 @@ class PcInlayHintsProvider( val pos = driver.sourcePosition(params) def provide(): List[InlayHint] = - val deepFolder = DeepFolder[InlayHints](collectDecorations) + val deepFolder = PcCollector.DeepFolderWithParent[InlayHints](collectDecorations) Interactive .pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx) .headOption @@ -68,11 +68,23 @@ class PcInlayHintsProvider( def collectDecorations( inlayHints: InlayHints, tree: Tree, + parent: Option[Tree] ): InlayHints = + // XRay hints are not mutually exclusive with other hints, so they must be matched separately + val firstPassHints = (tree, parent) match { + case XRayModeHint(tpe, pos) => + inlayHints.addToBlock( + adjustPos(pos).toLsp, + LabelPart(": ") :: toLabelParts(tpe, pos), + InlayHintKind.Type + ) + case _ => inlayHints + } + tree match case ImplicitConversion(symbol, range) => val adjusted = adjustPos(range) - inlayHints + firstPassHints .add( adjusted.startPos.toLsp, labelPart(symbol, symbol.decodedName) :: LabelPart("(") :: Nil, @@ -84,17 +96,17 @@ class PcInlayHintsProvider( InlayHintKind.Parameter, ) case ImplicitParameters(trees, pos) => - inlayHints.add( + firstPassHints.add( adjustPos(pos).toLsp, ImplicitParameters.partsFromImplicitArgs(trees).map((label, maybeSymbol) => maybeSymbol match case Some(symbol) => labelPart(symbol, label) case None => LabelPart(label) ), - InlayHintKind.Parameter + InlayHintKind.Parameter, ) case ValueOf(label, pos) => - inlayHints.add( + firstPassHints.add( adjustPos(pos).toLsp, LabelPart("(") :: LabelPart(label) :: List(LabelPart(")")), InlayHintKind.Parameter, @@ -102,7 +114,7 @@ class PcInlayHintsProvider( case TypeParameters(tpes, pos, sel) if !syntheticTupleApply(sel) => val label = tpes.map(toLabelParts(_, pos)).separated("[", ", ", "]") - inlayHints.add( + firstPassHints.add( adjustPos(pos).endPos.toLsp, label, InlayHintKind.Type, @@ -110,9 +122,9 @@ class PcInlayHintsProvider( case InferredType(tpe, pos, defTree) if !isErrorTpe(tpe) => val adjustedPos = adjustPos(pos).endPos - if inlayHints.containsDef(adjustedPos.start) then inlayHints + if firstPassHints.containsDef(adjustedPos.start) then firstPassHints else - inlayHints + firstPassHints .add( adjustedPos.toLsp, LabelPart(": ") :: toLabelParts(tpe, pos), @@ -138,7 +150,7 @@ class PcInlayHintsProvider( pos.withStart(pos.start + 1) - args.foldLeft(inlayHints) { + args.foldLeft(firstPassHints) { case (ih, (name, pos0, isByName)) => val pos = adjustPos(pos0) val isBlock = isBlockParam(pos) @@ -158,7 +170,7 @@ class PcInlayHintsProvider( ) else ih } - case _ => inlayHints + case _ => firstPassHints private def toLabelParts( tpe: Type, @@ -491,3 +503,55 @@ object Parameters: case _ => None else None end Parameters + +object XRayModeHint: + def unapply(trees: (Tree, Option[Tree]))(using params: InlayHintsParams, ctx: Context): Option[(Type, SourcePosition)] = + if params.hintsXRayMode() then + val (tree, parent) = trees + val isParentApply = parent match + case Some(_: Apply) => true + case _ => false + val isParentOnSameLine = parent match + case Some(sel: Select) if sel.isForComprehensionMethod => false + case Some(par) if par.sourcePos.exists && par.sourcePos.line == tree.sourcePos.line => true + case _ => false + + tree match + /* + anotherTree + .innerSelect() + */ + case a @ Apply(inner, _) + if inner.sourcePos.exists && !isParentOnSameLine && !isParentApply && + endsInSimpleSelect(a) && isEndOfLine(tree.sourcePos) => + Some((a.tpe.widen.deepDealiasAndSimplify, tree.sourcePos)) + /* + innerTree + .select + */ + case select @ Select(innerTree, _) + if innerTree.sourcePos.exists && !isParentOnSameLine && !isParentApply && + isEndOfLine(tree.sourcePos) => + Some((select.tpe.widen.deepDealiasAndSimplify, tree.sourcePos)) + case _ => None + else None + + @tailrec + private def endsInSimpleSelect(ap: Tree)(using ctx: Context): Boolean = + ap match + case Apply(sel: Select, _) => + sel.name != nme.apply && !isInfix(sel) + case Apply(TypeApply(sel: Select, _), _) => + sel.name != nme.apply && !isInfix(sel) + case Apply(innerTree @ Apply(_, _), _) => + endsInSimpleSelect(innerTree) + case _ => false + + private def isEndOfLine(pos: SourcePosition): Boolean = + if pos.exists then + val source = pos.source + val end = pos.end + end >= source.length || source(end) == '\n' || source(end) == '\r' + else false + +end XRayModeHint diff --git a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala index 32c6ad26c6a5..431e4908013a 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala @@ -34,6 +34,7 @@ class BaseInlayHintsSuite extends BasePCSuite { inferredTypes = true, typeParameters = true, implicitParameters = true, + hintsXRayMode = true, byNameParameters = true, implicitConversions = true, namedParameters = true, diff --git a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala index 1152e2928ad6..f91e0e2deb15 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/InlayHintsSuite.scala @@ -1300,4 +1300,338 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin ) + @Test def `xray-single-chain-same-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar + |val thing2: Foo = foo.foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar + |val thing2: Foo = foo.foo() + |} + |""".stripMargin + ) + + @Test def `xray-multi-chain-same-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar.bar + |val thing2: Foo = foo.foo().foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar.bar.bar + |val thing2: Foo = foo.foo().foo() + |} + |""".stripMargin + ) + + @Test def `xray-single-chain-new-line` = + check( + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar + | .bar + |val thing2: Foo = foo + | .foo() + |} + |""".stripMargin, + """|object Main{ + | trait Bar { + | def bar: Bar + | } + | + | trait Foo { + | def foo(): Foo + | } + | + |val bar: Bar = ??? + |val foo: Foo = ??? + | + |val thing1: Bar = bar + | .bar + |val thing2: Foo = foo + | .foo() + |} + |""".stripMargin + ) + + @Test def `xray-simple-chain` = + check( + """|object Main{ + | trait Foo { + | def bar: Bar + | } + | + | trait Bar { + | def foo(): Foo + | } + | + |val foo: Foo = ??? + | + |val thingy: Bar = foo + | .bar + | .foo() + | .bar + |} + |""".stripMargin, + """|object Main{ + | trait Foo { + | def bar: Bar + | } + | + | trait Bar { + | def foo(): Foo + | } + | + |val foo: Foo = ??? + | + |val thingy: Bar = foo + | .bar/* : Bar<<(6:8)>>*/ + | .foo()/*: Foo<<(2:8)>>*/ + | .bar/* : Bar<<(6:8)>>*/ + |} + |""".stripMargin + ) + + @Test def `xray-long-chain` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello", + | "World" + | ) + | .stringListify( + | "Hello", + | "World" + | ) + | .intify + | .intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-long-chain-same-line` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello", + | "World" + | ) + | .stringListify( + | "Hello", + | "World" + | ) + | .intify.intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String*): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/* : Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello", + | "World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify.intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-tikka-masala-curried` = + check( + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String)(s2: String): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify + | .stringListify( + | "Hello" + | )( + | "World" + | ) + | .stringListify( + | "Hello" + | )( + | "World" + | ) + | .intify + | .intify + |} + |""".stripMargin, + """|object Main{ + | trait Foo[F] { + | def intify: Foo[Int] + | def stringListify(s: String)(s2: String): Foo[String] + | } + | + |val foo: Foo[String] = ??? + | + |val thingy: Foo[Int] = foo + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .stringListify( + | /*s = */"Hello" + | )( + | /*s2 = */"World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .stringListify( + | /*s = */"Hello" + | )( + | /*s2 = */"World" + | )/* : Foo<<(2:8)>>[String<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + | .intify/*: Foo<<(2:8)>>[Int<>]*/ + |} + |""".stripMargin + ) + + @Test def `xray-for-comprehension` = + check( + """|object Main{ + |trait Foo[A]{ + | def flatMap[B](f: A => Foo[B]): Foo[B] + | def map[B](f: A => B): Foo[B] + | def bar(s: String): Foo[A] + |} + |val foo1: Foo[String] = ??? + |val foo2: Foo[Int] = ??? + |val result = for { + | foo <- foo1 + | bar <- foo2 + | .bar(s = foo) + | .bar(s = foo) + | .bar(s = foo) + |} yield bar + |} + |""".stripMargin, + """|object Main{ + |trait Foo[A]{ + | def flatMap[B](f: A => Foo[B]): Foo[B] + | def map[B](f: A => B): Foo[B] + | def bar(s: String): Foo[A] + |} + |val foo1: Foo[String] = ??? + |val foo2: Foo[Int] = ??? + |val result/*: Foo<<(2:6)>>[Int<>]*/ = for { + | foo <- foo1 + | bar <- foo2 + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + | .bar(s = foo)/*: Foo<<(2:6)>>[Int<>]*/ + |} yield bar + |} + |""".stripMargin + ) + } diff --git a/project/Build.scala b/project/Build.scala index 7bab975f9de6..9850c1aa686b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1228,7 +1228,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.5.3" + val mtagsVersion = "1.6.2" Seq( libraryDependencies ++= Seq( "org.lz4" % "lz4-java" % "1.8.0",