Skip to content

Commit d944b61

Browse files
authored
Port Inlay hints for name parameters (#23375)
Add inlay hints for name parameters. Porting scalameta/metals#7400
2 parents 4000759 + dc14b81 commit d944b61

File tree

2 files changed

+211
-88
lines changed

2 files changed

+211
-88
lines changed

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

Lines changed: 85 additions & 39 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,44 @@ 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)
130-
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-
)
134+
startsWithBrace && endsWithBrace
135+
136+
def adjustBlockParamPos(pos: SourcePosition): SourcePosition =
137+
pos.withStart(pos.start + 1)
138+
139+
140+
args.foldLeft(inlayHints) {
141+
case (ih, (name, pos0, isByName)) =>
142+
val pos = adjustPos(pos0)
143+
val isBlock = isBlockParam(pos)
144+
val namedLabel =
145+
if params.namedParameters() && !isInfixFun && !isBlock && !isNamedParam(pos) then s"${name} = " else ""
146+
val byNameLabel =
147+
if params.byNameParameters() && isByName && (!isInfixFun || isBlock) then "=> " else ""
148+
149+
val labelStr = s"${namedLabel}${byNameLabel}"
150+
val hintPos = if isBlock then adjustBlockParamPos(pos) else pos
151+
152+
if labelStr.nonEmpty then
153+
ih.add(
154+
hintPos.startPos.toLsp,
155+
List(LabelPart(labelStr)),
156+
InlayHintKind.Parameter,
157+
)
158+
else ih
141159
}
142160
case _ => inlayHints
143161

@@ -412,27 +430,55 @@ object InferredType:
412430

413431
end InferredType
414432

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

420-
if (params.byNameParameters()){
460+
if (params.namedParameters() || params.byNameParameters()) then
421461
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-
)
462+
case Apply(fun, args) if isRealApply(fun) =>
463+
val underlyingFun = getUnderlyingFun(fun)
464+
if shouldSkipFun(underlyingFun) then
465+
None
466+
else
467+
val funTp = fun.typeOpt.widenTermRefExpr
468+
val paramNames = funTp.paramNamess.flatten
469+
val paramInfos = funTp.paramInfoss.flatten
470+
Some(
471+
// Check if the function is an infix function or the underlying function is an infix function
472+
isInfixFun(fun, args) || underlyingFun.isInfix,
473+
(
474+
args
475+
.zip(paramNames)
476+
.zip(paramInfos)
477+
.collect {
478+
case ((arg, paramName), paramInfo) if !arg.span.isZeroExtent => (paramName.fieldName, arg.sourcePos, paramInfo.isByName)
479+
}
480+
)
481+
)
436482
case _ => None
437-
} else None
438-
end ByNameParameters
483+
else None
484+
end Parameters

0 commit comments

Comments
 (0)