Skip to content

Commit

Permalink
improvement: Add separate option for decorations for evidence params
Browse files Browse the repository at this point in the history
  • Loading branch information
jkciesluk committed Mar 12, 2024
1 parent d8475d9 commit d3a6581
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 53 deletions.
1 change: 1 addition & 0 deletions metals-bench/src/main/scala/bench/InlayHintsBench.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class InlayHintsBench extends PcBenchmark {
true,
true,
true,
true,
)
pc.inlayHints(pcParams).get().asScala.toList
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ class Compilers(
userConfig().showInferredType.contains("true"),
implicitParameters = userConfig().showImplicitArguments,
implicitConversions = userConfig().showImplicitConversionsAndClasses,
contextBounds = userConfig().showEvidenceParams,
)

pc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -609,6 +622,7 @@ object UserConfiguration {
showInferredType,
showImplicitArguments,
showImplicitConversionsAndClasses,
showEvidenceParams,
enableStripMarginOnTypeFormatting,
enableIndentOnPaste,
enableSemanticHighlighting,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ public interface InlayHintsParams extends RangeParams {
*/
boolean implicitConversions();

default boolean contextBounds() {
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -210,23 +217,29 @@ object ImplicitConversion:
end ImplicitConversion

object ImplicitParameters:
def unapply(tree: Tree)(using Context) =
def unapply(tree: Tree)(using Context): Option[(List[(Symbol, Boolean)], 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((arg, name) => (arg.symbol, isContextBoundParam(name)))
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
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:
Expand Down
16 changes: 11 additions & 5 deletions tests/cross/src/main/scala/tests/BaseInlayHintsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand All @@ -31,10 +36,11 @@ class BaseInlayHintsSuite extends BasePCSuite {
)
val pcParams = CompilerInlayHintsParams(
rangeParams,
true,
true,
true,
true
showInferredType,
showTypeArguments,
showImplicitArguments,
showImplicitConversions,
showContextBounds
)

val inlayHints = presentationCompiler
Expand Down
76 changes: 70 additions & 6 deletions tests/cross/src/test/scala/tests/pc/InlayHintsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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<<scala/Nothing#>>*/ = ???
| test/*[Int<<scala/Int#>>]*/(1)/*(using given_Int<<(2:8)>>)*/
|}
|""".stripMargin,
showContextBounds = false
)

check(
"context-bounds1".tag(IgnoreScala2.and(IgnoreForScala3CompilerPC)),
"""|package example
Expand Down Expand Up @@ -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<<scala/Nothing#>>*/ = ???
| test/*[Int<<scala/Int#>>]*/(1)
|}
|""".stripMargin,
showContextBounds = false
)

check(
"context-bounds4".tag(IgnoreScala2.and(IgnoreForScala3CompilerPC)),
"""|package example
|object O {
| def test[T: Ordering](x: T)(using Int) = ???
Expand All @@ -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
Expand All @@ -920,17 +955,46 @@ class InlayHintsSuite extends BaseInlayHintsSuite {
|object O {
| implicit val i: Int = 123
| def test[T: Ordering](x: T)(implicit v: Int)/*: Nothing<<scala/Nothing#>>*/ = ???
| test/*[Int<<scala/Int#>>]*/(1)/*(Int<<scala/math/Ordering.Int.>>, i<<(2:15)>>)*/
| test/*[Int<<scala/Int#>>]*/(1)/*(Int<<scala/math/Ordering.Int.>>)*/
|}
|""".stripMargin,
compat = Map(
"3" -> """|package example
|object O {
| implicit val i: Int = 123
| def test[T: Ordering](x: T)(implicit v: Int)/*: Nothing<<scala/Nothing#>>*/ = ???
| test/*[Int<<scala/Int#>>]*/(1)/*(using Int<<scala/math/Ordering.Int.>>, i<<(2:15)>>)*/
| test/*[Int<<scala/Int#>>]*/(1)/*(using Int<<scala/math/Ordering.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<<scala/Nothing#>>*/ = ???
| test/*[Int<<scala/Int#>>]*/(1)(2)/*(Int<<scala/math/Ordering.Int.>>)*/
|}
|""".stripMargin,
compat = Map(
"3" -> """|package example
|object O {
| implicit val i: Int = 123
| def test[T: Ordering](x: T)(y: T)/*: Nothing<<scala/Nothing#>>*/ = ???
| test/*[Int<<scala/Int#>>]*/(1)(2)/*(using Int<<scala/math/Ordering.Int.>>)*/
|}
|""".stripMargin
),
showImplicitArguments = false
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ abstract class BaseInlayHintsExpectSuite(
true,
true,
true,
true,
)
val inlayHints =
compiler.inlayHints(pcParams).get().asScala.toList
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/src/main/scala/tests/BaseInlayHintsLspSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit d3a6581

Please sign in to comment.