Skip to content

Commit d41d2a3

Browse files
committed
Port Inlay hints for name parameters
1 parent 96de70c commit d41d2a3

File tree

2 files changed

+230
-72
lines changed

2 files changed

+230
-72
lines changed

presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import scala.meta.pc.SymbolSearch
1717
import dotty.tools.dotc.ast.tpd.*
1818
import dotty.tools.dotc.core.Contexts.Context
1919
import dotty.tools.dotc.core.Flags
20+
import dotty.tools.dotc.core.Names.Name
2021
import dotty.tools.dotc.core.StdNames.*
2122
import dotty.tools.dotc.core.Symbols.*
2223
import dotty.tools.dotc.core.Types.*
@@ -29,6 +30,7 @@ import dotty.tools.dotc.util.Spans.Span
2930
import org.eclipse.lsp4j.InlayHint
3031
import org.eclipse.lsp4j.InlayHintKind
3132
import org.eclipse.{lsp4j as l}
33+
import dotty.tools.dotc.core.NameOps.fieldName
3234

3335
class PcInlayHintsProvider(
3436
driver: InteractiveDriver,
@@ -116,8 +118,8 @@ class PcInlayHintsProvider(
116118
InlayHintKind.Type,
117119
)
118120
.addDefinition(adjustedPos.start)
119-
case ByNameParameters(byNameParams) =>
120-
def adjustByNameParameterPos(pos: SourcePosition): SourcePosition =
121+
case NamedParameters(_) | ByNameParameters(_) =>
122+
def adjustBlockParameterPosOpt(pos: SourcePosition): Option[SourcePosition] =
121123
val adjusted = adjustPos(pos)
122124
val start = text.indexWhere(!_.isWhitespace, adjusted.start)
123125
val end = text.lastIndexWhere(!_.isWhitespace, adjusted.end - 1)
@@ -126,16 +128,51 @@ class PcInlayHintsProvider(
126128
val endsWithBrace = text.lift(end).contains('}')
127129

128130
if startsWithBrace && endsWithBrace then
129-
adjusted.withStart(start + 1)
131+
Some(adjusted.withStart(start + 1))
130132
else
131-
adjusted
133+
None
134+
135+
def adjustParameterPos(pos: SourcePosition): SourcePosition =
136+
adjustBlockParameterPosOpt(pos) match
137+
case Some(adjusted) => adjusted
138+
case None => adjustPos(pos)
139+
140+
def isNamedParameter(pos: SourcePosition): Boolean =
141+
val adjusted = adjustPos(pos)
142+
val start = text.indexWhere(!_.isWhitespace, adjusted.start)
143+
val end = text.lastIndexWhere(!_.isWhitespace, adjusted.end - 1)
144+
text.slice(start, end).contains('=')
145+
146+
val namedParams = NamedParameters.unapply(tree).getOrElse(Nil).collect {
147+
// We don't want to show parameter names for block parameters or named parameters
148+
case (name, pos) if adjustBlockParameterPosOpt(pos).isEmpty && !isNamedParameter(pos) => (name, pos)
149+
}
150+
val byNameParams = ByNameParameters.unapply(tree).getOrElse(Nil)
151+
152+
val namedAndByNameInlayHints =
153+
namedParams.collect {
154+
case (name, pos) if byNameParams.contains(pos) =>
155+
(name.toString() + " = => ", adjustParameterPos(pos))
156+
}
157+
158+
val namedInlayHints =
159+
namedParams.collect {
160+
case (name, pos) if !byNameParams.contains(pos) =>
161+
(name.toString() + " = ", adjustParameterPos(pos))
162+
}
163+
164+
val namedParamsPos = namedParams.map(_._2)
165+
val byNameInlayHints =
166+
byNameParams.collect {
167+
case pos if !namedParamsPos.contains(pos) =>
168+
("=> ", adjustParameterPos(pos))
169+
}
132170

133-
byNameParams.foldLeft(inlayHints) {
134-
case (ih, pos) =>
135-
val adjusted = adjustByNameParameterPos(pos)
171+
(namedAndByNameInlayHints ++ namedInlayHints ++ byNameInlayHints).foldLeft(inlayHints) {
172+
case (ih, (labelStr, pos)) =>
136173
ih.add(
137-
adjusted.startPos.toLsp,
138-
List(LabelPart("=> ")),
174+
pos.startPos.toLsp,
175+
List(LabelPart(labelStr)),
139176
InlayHintKind.Parameter
140177
)
141178
}
@@ -414,16 +451,11 @@ end InferredType
414451

415452
object ByNameParameters:
416453
def unapply(tree: Tree)(using params: InlayHintsParams, ctx: Context): Option[List[SourcePosition]] =
417-
def shouldSkipSelect(sel: Select) =
418-
isForComprehensionMethod(sel) || sel.symbol.name == nme.unapply
419-
420-
if (params.byNameParameters()){
454+
if (params.byNameParameters()) {
421455
tree match
422-
case Apply(TypeApply(sel: Select, _), _) if shouldSkipSelect(sel) =>
423-
None
424-
case Apply(sel: Select, _) if shouldSkipSelect(sel) =>
456+
case FlattenApplies(sel: Select, args) if SkipRules.shouldSkipSelect(sel, args) =>
425457
None
426-
case Apply(fun, args) =>
458+
case Apply(fun, args) if !SkipRules.shouldSkipInfix(fun, args) =>
427459
val funTp = fun.typeOpt.widenTermRefExpr
428460
val params = funTp.paramInfoss.flatten
429461
Some(
@@ -436,3 +468,51 @@ object ByNameParameters:
436468
case _ => None
437469
} else None
438470
end ByNameParameters
471+
472+
object NamedParameters:
473+
def unapply(tree: Tree)(using params: InlayHintsParams, ctx: Context): Option[List[(Name, SourcePosition)]] =
474+
def isRealApply(tree: Tree) =
475+
!tree.symbol.isOneOf(Flags.GivenOrImplicit) && !tree.span.isZeroExtent
476+
if (params.namedParameters()){
477+
tree match
478+
case FlattenApplies(sel: Select, args) if SkipRules.shouldSkipSelect(sel, args) =>
479+
None
480+
case Apply(fun, args) if isRealApply(fun) && !SkipRules.shouldSkipInfix(fun, args) =>
481+
val funTp = fun.typeOpt.widenTermRefExpr
482+
val params = funTp.paramNamess.flatten
483+
Some(
484+
args
485+
.zip(params)
486+
.collect {
487+
case (arg, param) if !arg.span.isZeroExtent => (param.fieldName, arg.sourcePos)
488+
}
489+
)
490+
case _ => None
491+
} else None
492+
end NamedParameters
493+
494+
private object SkipRules:
495+
def shouldSkipSelect(sel: Select, args: List[Tree])(using Context): Boolean =
496+
isForComprehensionMethod(sel) || sel.symbol.name == nme.unapply || sel.isInfix
497+
498+
def shouldSkipInfix(fun: Tree, args: List[Tree])(using Context): Boolean =
499+
val source = fun.source
500+
if args.isEmpty then false
501+
else
502+
val isInfixExtensionMethod =
503+
!(fun.span.end until args.head.span.start)
504+
.map(source.apply)
505+
.contains('.')
506+
isInfixExtensionMethod && fun.symbol.is(Flags.ExtensionMethod)
507+
508+
private object FlattenApplies:
509+
/*
510+
* Extractor that strips away a (possibly nested) chain of `Apply` / `TypeApply`
511+
* wrappers and returns the underlying function tree.
512+
*/
513+
def unapply(tree: Tree): (Tree, List[Tree]) =
514+
tree match
515+
case Apply(FlattenApplies(fun, argss), args) => (fun, argss ++: args)
516+
case TypeApply(FlattenApplies(fun, argss), args) => (fun, argss ++: args)
517+
case t => (t, Nil)
518+
end FlattenApplies

0 commit comments

Comments
 (0)