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/src/main/dotty/tools/pc/completions/CompletionAffix.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala index 4ed58c773a7c..78f9f5f68bfb 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionAffix.scala @@ -56,6 +56,7 @@ case class CompletionAffix( private def loopPrefix(prefixes: List[PrefixKind]): String = prefixes match case PrefixKind.New :: tail => "new " + loopPrefix(tail) + case PrefixKind.Using :: tail => "using " + loopPrefix(tail) case _ => "" /** @@ -87,7 +88,7 @@ enum SuffixKind: case Brace, Bracket, Template, NoSuffix enum PrefixKind: - case New + case New, Using type Suffix = Affix[SuffixKind] type Prefix = Affix[PrefixKind] diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 6b42af504a6e..959efb963c27 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -74,7 +74,15 @@ class Completions( case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe => false case sel :: (funSel @ Select(fun, name)) :: (appl: GenericApply) :: _ if appl.fun == funSel && sel == fun => false - case _ => true) + case _ => true) && + (adjustedPath match + /* In case of `class X derives TC@@` we shouldn't add `[]` + */ + case Ident(_) :: (templ: untpd.DerivingTemplate) :: _ => + val pos = completionPos.toSourcePosition + !templ.derived.exists(_.sourcePos.contains(pos)) + case _ => true + ) private lazy val isNew: Boolean = Completion.isInNewContext(adjustedPath) @@ -193,13 +201,24 @@ class Completions( ) end isAbstractType - private def findSuffix(symbol: Symbol): CompletionAffix = + private def findSuffix(symbol: Symbol, adjustedPath: List[untpd.Tree]): CompletionAffix = CompletionAffix.empty .chain { suffix => // for [] suffix if shouldAddSuffix && symbol.info.typeParams.nonEmpty then suffix.withNewSuffixSnippet(Affix(SuffixKind.Bracket)) else suffix } + .chain{ suffix => + adjustedPath match + case (ident: Ident) :: (app@Apply(_, List(arg))) :: _ => + app.symbol.info match + case mt@MethodType(termNames) if app.symbol.paramSymss.last.exists(_.is(Given)) && + !text.substring(app.fun.span.start, arg.span.end).nn.contains("using") => + suffix.withNewPrefix(Affix(PrefixKind.Using)) + case _ => suffix + case _ => suffix + + } .chain { suffix => // for () suffix if shouldAddSuffix && symbol.is(Flags.Method) then val paramss = getParams(symbol) @@ -273,7 +292,7 @@ class Completions( val existsApply = extraMethodDenots.exists(_.symbol.name == nme.apply) extraMethodDenots.map { methodDenot => - val suffix = findSuffix(methodDenot.symbol) + val suffix = findSuffix(methodDenot.symbol, adjustedPath) val affix = if methodDenot.symbol.isConstructor && existsApply then adjustedPath match case (select @ Select(qual, _)) :: _ => @@ -295,7 +314,7 @@ class Completions( if skipOriginalDenot then extraCompletionValues else - val suffix = findSuffix(denot.symbol) + val suffix = findSuffix(denot.symbol, adjustedPath) val name = undoBacktick(label) val denotCompletionValue = toCompletionValue(name, denot, suffix) denotCompletionValue :: extraCompletionValues 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/completion/CompletionArgSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala index 044b5456d31d..910044485896 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala @@ -238,6 +238,65 @@ class CompletionArgSuite extends BaseCompletionSuite: "" ) + @Test def `using` = + checkEdit( + s"""|def hello(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello(st@@) + |""".stripMargin, + s"""|def hello(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello(using str) + |""".stripMargin, + assertSingleItem = false) + + @Test def `using2` = + checkEdit( + s"""|def hello(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello(using st@@) + |""".stripMargin, + s"""|def hello(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello(using str) + |""".stripMargin, + assertSingleItem = false) + + @Test def `using3` = + checkEdit( + s"""|def hello(using String, Int): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | val int = 4 + | hello(str, in@@) + |""".stripMargin, + s"""|def hello(using String, Int): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | val int = 4 + | hello(str, int) + |""".stripMargin, + assertSingleItem = false) + + @Test def `using4` = + checkEdit( + s"""|def hello(name: String)(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello("name")(str@@) + |""".stripMargin, + s"""|def hello(name: String)(using String): Unit = ??? + |@main def main1(): Unit = + | val str = "hello" + | hello("name")(using str) + |""".stripMargin, + assertSingleItem = false + ) + @Test def `default-args` = check( s"""|object Main { diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 13f373f068c4..84dc4cf232b2 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -2241,3 +2241,11 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, "asTerm: Term" ) + + @Test def `derives-no-square-brackets` = + check( + """ + |case class Miau(y: Int) derives Ordering, CanEqu@@ + |""".stripMargin, + "CanEqual scala" + ) 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 80e63806689c..32651ce13ab9 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",