Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport "presentation-compiler: Add synthetic decorations" to LTS #20759

Merged
merged 1 commit into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 2 additions & 51 deletions presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ abstract class PcCollector[T](
val uri = params.uri().nn
val filePath = Paths.get(uri).nn
val sourceText = params.text().nn
val text = sourceText.toCharArray().nn
val source =
SourceFile.virtual(filePath.toString(), sourceText)
driver.run(uri, source)
Expand Down Expand Up @@ -70,45 +71,6 @@ abstract class PcCollector[T](
parent: Option[Tree]
)(tree: Tree| EndMarker, pos: SourcePosition, symbol: Option[Symbol]): T

/**
* @return (adjusted position, should strip backticks)
*/
def adjust(
pos1: SourcePosition,
forRename: Boolean = false
): (SourcePosition, Boolean) =
if !pos1.span.isCorrect then (pos1, false)
else
val pos0 =
val span = pos1.span
if span.exists && span.point > span.end then
pos1.withSpan(
span
.withStart(span.point)
.withEnd(span.point + (span.end - span.start))
)
else pos1

val pos =
if pos0.end > 0 && sourceText(pos0.end - 1) == ',' then
pos0.withEnd(pos0.end - 1)
else pos0
val isBackticked =
sourceText(pos.start) == '`' &&
pos.end > 0 &&
sourceText(pos.end - 1) == '`'
// when the old name contains backticks, the position is incorrect
val isOldNameBackticked = sourceText(pos.start) != '`' &&
pos.start > 0 &&
sourceText(pos.start - 1) == '`' &&
sourceText(pos.end) == '`'
if isBackticked && forRename then
(pos.withStart(pos.start + 1).withEnd(pos.end - 1), true)
else if isOldNameBackticked then
(pos.withStart(pos.start - 1).withEnd(pos.end + 1), false)
else (pos, false)
end adjust

def symbolAlternatives(sym: Symbol) =
def member(parent: Symbol) = parent.info.member(sym.name).symbol
def primaryConstructorTypeParam(owner: Symbol) =
Expand Down Expand Up @@ -447,7 +409,7 @@ abstract class PcCollector[T](
*/
case sel: Select
if sel.span.isCorrect && filter(sel) &&
!isForComprehensionMethod(sel) =>
!sel.isForComprehensionMethod =>
occurrences + collect(
sel,
pos.withSpan(selectNameSpan(sel))
Expand Down Expand Up @@ -602,17 +564,6 @@ abstract class PcCollector[T](
Span(span.start, span.start + realName.length, point)
else Span(point, span.end, point)
else span

private val forCompMethods =
Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach)

// We don't want to collect synthethic `map`, `withFilter`, `foreach` and `flatMap` in for-comprenhensions
private def isForComprehensionMethod(sel: Select): Boolean =
val syntheticName = sel.name match
case name: TermName => forCompMethods(name)
case _ => false
val wrongSpan = sel.qualifier.span.contains(sel.nameSpan)
syntheticName && wrongSpan
end PcCollector

object PcCollector:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final class PcDocumentHighlightProvider(
toAdjust: SourcePosition,
sym: Option[Symbol]
): DocumentHighlight =
val (pos, _) = adjust(toAdjust)
val (pos, _) = toAdjust.adjust(text)
tree match
case _: NamedDefTree =>
DocumentHighlight(pos.toLsp, DocumentHighlightKind.Write)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ final class PcInlineValueProviderImpl(
) extends PcCollector[Option[Occurence]](driver, params)
with InlineValueProvider:

val text = params.text().nn.toCharArray().nn

val position: l.Position = pos.toLsp.getStart().nn

override def collect(parent: Option[Tree])(
Expand All @@ -38,7 +36,7 @@ final class PcInlineValueProviderImpl(
): Option[Occurence] =
tree match
case tree: Tree =>
val (adjustedPos, _) = adjust(pos)
val (adjustedPos, _) = pos.adjust(text)
Some(Occurence(tree, parent, adjustedPos))
case _ => None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class PcRenameProvider(
def collect(
parent: Option[Tree]
)(tree: Tree | EndMarker, toAdjust: SourcePosition, sym: Option[Symbol]): l.TextEdit =
val (pos, stripBackticks) = adjust(toAdjust, forRename = true)
val (pos, stripBackticks) = toAdjust.adjust(text, forRename = true)
l.TextEdit(
pos.toLsp,
if stripBackticks then newName.stripBackticks else newName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ final class PcSemanticTokensProvider(
Some(
makeNode(
sym = sym,
pos = adjust(pos)._1,
pos = pos.adjust(text)._1,
isDefinition = isDefinition(tree),
isDeclaration = isDeclaration(tree)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package dotty.tools.pc


import java.nio.file.Paths

import scala.meta.internal.metals.ReportContext
import dotty.tools.pc.utils.MtagsEnrichments.*
import dotty.tools.pc.printer.ShortenedTypePrinter
import scala.meta.pc.SymbolSearch
import scala.meta.pc.SyntheticDecoration
import scala.meta.pc.SyntheticDecorationsParams
import scala.meta.internal.pc.DecorationKind
import scala.meta.internal.pc.Decoration


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.StdNames.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.interactive.Interactive
import dotty.tools.dotc.interactive.InteractiveDriver
import dotty.tools.dotc.util.SourceFile
import dotty.tools.dotc.util.SourcePosition
import dotty.tools.dotc.util.Spans.Span
import dotty.tools.pc.IndexedContext

final class PcSyntheticDecorationsProvider(
driver: InteractiveDriver,
params: SyntheticDecorationsParams,
symbolSearch: SymbolSearch,
)(using ReportContext):

val uri = params.uri().nn
val filePath = Paths.get(uri).nn
val sourceText = params.text().nn
val text = sourceText.toCharArray().nn
val source =
SourceFile.virtual(filePath.toString, sourceText)
driver.run(uri, source)
given ctx: Context = driver.currentCtx
val unit = driver.currentCtx.run.nn.units.head

def tpdTree = unit.tpdTree

def provide(): List[SyntheticDecoration] =
val deepFolder = DeepFolder[Synthetics](collectDecorations)
deepFolder(Synthetics.empty, tpdTree).decorations

def collectDecorations(
decorations: Synthetics,
tree: Tree,
): Synthetics =
tree match
case ImplicitConversion(name, range) if params.implicitConversions() =>
val adjusted = range.adjust(text)._1
decorations
.add(
Decoration(
adjusted.startPos.toLsp,
name + "(",
DecorationKind.ImplicitConversion,
)
)
.add(
Decoration(
adjusted.endPos.toLsp,
")",
DecorationKind.ImplicitConversion,
)
)
case ImplicitParameters(names, pos, allImplicit)
if params.implicitParameters() =>
val label =
if allImplicit then names.mkString("(", ", ", ")")
else names.mkString(", ", ", ", "")
decorations.add(
Decoration(
pos.adjust(text)._1.toLsp,
label,
DecorationKind.ImplicitParameter,
)
)
case TypeParameters(tpes, pos, sel)
if params.typeParameters() && !syntheticTupleApply(sel) =>
val label = tpes.map(toLabel(_, pos)).mkString("[", ", ", "]")
decorations.add(
Decoration(
pos.adjust(text)._1.endPos.toLsp,
label,
DecorationKind.TypeParameter,
)
)
case InferredType(tpe, pos, defTree) if params.inferredTypes() =>
val adjustedPos = pos.adjust(text)._1.endPos
if decorations.containsDef(adjustedPos.start) then decorations
else
decorations.add(
Decoration(
adjustedPos.toLsp,
": " + toLabel(tpe, pos),
DecorationKind.InferredType,
),
adjustedPos.start,
)
case _ => decorations

private def toLabel(
tpe: Type,
pos: SourcePosition,
): String =
val tpdPath =
Interactive.pathTo(unit.tpdTree, pos.span)

val indexedCtx = IndexedContext(Interactive.contextOfPath(tpdPath))
val printer = ShortenedTypePrinter(
symbolSearch
)(using indexedCtx)
def optDealias(tpe: Type): Type =
def isInScope(tpe: Type): Boolean =
tpe match
case tref: TypeRef =>
indexedCtx.lookupSym(
tref.currentSymbol
) == IndexedContext.Result.InScope
case AppliedType(tycon, args) =>
isInScope(tycon) && args.forall(isInScope)
case _ => true
if isInScope(tpe)
then tpe
else tpe.metalsDealias(using indexedCtx.ctx)

val dealiased = optDealias(tpe)
printer.tpe(dealiased)
end toLabel

private val definitions = IndexedContext(ctx).ctx.definitions
private def syntheticTupleApply(tree: Tree): Boolean =
tree match
case sel: Select =>
if definitions.isTupleNType(sel.symbol.info.finalResultType) then
sel match
case Select(tupleClass: Ident, _)
if !tupleClass.span.isZeroExtent &&
tupleClass.span.exists &&
tupleClass.name.startsWith("Tuple") =>
val pos = tupleClass.sourcePos
!sourceText.slice(pos.start, pos.end).mkString.startsWith("Tuple")
case _ => true
else false
case _ => false
end PcSyntheticDecorationsProvider

object ImplicitConversion:
def unapply(tree: Tree)(using Context) =
tree match
case Apply(fun: Ident, args) if isSynthetic(fun) =>
implicitConversion(fun, args)
case Apply(Select(fun, name), args)
if name == nme.apply && isSynthetic(fun) =>
implicitConversion(fun, args)
case _ => None
private def isSynthetic(tree: Tree)(using Context) =
tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit)

private def implicitConversion(fun: Tree, args: List[Tree])(using Context) =
val lastArgPos =
args.lastOption.map(_.sourcePos).getOrElse(fun.sourcePos)
Some(
fun.symbol.decodedName,
lastArgPos.withStart(fun.sourcePos.start),
)
end ImplicitConversion

object ImplicitParameters:
def unapply(tree: Tree)(using Context) =
tree match
case Apply(fun, args)
if args.exists(isSyntheticArg) && !tree.sourcePos.span.isZeroExtent =>
val (implicitArgs, providedArgs) = args.partition(isSyntheticArg)
val allImplicit = providedArgs.isEmpty
val pos = implicitArgs.head.sourcePos
Some(implicitArgs.map(_.symbol.decodedName), pos, allImplicit)
case Apply(ta @ TypeApply(fun, _), _)
if fun.span.isSynthetic && isValueOf(fun) =>
Some(
List("new " + tpnme.valueOf.decoded.capitalize + "(...)"),
fun.sourcePos,
true,
)
case _ => None
private def isValueOf(tree: Tree)(using Context) =
val symbol = tree.symbol.maybeOwner
symbol.name.decoded == tpnme.valueOf.decoded.capitalize
private def isSyntheticArg(tree: Tree)(using Context) = tree match
case tree: Ident =>
tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit)
case _ => false
end ImplicitParameters

object TypeParameters:
def unapply(tree: Tree)(using Context) =
tree match
case TypeApply(sel: Select, _) if sel.isForComprehensionMethod => None
case TypeApply(fun, args) if inferredTypeArgs(args) =>
val pos = fun match
case sel: Select if sel.isInfix =>
sel.sourcePos.withEnd(sel.nameSpan.end)
case _ => fun.sourcePos
val tpes = args.map(_.tpe.stripTypeVar.widen.finalResultType)
Some((tpes, pos.endPos, fun))
case _ => None
private def inferredTypeArgs(args: List[Tree]): Boolean =
args.forall {
case tt: TypeTree if tt.span.exists && !tt.span.isZeroExtent => true
case _ => false
}
end TypeParameters

object InferredType:
def unapply(tree: Tree)(using Context) =
tree match
case vd @ ValDef(_, tpe, _)
if isValidSpan(tpe.span, vd.nameSpan) &&
!vd.symbol.is(Flags.Enum) =>
if vd.symbol == vd.symbol.sourceSymbol then
Some(tpe.tpe, tpe.sourcePos.withSpan(vd.nameSpan), vd)
else None
case vd @ DefDef(_, _, tpe, _)
if isValidSpan(tpe.span, vd.nameSpan) &&
tpe.span.start >= vd.nameSpan.end &&
!vd.symbol.isConstructor &&
!vd.symbol.is(Flags.Mutable) =>
if vd.symbol == vd.symbol.sourceSymbol then
Some(tpe.tpe, tpe.sourcePos, vd)
else None
case bd @ Bind(
name,
Ident(nme.WILDCARD),
) =>
Some(bd.symbol.info, bd.namePos, bd)
case _ => None

private def isValidSpan(tpeSpan: Span, nameSpan: Span): Boolean =
tpeSpan.isZeroExtent &&
nameSpan.exists &&
!nameSpan.isZeroExtent

end InferredType

case class Synthetics(
decorations: List[Decoration],
definitions: Set[Int],
):
def containsDef(offset: Int) = definitions(offset)
def add(decoration: Decoration, offset: Int) =
copy(
decorations = decoration :: decorations,
definitions = definitions + offset,
)
def add(decoration: Decoration) =
copy(decorations = decoration :: decorations)

object Synthetics:
def empty: Synthetics = Synthetics(Nil, Set.empty)
Loading
Loading