Skip to content

[SemanticDB] Support synthetics: implicit params, context params, and implicit conversions #13288

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

Merged
merged 7 commits into from
Aug 30, 2021
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
51 changes: 21 additions & 30 deletions compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import NameOps._
import util.Spans.Span
import util.{SourceFile, SourcePosition}
import transform.SymUtils._
import SymbolInformation.{Kind => k}

import scala.jdk.CollectionConverters._
import scala.collection.mutable
Expand Down Expand Up @@ -46,12 +45,13 @@ class ExtractSemanticDB extends Phase:
val unit = ctx.compilationUnit
val extractor = Extractor()
extractor.extract(unit.tpdTree)
ExtractSemanticDB.write(unit.source, extractor.occurrences.toList, extractor.symbolInfos.toList)
ExtractSemanticDB.write(unit.source, extractor.occurrences.toList, extractor.symbolInfos.toList, extractor.synthetics.toList)

/** Extractor of symbol occurrences from trees */
class Extractor extends TreeTraverser:
given s.SemanticSymbolBuilder = s.SemanticSymbolBuilder()
val converter = s.TypeOps()
val synth = SyntheticsExtractor()
given converter: s.TypeOps = s.TypeOps()

/** The bodies of synthetic locals */
private val localBodies = mutable.HashMap[Symbol, Tree]()
Expand All @@ -62,6 +62,8 @@ class ExtractSemanticDB extends Phase:
/** The extracted symbol infos */
val symbolInfos = new mutable.ListBuffer[SymbolInformation]()

val synthetics = new mutable.ListBuffer[s.Synthetic]()

/** A cache of localN names */
val localNames = new mutable.HashSet[String]()

Expand Down Expand Up @@ -158,11 +160,13 @@ class ExtractSemanticDB extends Phase:
tree match
case tree: DefDef =>
tree.paramss.foreach(_.foreach(param => registerSymbolSimple(param.symbol)))
case tree: ValDef if tree.symbol.is(Given) => traverse(tree.tpt)
case tree: ValDef if tree.symbol.is(Given) =>
traverse(tree.tpt)
case _ =>
if !tree.symbol.isGlobal then
localBodies(tree.symbol) = tree.rhs
// ignore rhs

case PatternValDef(pat, rhs) =>
traverse(rhs)
PatternValDef.collectPats(pat).foreach(traverse)
Expand All @@ -179,6 +183,10 @@ class ExtractSemanticDB extends Phase:
registerUseGuarded(None, privateWithin, spanOfSymbol(privateWithin, tree.span, tree.source), tree.source)
else if !excludeSymbol(tree.symbol) then
registerSymbol(tree.symbol, symbolKinds(tree))
case tree: Template if tree.symbol.owner.is(Invisible) =>
// do nothing
// exclude the symbols and synthetics generated by @main annotation
// (main class generated by @main has `Invisible` flag, see `MainProxies.scala`).
case tree: Template =>
val ctorSym = tree.constr.symbol
for parent <- tree.parentsOrDerived if parent.span.hasLength do
Expand All @@ -197,6 +205,7 @@ class ExtractSemanticDB extends Phase:
case tree: Apply =>
@tu lazy val genParamSymbol: Name => String = tree.fun.symbol.funParamSymbol
traverse(tree.fun)
synth.tryFindSynthetic(tree).foreach(synthetics.addOne)
for arg <- tree.args do
arg match
case tree @ NamedArg(name, arg) =>
Expand Down Expand Up @@ -292,12 +301,6 @@ class ExtractSemanticDB extends Phase:
end PatternValDef


private def range(span: Span, treeSource: SourceFile)(using Context): Option[Range] =
def lineCol(offset: Int) = (treeSource.offsetToLine(offset), treeSource.column(offset))
val (startLine, startCol) = lineCol(span.start)
val (endLine, endCol) = lineCol(span.end)
Some(Range(startLine, startCol, endLine, endCol))


private def registerSymbol(sym: Symbol, symkinds: Set[SymbolKind])(using Context): Unit =
val sname = sym.symbolName
Expand Down Expand Up @@ -338,24 +341,6 @@ class ExtractSemanticDB extends Phase:
if !sym.is(Package) then
registerSymbol(sym, symkinds)

private def namePresentInSource(sym: Symbol, span: Span, source:SourceFile)(using Context): Boolean =
if !span.exists then false
else
val content = source.content()
val (start, end) =
if content.lift(span.end - 1).exists(_ == '`') then
(span.start + 1, span.end - 1)
else (span.start, span.end)
val nameInSource = content.slice(start, end).mkString
// for secondary constructors `this`
if sym.isConstructor && nameInSource == nme.THISkw.toString then
true
else
val target =
if sym.isPackageObject then sym.owner
else sym
nameInSource == target.name.stripModuleClassSuffix.lastPart.toString

private def spanOfSymbol(sym: Symbol, span: Span, treeSource: SourceFile)(using Context): Span =
val contents = if treeSource.exists then treeSource.content() else Array.empty[Char]
val idx = contents.indexOfSlice(sym.name.show, span.start)
Expand Down Expand Up @@ -466,7 +451,12 @@ object ExtractSemanticDB:

