From da885696899b409d780e23a00dda1b8ee58a076f Mon Sep 17 00:00:00 2001 From: Jakub Ciesluk <323892@uwr.edu.pl> Date: Tue, 12 Mar 2024 11:12:53 +0100 Subject: [PATCH] improvement: Add separate option for decorations for evidence params --- .../main/scala/bench/InlayHintsBench.scala | 1 + .../meta/internal/metals/Compilers.scala | 1 + .../internal/metals/UserConfiguration.scala | 14 ++++ .../java/scala/meta/pc/InlayHintsParams.java | 4 + .../metals/CompilerInlayHintsParams.scala | 3 +- .../internal/pc/PcInlayHintsProvider.scala | 61 +++++++++------ .../internal/pc/PcInlayHintsProvider.scala | 47 +++++++----- .../scala/tests/BaseInlayHintsSuite.scala | 16 ++-- .../test/scala/tests/pc/InlayHintsSuite.scala | 76 +++++++++++++++++-- .../tests/BaseInlayHintsExpectSuite.scala | 1 + .../scala/tests/BaseInlayHintsLspSuite.scala | 3 +- 11 files changed, 175 insertions(+), 52 deletions(-) diff --git a/metals-bench/src/main/scala/bench/InlayHintsBench.scala b/metals-bench/src/main/scala/bench/InlayHintsBench.scala index f4eb48696bc..469ca792823 100644 --- a/metals-bench/src/main/scala/bench/InlayHintsBench.scala +++ b/metals-bench/src/main/scala/bench/InlayHintsBench.scala @@ -94,6 +94,7 @@ class InlayHintsBench extends PcBenchmark { true, true, true, + true, ) pc.inlayHints(pcParams).get().asScala.toList } diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index 8ed669053dc..be5c310f5f9 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -616,6 +616,7 @@ class Compilers( userConfig().showInferredType.contains("true"), implicitParameters = userConfig().showImplicitArguments, implicitConversions = userConfig().showImplicitConversionsAndClasses, + contextBounds = userConfig().showEvidenceParams, ) pc diff --git a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala index 62ba6667c71..835f9079bf7 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala @@ -44,6 +44,7 @@ case class UserConfiguration( showInferredType: Option[String] = None, showImplicitArguments: Boolean = false, showImplicitConversionsAndClasses: Boolean = false, + showEvidenceParams: Boolean = false, enableStripMarginOnTypeFormatting: Boolean = true, enableIndentOnPaste: Boolean = false, enableSemanticHighlighting: Boolean = true, @@ -260,6 +261,16 @@ object UserConfiguration { |shown in the hover. |""".stripMargin, ), + UserConfigurationOption( + "show-evidence-params", + "false", + "false", + "Should display context bounds evidence parameters at usage sites", + """|When this option is enabled, each place where a context bound is used has it + |displayed either as additional decorations if they are supported by the editor or + |shown in the hover. + |""".stripMargin, + ), UserConfigurationOption( "enable-semantic-highlighting", "true", @@ -538,6 +549,8 @@ object UserConfiguration { getBooleanKey("show-implicit-arguments").getOrElse(false) val showImplicitConversionsAndClasses = getBooleanKey("show-implicit-conversions-and-classes").getOrElse(false) + val showEvidenceParams = + getBooleanKey("show-evidence-params").getOrElse(false) val enableStripMarginOnTypeFormatting = getBooleanKey("enable-strip-margin-on-type-formatting").getOrElse(true) val enableIndentOnPaste = @@ -609,6 +622,7 @@ object UserConfiguration { showInferredType, showImplicitArguments, showImplicitConversionsAndClasses, + showEvidenceParams, enableStripMarginOnTypeFormatting, enableIndentOnPaste, enableSemanticHighlighting, diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/InlayHintsParams.java b/mtags-interfaces/src/main/java/scala/meta/pc/InlayHintsParams.java index 3a6391afa5d..1c6cd157acf 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/InlayHintsParams.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/InlayHintsParams.java @@ -29,4 +29,8 @@ public interface InlayHintsParams extends RangeParams { */ boolean implicitConversions(); + default boolean contextBounds() { + return false; + } + } \ No newline at end of file diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala index aba471e5cf4..3e63df9db61 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala @@ -10,7 +10,8 @@ case class CompilerInlayHintsParams( inferredTypes: Boolean, typeParameters: Boolean, implicitParameters: Boolean, - implicitConversions: Boolean + implicitConversions: Boolean, + override val contextBounds: Boolean ) extends InlayHintsParams { override def uri(): URI = rangeParams.uri override def text(): String = rangeParams.text diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcInlayHintsProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcInlayHintsProvider.scala index 2bbdc58c63b..efe4ddbc059 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcInlayHintsProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcInlayHintsProvider.scala @@ -57,17 +57,24 @@ final class PcInlayHintsProvider( LabelPart(")") :: Nil, InlayHintKind.Parameter ) - case ImplicitParameters(symbols, pos, allImplicit) - if params.implicitParameters() => - val labelParts = symbols.map(s => List(labelPart(s, s.decodedName))) - val label = - if (allImplicit) labelParts.separated("(", ", ", ")") - else labelParts.separated(", ") - inlayHints.add( - adjustPos(pos).focusEnd.toLsp, - label, - InlayHintKind.Parameter - ) + case ImplicitParameters(args, pos, allImplicit) + if params.implicitParameters() || params.contextBounds() => + val symbols = args.collect { + case (arg, false) if params.implicitParameters() => arg + case (arg, true) if params.contextBounds() => arg + } + if (symbols.isEmpty) inlayHints + else { + val labelParts = symbols.map(s => List(labelPart(s, s.decodedName))) + val label = + if (allImplicit) labelParts.separated("(", ", ", ")") + else labelParts.separated(", ") + inlayHints.add( + adjustPos(pos).focusEnd.toLsp, + label, + InlayHintKind.Parameter + ) + } case ValueOf(label, pos) if params.implicitParameters() => inlayHints.add( adjustPos(pos).focusEnd.toLsp, @@ -160,23 +167,33 @@ final class PcInlayHintsProvider( fun.pos.isOffset && fun.symbol != null && fun.symbol.isImplicit } object ImplicitParameters { - def unapply(tree: Tree): Option[(List[Symbol], Position, Boolean)] = + def unapply( + tree: Tree + ): Option[(List[(Symbol, Boolean)], Position, Boolean)] = tree match { - case Apply(_, args) + case Apply(fun, args) if args.exists(isSyntheticArg) && !tree.pos.isOffset => - val (implicitArgs, providedArgs) = args.partition(isSyntheticArg) - val allImplicit = providedArgs.isEmpty - val pos = providedArgs.lastOption.fold(tree.pos)(_.pos) - Some( - implicitArgs.map(_.symbol), - pos, - allImplicit - ) + fun.tpe.widen match { + case mt: MethodType if mt.params.nonEmpty => + val (implicitArgs0, providedArgs) = + args.zip(mt.params).partition { case (arg, _) => + isSyntheticArg(arg) + } + val implicitArgs = implicitArgs0.map { case (arg, sym) => + (arg.symbol, isContextBoundParam(sym)) + } + val allImplicit = providedArgs.isEmpty + val pos = providedArgs.lastOption.fold(tree.pos)(_._1.pos) + Some((implicitArgs, pos, allImplicit)) + case _ => None + } + case _ => None } - private def isSyntheticArg(arg: Tree): Boolean = arg.pos.isOffset && arg.symbol != null && arg.symbol.isImplicit + private def isContextBoundParam(sym: Symbol) = + sym.name.toString.startsWith(nme.EVIDENCE_PARAM_PREFIX) } object ValueOf { diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcInlayHintsProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcInlayHintsProvider.scala index 93354e9712b..e270d457a4a 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcInlayHintsProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcInlayHintsProvider.scala @@ -13,6 +13,7 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* @@ -73,17 +74,23 @@ class PcInlayHintsProvider( LabelPart(")") :: Nil, InlayHintKind.Parameter, ) - case ImplicitParameters(symbols, pos, allImplicit) - if params.implicitParameters() => - val labelParts = symbols.map(s => List(labelPart(s, s.decodedName))) - val label = - if allImplicit then labelParts.separated("(using ", ", ", ")") - else labelParts.separated(", ") - inlayHints.add( - adjustPos(pos).toLsp, - label, - InlayHintKind.Parameter, - ) + case ImplicitParameters(args, pos, allImplicit) + if params.implicitParameters() || params.contextBounds() => + val symbols = args.collect { + case (arg, false) if params.implicitParameters() => arg + case (arg, true) if params.contextBounds() => arg + } + if symbols.isEmpty then inlayHints + else + val labelParts = symbols.map(s => List(labelPart(s, s.decodedName))) + val label = + if allImplicit then labelParts.separated("(using ", ", ", ")") + else labelParts.separated(", ") + inlayHints.add( + adjustPos(pos).toLsp, + label, + InlayHintKind.Parameter, + ) case ValueOf(label, pos) if params.implicitParameters() => inlayHints.add( adjustPos(pos).toLsp, @@ -214,19 +221,25 @@ object ImplicitParameters: tree match case Apply(fun, args) if args.exists(isSyntheticArg) && !tree.sourcePos.span.isZeroExtent => - val (implicitArgs, providedArgs) = args.partition(isSyntheticArg) - val allImplicit = providedArgs.isEmpty || providedArgs.forall { - case Ident(name) => name == nme.MISSING - case _ => false + fun.typeOpt.widen.paramNamess.headOption.map {paramNames => + val (implicitArgs0, providedArgs) = args.zip(paramNames).partition((arg, _) => isSyntheticArg(arg)) + val pos = implicitArgs0.head._1.sourcePos + val implicitArgs = implicitArgs0.map((arg, name) => (arg.symbol, isContextBoundParam(name))) + val allImplicit = providedArgs.isEmpty || providedArgs.forall { + case (Ident(name), _) => name == nme.MISSING + case _ => false + } + (implicitArgs, pos, allImplicit) } - val pos = implicitArgs.head.sourcePos - Some(implicitArgs.map(_.symbol), pos, allImplicit) case _ => None private def isSyntheticArg(tree: Tree)(using Context) = tree match case tree: Ident => tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit) case _ => false + private def isContextBoundParam(name: Name) = + // In 3.1.3 NameKinds.ContextBoundParamName.separator is not available + name.toString.startsWith("evidence$") end ImplicitParameters object ValueOf: diff --git a/tests/cross/src/main/scala/tests/BaseInlayHintsSuite.scala b/tests/cross/src/main/scala/tests/BaseInlayHintsSuite.scala index ae0bc630fd7..1366b79c724 100644 --- a/tests/cross/src/main/scala/tests/BaseInlayHintsSuite.scala +++ b/tests/cross/src/main/scala/tests/BaseInlayHintsSuite.scala @@ -15,7 +15,12 @@ class BaseInlayHintsSuite extends BasePCSuite { name: TestOptions, base: String, expected: String, - compat: Map[String, String] = Map.empty + compat: Map[String, String] = Map.empty, + showInferredType: Boolean = true, + showTypeArguments: Boolean = true, + showImplicitArguments: Boolean = true, + showImplicitConversions: Boolean = true, + showContextBounds: Boolean = true )(implicit location: Location): Unit = test(name) { def pkgWrap(text: String) = @@ -31,10 +36,11 @@ class BaseInlayHintsSuite extends BasePCSuite { ) val pcParams = CompilerInlayHintsParams( rangeParams, - true, - true, - true, - true + showInferredType, + showTypeArguments, + showImplicitArguments, + showImplicitConversions, + showContextBounds ) val inlayHints = presentationCompiler diff --git a/tests/cross/src/test/scala/tests/pc/InlayHintsSuite.scala b/tests/cross/src/test/scala/tests/pc/InlayHintsSuite.scala index 7118bcff537..d11c797c158 100644 --- a/tests/cross/src/test/scala/tests/pc/InlayHintsSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/InlayHintsSuite.scala @@ -848,7 +848,25 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |""".stripMargin ) - // TODO: Add a separate option for hints for context bounds + check( + "context-bounds".tag(IgnoreScala2.and(IgnoreForScala3CompilerPC)), + """|package example + |object O { + | given Int = 1 + | def test[T: Ordering](x: T)(using Int) = ??? + | test(1) + |} + |""".stripMargin, + """|package example + |object O { + | given Int = 1 + | def test[T: Ordering](x: T)(using Int)/*: Nothing<>*/ = ??? + | test/*[Int<>]*/(1)/*(using given_Int<<(2:8)>>)*/ + |} + |""".stripMargin, + showContextBounds = false + ) + check( "context-bounds1".tag(IgnoreScala2.and(IgnoreForScala3CompilerPC)), """|package example @@ -892,7 +910,24 @@ class InlayHintsSuite extends BaseInlayHintsSuite { ) check( - "context-bounds3".tag(IgnoreScala2.and(IgnoreForScala3CompilerPC)), + "context-bounds3".tag(IgnoreForScala3CompilerPC), + """|package example + |object O { + | def test[T: Ordering](x: T) = ??? + | test(1) + |} + |""".stripMargin, + """|package example + |object O { + | def test[T: Ordering](x: T)/*: Nothing<>*/ = ??? + | test/*[Int<>]*/(1) + |} + |""".stripMargin, + showContextBounds = false + ) + + check( + "context-bounds4".tag(IgnoreScala2.and(IgnoreForScala3CompilerPC)), """|package example |object O { | def test[T: Ordering](x: T)(using Int) = ??? @@ -908,7 +943,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { ) check( - "context-bounds4".tag(IgnoreForScala3CompilerPC), + "context-bounds5".tag(IgnoreForScala3CompilerPC), """|package example |object O { | implicit val i: Int = 123 @@ -920,7 +955,7 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |object O { | implicit val i: Int = 123 | def test[T: Ordering](x: T)(implicit v: Int)/*: Nothing<>*/ = ??? - | test/*[Int<>]*/(1)/*(Int<>, i<<(2:15)>>)*/ + | test/*[Int<>]*/(1)/*(Int<>)*/ |} |""".stripMargin, compat = Map( @@ -928,9 +963,38 @@ class InlayHintsSuite extends BaseInlayHintsSuite { |object O { | implicit val i: Int = 123 | def test[T: Ordering](x: T)(implicit v: Int)/*: Nothing<>*/ = ??? - | test/*[Int<>]*/(1)/*(using Int<>, i<<(2:15)>>)*/ + | test/*[Int<>]*/(1)/*(using Int<>)*/ |} |""".stripMargin - ) + ), + showImplicitArguments = false + ) + + check( + "context-bounds6".tag(IgnoreForScala3CompilerPC), + """|package example + |object O { + | implicit val i: Int = 123 + | def test[T: Ordering](x: T)(y: T) = ??? + | test(1)(2) + |} + |""".stripMargin, + """|package example + |object O { + | implicit val i: Int = 123 + | def test[T: Ordering](x: T)(y: T)/*: Nothing<>*/ = ??? + | test/*[Int<>]*/(1)(2)/*(Int<>)*/ + |} + |""".stripMargin, + compat = Map( + "3" -> """|package example + |object O { + | implicit val i: Int = 123 + | def test[T: Ordering](x: T)(y: T)/*: Nothing<>*/ = ??? + | test/*[Int<>]*/(1)(2)/*(using Int<>)*/ + |} + |""".stripMargin + ), + showImplicitArguments = false ) } diff --git a/tests/unit/src/main/scala/tests/BaseInlayHintsExpectSuite.scala b/tests/unit/src/main/scala/tests/BaseInlayHintsExpectSuite.scala index 73c548544fa..74fbd88082d 100644 --- a/tests/unit/src/main/scala/tests/BaseInlayHintsExpectSuite.scala +++ b/tests/unit/src/main/scala/tests/BaseInlayHintsExpectSuite.scala @@ -38,6 +38,7 @@ abstract class BaseInlayHintsExpectSuite( true, true, true, + true, ) val inlayHints = compiler.inlayHints(pcParams).get().asScala.toList diff --git a/tests/unit/src/main/scala/tests/BaseInlayHintsLspSuite.scala b/tests/unit/src/main/scala/tests/BaseInlayHintsLspSuite.scala index 9522ec570d4..9d5531b29de 100644 --- a/tests/unit/src/main/scala/tests/BaseInlayHintsLspSuite.scala +++ b/tests/unit/src/main/scala/tests/BaseInlayHintsLspSuite.scala @@ -23,7 +23,8 @@ abstract class BaseInlayHintsLspSuite(name: String, scalaVersion: String) .getOrElse("""{ | "show-implicit-arguments": true, | "show-implicit-conversions-and-classes": true, - | "show-inferred-type": "true" + | "show-inferred-type": "true", + | "show-evidence-params": true |} |""".stripMargin) val fileName = "Main.scala"