From 448ea814ea7f8080d2c92ff0574a2c0ba0cf7145 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 | 71 +++++++++++------ .../internal/pc/PcInlayHintsProvider.scala | 59 +++++++++----- .../scala/tests/BaseInlayHintsSuite.scala | 16 ++-- .../test/scala/tests/pc/InlayHintsSuite.scala | 76 +++++++++++++++++-- .../tests/BaseInlayHintsExpectSuite.scala | 1 + .../scala/tests/BaseInlayHintsLspSuite.scala | 3 +- 11 files changed, 195 insertions(+), 54 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..05ae04e9689 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 Param(sym) if params.implicitParameters() => sym + case ContextBound(sym) if params.contextBounds() => sym + } + 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,25 +167,43 @@ 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[ImplicitParamSym], 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(ImplicitParamSym(_)) + 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 - } + } + sealed trait ImplicitParamSym + case class Param(sym: Symbol) extends ImplicitParamSym + case class ContextBound(sym: Symbol) extends ImplicitParamSym + object ImplicitParamSym { + def apply(argWithName: (Tree, Symbol)): ImplicitParamSym = { + val (arg, name) = argWithName + if (isContextBoundParam(name)) ContextBound(arg.symbol) + else Param(arg.symbol) + } + private def isContextBoundParam(sym: Symbol) = + sym.name.toString.startsWith(nme.EVIDENCE_PARAM_PREFIX) + } object ValueOf { def unapply(tree: Tree): Option[(String, Position)] = tree match { case Apply(ta: TypeApply, Apply(fun, _) :: _) 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..1d303e37639 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 Param(sym) if params.implicitParameters() => sym + case ContextBound(sym) if params.contextBounds() => sym + } + 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, @@ -210,17 +217,20 @@ object ImplicitConversion: end ImplicitConversion object ImplicitParameters: - def unapply(tree: Tree)(using Context) = + def unapply(tree: Tree)(using Context): Option[(List[ImplicitParamSym], SourcePosition, Boolean)] = 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 firstImplicitPos = implicitArgs0.head._1.sourcePos + val implicitArgs = implicitArgs0.map(ImplicitParamSym(_)) + val allImplicit = providedArgs.isEmpty || providedArgs.forall { + case (Ident(name), _) => name == nme.MISSING + case _ => false + } + (implicitArgs, firstImplicitPos, allImplicit) } - val pos = implicitArgs.head.sourcePos - Some(implicitArgs.map(_.symbol), pos, allImplicit) case _ => None private def isSyntheticArg(tree: Tree)(using Context) = tree match @@ -229,6 +239,19 @@ object ImplicitParameters: case _ => false end ImplicitParameters +sealed trait ImplicitParamSym extends Any +case class Param(sym: Symbol) extends AnyVal with ImplicitParamSym +case class ContextBound(sym: Symbol) extends AnyVal with ImplicitParamSym + +object ImplicitParamSym: + def apply(argWithName: (Tree, Name))(using Context) = + val (arg, name) = argWithName + if isContextBoundParam(name) then ContextBound(arg.symbol) + else Param(arg.symbol) + private def isContextBoundParam(name: Name) = + // In 3.1.3 NameKinds.ContextBoundParamName.separator is not available + name.toString.startsWith("evidence$") + object ValueOf: def unapply(tree: Tree)(using Context) = tree match 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"