val name: String = "extractSemanticDB"

def write(source: SourceFile, occurrences: List[SymbolOccurrence], symbolInfos: List[SymbolInformation])(using Context): Unit =
def write(
source: SourceFile,
occurrences: List[SymbolOccurrence],
symbolInfos: List[SymbolInformation],
synthetics: List[Synthetic],
)(using Context): Unit =
def absolutePath(path: Path): Path = path.toAbsolutePath.normalize
val semanticdbTarget =
val semanticdbTargetSetting = ctx.settings.semanticdbTarget.value
Expand All @@ -488,7 +478,8 @@ object ExtractSemanticDB:
text = "",
md5 = internal.MD5.compute(String(source.content)),
symbols = symbolInfos,
occurrences = occurrences
occurrences = occurrences,
synthetics = synthetics,
)
val docs = TextDocuments(List(doc))
val out = Files.newOutputStream(outpath)
Expand Down
125 changes: 116 additions & 9 deletions compiler/src/dotty/tools/dotc/semanticdb/PPrint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import dotty.tools.dotc.{semanticdb => s}
import scala.collection.mutable
import dotty.tools.dotc.semanticdb.Scala3.{_, given}
import SymbolInformation.Kind._

class SymbolInfomationPrinter (symtab: PrinterSymtab):
import dotty.tools.dotc.util.SourceFile
class SymbolInformationPrinter (symtab: PrinterSymtab):
val notes = InfoNotes()
val infoPrinter = InfoPrinter(notes)

Expand All @@ -28,8 +28,9 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
val displayName = if sym.isGlobal then sym.desc.value else sym
SymbolInformation(symbol = sym, displayName = displayName)
}
end InfoNotes

