Skip to content

Commit e217b3e

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

File tree

2 files changed

+216
-87
lines changed

2 files changed

+216
-87
lines changed

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

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ 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.NameOps.fieldName
21+
import dotty.tools.dotc.core.Names.Name
2022
import dotty.tools.dotc.core.StdNames.*
2123
import dotty.tools.dotc.core.Symbols.*
2224
import dotty.tools.dotc.core.Types.*
@@ -116,28 +118,50 @@ class PcInlayHintsProvider(
116118
InlayHintKind.Type,
117119
)
118120
.addDefinition(adjustedPos.start)
119-
case ByNameParameters(byNameParams) =>
120-
def adjustByNameParameterPos(pos: SourcePosition): SourcePosition =
121-
val adjusted = adjustPos(pos)
122-
val start = text.indexWhere(!_.isWhitespace, adjusted.start)
123-
val end = text.lastIndexWhere(!_.isWhitespace, adjusted.end - 1)
121+
case Parameters(isInfixFun, args) =>
122+
def isNamedParam(pos: SourcePosition): Boolean =
123+
val start = text.indexWhere(!_.isWhitespace, pos.start)
124+
val end = text.lastIndexWhere(!_.isWhitespace, pos.end - 1)
124125

126+
text.slice(start, end).contains('=')
127+
128+
def isBlockParam(pos: SourcePosition): Boolean =
129+
val start = text.indexWhere(!_.isWhitespace, pos.start)
130+
val end = text.lastIndexWhere(!_.isWhitespace, pos.end - 1)
125131
val startsWithBrace = text.lift(start).contains('{')
126132
val endsWithBrace = text.lift(end).contains('}')
127133

128-
if startsWithBrace && endsWithBrace then
129-
adjusted.withStart(start + 1)
134+
startsWithBrace && endsWithBrace
135+
136+
def adjustBlockParamPos(pos: SourcePosition): SourcePosition =
137+
if isBlockParam(pos) then
138+
pos.withStart(pos.start + 1)
130139
else
131-
adjusted
132-
133-
byNameParams.foldLeft(inlayHints) {
134-
case (ih, pos) =>
135-
val adjusted = adjustByNameParameterPos(pos)
136-
ih.add(
137-
adjusted.startPos.toLsp,
138-
List(LabelPart("=> ")),
139-
InlayHintKind.Parameter
140-
)
140+
pos
141+
142+
143+
args.foldLeft(inlayHints) {
144+
case (ih, (name, pos0, isByName)) =>
145+
val pos = adjustPos(pos0)
146+
val isBlock = isBlockParam(pos)
147+
val namedLabel =
148+
if params.namedParameters() && !isInfixFun && !isBlock && !isNamedParam(pos)
149+
then s"${name} = "
150+
else ""
151+
val byNameLabel =
152+
if params.byNameParameters() && isByName && (!isInfixFun || isBlock)
153+
then "=> "
154+
else ""
155+
156+
val labelStr = s"${namedLabel}${byNameLabel}"
157+
158+
if labelStr.nonEmpty then
159+
ih.add(
160+
adjustBlockParamPos(pos).startPos.toLsp,
161+
List(LabelPart(labelStr)),
162+
InlayHintKind.Parameter,
163+
)
164+
else ih
141165
}
142166
case _ => inlayHints
143167

@@ -412,27 +436,55 @@ object InferredType:
412436

413437
end InferredType
414438

415-
object ByNameParameters:
416-
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
439+
object Parameters:
440+
def unapply(tree: Tree)(using params: InlayHintsParams, ctx: Context): Option[(Boolean, List[(Name, SourcePosition, Boolean)])] =
441+
def shouldSkipFun(fun: Tree)(using Context): Boolean =
442+
fun match
443+
case sel: Select => isForComprehensionMethod(sel) || sel.symbol.name == nme.unapply
444+
case _ => false
445+
446+
def isInfixFun(fun: Tree, args: List[Tree])(using Context): Boolean =
447+
val isInfixSelect = fun match
448+
case Select(sel, _) => sel.isInfix
449+
case _ => false
450+
val source = fun.source
451+
if args.isEmpty then isInfixSelect
452+
else
453+
(!(fun.span.end until args.head.span.start)
454+
.map(source.apply)
455+
.contains('.') && fun.symbol.is(Flags.ExtensionMethod)) || isInfixSelect
456+
457+
def isRealApply(tree: Tree) =
458+
!tree.symbol.isOneOf(Flags.GivenOrImplicit) && !tree.span.isZeroExtent
459+
460+
def getUnderlyingFun(tree: Tree): Tree =
461+
tree match
462+
case Apply(fun, _) => getUnderlyingFun(fun)
463+
case TypeApply(fun, _) => getUnderlyingFun(fun)
464+
case t => t
419465

420-
if (params.byNameParameters()){
466+
if (params.namedParameters() || params.byNameParameters()) then
421467
tree match
422-
case Apply(TypeApply(sel: Select, _), _) if shouldSkipSelect(sel) =>
423-
None
424-
case Apply(sel: Select, _) if shouldSkipSelect(sel) =>
425-
None
426-
case Apply(fun, args) =>
427-
val funTp = fun.typeOpt.widenTermRefExpr
428-
val params = funTp.paramInfoss.flatten
429-
Some(
430-
args
431-
.zip(params)
432-
.collect {
433-
case (tree, param) if param.isByName => tree.sourcePos
434-
}
435-
)
468+
case Apply(fun, args) if isRealApply(fun) =>
469+
val underlyingFun = getUnderlyingFun(fun)
470+
if shouldSkipFun(underlyingFun) then
471+
None
472+
else
473+
val funTp = fun.typeOpt.widenTermRefExpr
474+
val paramNames = funTp.paramNamess.flatten
475+
val paramInfos = funTp.paramInfoss.flatten
476+
Some(
477+
// Check if the function is an infix function or the underlying function is an infix function
478+
isInfixFun(fun, args) || underlyingFun.isInfix,
479+
(
480+
args
481+
.zip(paramNames)
482+
.zip(paramInfos)
483+
.collect {
484+
case ((arg, paramName), paramInfo) if !arg.span.isZeroExtent => (paramName.fieldName, arg.sourcePos, paramInfo.isByName)
485+
}
486+
)
487+
)
436488
case _ => None
437-
} else None
438-
end ByNameParameters
489+
else None
490+
end Parameters

0 commit comments

Comments
 (0)