class InfoPrinter(notes: InfoNotes) {
class InfoPrinter(notes: InfoNotes):
private enum SymbolStyle:
case Reference, Definition
def pprint(info: SymbolInformation): String =
Expand Down Expand Up @@ -81,7 +82,7 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
private def pprintDef(info: SymbolInformation) =
notes.enter(info)
pprint(info.symbol, SymbolStyle.Definition)
private def pprintRef(sym: String): String = pprint(sym, SymbolStyle.Reference)
def pprintRef(sym: String): String = pprint(sym, SymbolStyle.Reference)
private def pprintDef(sym: String): String = pprint(sym, SymbolStyle.Definition)
private def pprint(sym: String, style: SymbolStyle): String =
val info = notes.visit(sym)
Expand Down Expand Up @@ -137,7 +138,7 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
case _ =>
"<?>"

private def pprint(tpe: Type): String = {
protected def pprint(tpe: Type): String = {
def prefix(tpe: Type): String = tpe match
case TypeRef(pre, sym, args) =>
val preStr = pre match {
Expand Down Expand Up @@ -204,7 +205,7 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
case tpe => s"@${pprint(tpe)}"
}

private def pprint(const: Constant): String = const match {
protected def pprint(const: Constant): String = const match {
case Constant.Empty =>
"<?>"
case UnitConstant() =>
Expand Down Expand Up @@ -245,7 +246,6 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
s"private[${ssym}] "
case ProtectedWithinAccess(ssym) =>
s"protected[${ssym}] "

extension (scope: Scope)
private def infos: List[SymbolInformation] =
if (scope.symlinks.nonEmpty)
Expand All @@ -258,8 +258,8 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
case Some(s) => s.infos
case None => Nil
}
}
end SymbolInfomationPrinter
end InfoPrinter
end SymbolInformationPrinter

extension (info: SymbolInformation)
def prefixBeforeTpe: String = {
Expand All @@ -280,3 +280,110 @@ object PrinterSymtab:
new PrinterSymtab {
override def info(symbol: String): Option[SymbolInformation] = map.get(symbol)
}

def processRange(sb: StringBuilder, range: Range): Unit =
sb.append('[')
.append(range.startLine).append(':').append(range.startCharacter)
.append("..")
.append(range.endLine).append(':').append(range.endCharacter)
.append("):")



class SyntheticPrinter(symtab: PrinterSymtab, source: SourceFile) extends SymbolInformationPrinter(symtab):

def pprint(synth: Synthetic): String =
val sb = new StringBuilder()
val notes = InfoNotes()
val treePrinter = TreePrinter(source, synth.range, notes)

synth.range match
case Some(range) =>
processRange(sb, range)
sb.append(source.substring(range))
case None =>
sb.append("[):")
sb.append(" => ")
sb.append(treePrinter.pprint(synth.tree))
sb.toString

extension (source: SourceFile)
private def substring(range: Option[s.Range]): String =
range match
case Some(range) => source.substring(range)
case None => ""
private def substring(range: s.Range): String =
/** get the line length of a given line */
def lineLength(line: Int): Int =
val isLastLine = source.lineToOffsetOpt(line).nonEmpty && source.lineToOffsetOpt(line + 1).isEmpty
if isLastLine then source.content.length - source.lineToOffset(line) - 1
else source.lineToOffset(line + 1) - source.lineToOffset(line) - 1 // -1 for newline char

val start = source.lineToOffset(range.startLine) +
math.min(range.startCharacter, lineLength(range.startLine))
val end = source.lineToOffset(range.endLine) +
math.min(range.endCharacter, lineLength(range.endLine))
new String(source.content, start, end - start)


// def pprint(tree: s.Tree, range: Option[Range]): String =
class TreePrinter(source: SourceFile, originalRange: Option[Range], notes: InfoNotes) extends InfoPrinter(notes):
def pprint(tree: Tree): String =
val sb = new StringBuilder()
processTree(tree)(using sb)
sb.toString


private def rep[T](xs: Seq[T], seq: String)(f: T => Unit)(using sb: StringBuilder): Unit =
xs.zipWithIndex.foreach { (x, i) =>
if i != 0 then sb.append(seq)
f(x)
}

private def processTree(tree: Tree)(using sb: StringBuilder): Unit =
tree match {
case tree: ApplyTree =>
processTree(tree.function)
sb.append("(")
rep(tree.arguments, ", ")(processTree)
sb.append(")")
case tree: FunctionTree =>
sb.append("{")
sb.append("(")
rep(tree.parameters, ", ")(processTree)
sb.append(") =>")
processTree(tree.body)
sb.append("}")
case tree: IdTree =>
sb.append(pprintRef(tree.symbol))
case tree: LiteralTree =>
sb.append(pprint(tree.constant))
case tree: MacroExpansionTree =>
sb.append("(`macro-expandee` : `")
sb.append(pprint(tree.tpe))
sb.append(")")
case tree: OriginalTree =>
if (tree.range == originalRange && originalRange.nonEmpty) then
sb.append("*")
else
sb.append("orig(")
sb.append(source.substring(tree.range))
sb.append(")")
case tree: SelectTree =>
processTree(tree.qualifier)
sb.append(".")
tree.id match
case Some(tree) => processTree(tree)
case None => ()
case tree: TypeApplyTree =>
processTree(tree.function)
sb.append("[")
rep(tree.typeArguments, ", ")((t) => sb.append(pprint(t)))
sb.append("]")

case _ =>
sb.append("<?>")
}


end SyntheticPrinter
38 changes: 34 additions & 4 deletions compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import core.Flags._
import core.NameKinds
import core.StdNames.nme
import SymbolInformation.{Kind => k}
import dotty.tools.dotc.util.SourceFile
import dotty.tools.dotc.util.Spans.Span
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.{semanticdb => s}

import java.lang.Character.{isJavaIdentifierPart, isJavaIdentifierStart}

Expand All @@ -26,6 +30,30 @@ object Scala3:

private val WILDCARDTypeName = nme.WILDCARD.toTypeName

def range(span: Span, treeSource: SourceFile)(using Context): Option[Range] =
def lineCol(offset: Int) = (treeSource.offsetToLine(offset), treeSource.column(offset))
val (startLine, startCol) = lineCol(span.start)
val (endLine, endCol) = lineCol(span.end)
Some(Range(startLine, startCol, endLine, endCol))

def namePresentInSource(sym: Symbol, span: Span, source:SourceFile)(using Context): Boolean =
if !span.exists then false
else
val content = source.content()
val (start, end) =
if content.lift(span.end - 1).exists(_ == '`') then
(span.start + 1, span.end - 1)
else (span.start, span.end)
val nameInSource = content.slice(start, end).mkString
// for secondary constructors `this`
if sym.isConstructor && nameInSource == nme.THISkw.toString then
true
else
val target =
if sym.isPackageObject then sym.owner
else sym
nameInSource == target.name.stripModuleClassSuffix.lastPart.toString

sealed trait FakeSymbol {
private[Scala3] var sname: Option[String] = None
}
Expand Down Expand Up @@ -423,20 +451,22 @@ object Scala3:
def hasLength = range.endLine > range.startLine || range.endCharacter > range.startCharacter
end RangeOps

/** Sort symbol occurrences by their start position. */
given OccurrenceOrdering: Ordering[SymbolOccurrence] = (x, y) =>
x.range -> y.range match
private def compareRange(x: Option[Range], y: Option[Range]): Int = x -> y match
case None -> _ | _ -> None => 0
case Some(a) -> Some(b) =>
val byLine = Integer.compare(a.startLine, b.startLine)
if (byLine != 0)
byLine
else // byCharacter
Integer.compare(a.startCharacter, b.startCharacter)
end OccurrenceOrdering

/** Sort symbol occurrences by their start position. */
given Ordering[SymbolOccurrence] = (x, y) => compareRange(x.range, y.range)

given Ordering[SymbolInformation] = Ordering.by[SymbolInformation, String](_.symbol)(IdentifierOrdering())

given Ordering[Synthetic] = (x, y) => compareRange(x.range, y.range)

/**
* A comparator for identifier like "Predef" or "Function10".
*
Expand Down
Loading