Skip to content

Commit b014ffe

Browse files
authored
Merge pull request #13288 from tanishiking/synthetic-implicit-conversion
[SemanticDB] Support synthetics: implicit params, context params, and implicit conversions
2 parents 028c749 + 0edab1a commit b014ffe

File tree

11 files changed

+433
-64
lines changed

11 files changed

+433
-64
lines changed

compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import NameOps._
1616
import util.Spans.Span
1717
import util.{SourceFile, SourcePosition}
1818
import transform.SymUtils._
19-
import SymbolInformation.{Kind => k}
2019

2120
import scala.jdk.CollectionConverters._
2221
import scala.collection.mutable
@@ -46,12 +45,13 @@ class ExtractSemanticDB extends Phase:
4645
val unit = ctx.compilationUnit
4746
val extractor = Extractor()
4847
extractor.extract(unit.tpdTree)
49-
ExtractSemanticDB.write(unit.source, extractor.occurrences.toList, extractor.symbolInfos.toList)
48+
ExtractSemanticDB.write(unit.source, extractor.occurrences.toList, extractor.symbolInfos.toList, extractor.synthetics.toList)
5049

5150
/** Extractor of symbol occurrences from trees */
5251
class Extractor extends TreeTraverser:
5352
given s.SemanticSymbolBuilder = s.SemanticSymbolBuilder()
54-
val converter = s.TypeOps()
53+
val synth = SyntheticsExtractor()
54+
given converter: s.TypeOps = s.TypeOps()
5555

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

65+
val synthetics = new mutable.ListBuffer[s.Synthetic]()
66+
6567
/** A cache of localN names */
6668
val localNames = new mutable.HashSet[String]()
6769

@@ -158,11 +160,13 @@ class ExtractSemanticDB extends Phase:
158160
tree match
159161
case tree: DefDef =>
160162
tree.paramss.foreach(_.foreach(param => registerSymbolSimple(param.symbol)))
161-
case tree: ValDef if tree.symbol.is(Given) => traverse(tree.tpt)
163+
case tree: ValDef if tree.symbol.is(Given) =>
164+
traverse(tree.tpt)
162165
case _ =>
163166
if !tree.symbol.isGlobal then
164167
localBodies(tree.symbol) = tree.rhs
165168
// ignore rhs
169+
166170
case PatternValDef(pat, rhs) =>
167171
traverse(rhs)
168172
PatternValDef.collectPats(pat).foreach(traverse)
@@ -179,6 +183,10 @@ class ExtractSemanticDB extends Phase:
179183
registerUseGuarded(None, privateWithin, spanOfSymbol(privateWithin, tree.span, tree.source), tree.source)
180184
else if !excludeSymbol(tree.symbol) then
181185
registerSymbol(tree.symbol, symbolKinds(tree))
186+
case tree: Template if tree.symbol.owner.is(Invisible) =>
187+
// do nothing
188+
// exclude the symbols and synthetics generated by @main annotation
189+
// (main class generated by @main has `Invisible` flag, see `MainProxies.scala`).
182190
case tree: Template =>
183191
val ctorSym = tree.constr.symbol
184192
for parent <- tree.parentsOrDerived if parent.span.hasLength do
@@ -197,6 +205,7 @@ class ExtractSemanticDB extends Phase:
197205
case tree: Apply =>
198206
@tu lazy val genParamSymbol: Name => String = tree.fun.symbol.funParamSymbol
199207
traverse(tree.fun)
208+
synth.tryFindSynthetic(tree).foreach(synthetics.addOne)
200209
for arg <- tree.args do
201210
arg match
202211
case tree @ NamedArg(name, arg) =>
@@ -292,12 +301,6 @@ class ExtractSemanticDB extends Phase:
292301
end PatternValDef
293302

294303

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

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

341-
private def namePresentInSource(sym: Symbol, span: Span, source:SourceFile)(using Context): Boolean =
342-
if !span.exists then false
343-
else
344-
val content = source.content()
345-
val (start, end) =
346-
if content.lift(span.end - 1).exists(_ == '`') then
347-
(span.start + 1, span.end - 1)
348-
else (span.start, span.end)
349-
val nameInSource = content.slice(start, end).mkString
350-
// for secondary constructors `this`
351-
if sym.isConstructor && nameInSource == nme.THISkw.toString then
352-
true
353-
else
354-
val target =
355-
if sym.isPackageObject then sym.owner
356-
else sym
357-
nameInSource == target.name.stripModuleClassSuffix.lastPart.toString
358-
359344
private def spanOfSymbol(sym: Symbol, span: Span, treeSource: SourceFile)(using Context): Span =
360345
val contents = if treeSource.exists then treeSource.content() else Array.empty[Char]
361346
val idx = contents.indexOfSlice(sym.name.show, span.start)
@@ -466,7 +451,12 @@ object ExtractSemanticDB:
466451

467452
val name: String = "extractSemanticDB"
468453

469-
def write(source: SourceFile, occurrences: List[SymbolOccurrence], symbolInfos: List[SymbolInformation])(using Context): Unit =
454+
def write(
455+
source: SourceFile,
456+
occurrences: List[SymbolOccurrence],
457+
symbolInfos: List[SymbolInformation],
458+
synthetics: List[Synthetic],
459+
)(using Context): Unit =
470460
def absolutePath(path: Path): Path = path.toAbsolutePath.normalize
471461
val semanticdbTarget =
472462
val semanticdbTargetSetting = ctx.settings.semanticdbTarget.value
@@ -488,7 +478,8 @@ object ExtractSemanticDB:
488478
text = "",
489479
md5 = internal.MD5.compute(String(source.content)),
490480
symbols = symbolInfos,
491-
occurrences = occurrences
481+
occurrences = occurrences,
482+
synthetics = synthetics,
492483
)
493484
val docs = TextDocuments(List(doc))
494485
val out = Files.newOutputStream(outpath)

compiler/src/dotty/tools/dotc/semanticdb/PPrint.scala

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import dotty.tools.dotc.{semanticdb => s}
55
import scala.collection.mutable
66
import dotty.tools.dotc.semanticdb.Scala3.{_, given}
77
import SymbolInformation.Kind._
8-
9-
class SymbolInfomationPrinter (symtab: PrinterSymtab):
8+
import dotty.tools.dotc.util.SourceFile
9+
class SymbolInformationPrinter (symtab: PrinterSymtab):
1010
val notes = InfoNotes()
1111
val infoPrinter = InfoPrinter(notes)
1212

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

32-
class InfoPrinter(notes: InfoNotes) {
33+
class InfoPrinter(notes: InfoNotes):
3334
private enum SymbolStyle:
3435
case Reference, Definition
3536
def pprint(info: SymbolInformation): String =
@@ -81,7 +82,7 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
8182
private def pprintDef(info: SymbolInformation) =
8283
notes.enter(info)
8384
pprint(info.symbol, SymbolStyle.Definition)
84-
private def pprintRef(sym: String): String = pprint(sym, SymbolStyle.Reference)
85+
def pprintRef(sym: String): String = pprint(sym, SymbolStyle.Reference)
8586
private def pprintDef(sym: String): String = pprint(sym, SymbolStyle.Definition)
8687
private def pprint(sym: String, style: SymbolStyle): String =
8788
val info = notes.visit(sym)
@@ -137,7 +138,7 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
137138
case _ =>
138139
"<?>"
139140

140-
private def pprint(tpe: Type): String = {
141+
protected def pprint(tpe: Type): String = {
141142
def prefix(tpe: Type): String = tpe match
142143
case TypeRef(pre, sym, args) =>
143144
val preStr = pre match {
@@ -204,7 +205,7 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
204205
case tpe => s"@${pprint(tpe)}"
205206
}
206207

207-
private def pprint(const: Constant): String = const match {
208+
protected def pprint(const: Constant): String = const match {
208209
case Constant.Empty =>
209210
"<?>"
210211
case UnitConstant() =>
@@ -245,7 +246,6 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
245246
s"private[${ssym}] "
246247
case ProtectedWithinAccess(ssym) =>
247248
s"protected[${ssym}] "
248-
249249
extension (scope: Scope)
250250
private def infos: List[SymbolInformation] =
251251
if (scope.symlinks.nonEmpty)
@@ -258,8 +258,8 @@ class SymbolInfomationPrinter (symtab: PrinterSymtab):
258258
case Some(s) => s.infos
259259
case None => Nil
260260
}
261-
}
262-
end SymbolInfomationPrinter
261+
end InfoPrinter
262+
end SymbolInformationPrinter
263263

264264
extension (info: SymbolInformation)
265265
def prefixBeforeTpe: String = {
@@ -280,3 +280,110 @@ object PrinterSymtab:
280280
new PrinterSymtab {
281281
override def info(symbol: String): Option[SymbolInformation] = map.get(symbol)
282282
}
283+
284+
def processRange(sb: StringBuilder, range: Range): Unit =
285+
sb.append('[')
286+
.append(range.startLine).append(':').append(range.startCharacter)
287+
.append("..")
288+
.append(range.endLine).append(':').append(range.endCharacter)
289+
.append("):")
290+
291+
292+
293+
class SyntheticPrinter(symtab: PrinterSymtab, source: SourceFile) extends SymbolInformationPrinter(symtab):
294+
295+
def pprint(synth: Synthetic): String =
296+
val sb = new StringBuilder()
297+
val notes = InfoNotes()
298+
val treePrinter = TreePrinter(source, synth.range, notes)
299+
300+
synth.range match
301+
case Some(range) =>
302+
processRange(sb, range)
303+
sb.append(source.substring(range))
304+
case None =>
305+
sb.append("[):")
306+
sb.append(" => ")
307+
sb.append(treePrinter.pprint(synth.tree))
308+
sb.toString
309+
310+
extension (source: SourceFile)
311+
private def substring(range: Option[s.Range]): String =
312+
range match
313+
case Some(range) => source.substring(range)
314+
case None => ""
315+
private def substring(range: s.Range): String =
316+
/** get the line length of a given line */
317+
def lineLength(line: Int): Int =
318+
val isLastLine = source.lineToOffsetOpt(line).nonEmpty && source.lineToOffsetOpt(line + 1).isEmpty
319+
if isLastLine then source.content.length - source.lineToOffset(line) - 1
320+
else source.lineToOffset(line + 1) - source.lineToOffset(line) - 1 // -1 for newline char
321+
322+
val start = source.lineToOffset(range.startLine) +
323+
math.min(range.startCharacter, lineLength(range.startLine))
324+
val end = source.lineToOffset(range.endLine) +
325+
math.min(range.endCharacter, lineLength(range.endLine))
326+
new String(source.content, start, end - start)
327+
328+
329+
// def pprint(tree: s.Tree, range: Option[Range]): String =
330+
class TreePrinter(source: SourceFile, originalRange: Option[Range], notes: InfoNotes) extends InfoPrinter(notes):
331+
def pprint(tree: Tree): String =
332+
val sb = new StringBuilder()
333+
processTree(tree)(using sb)
334+
sb.toString
335+
336+
337+
private def rep[T](xs: Seq[T], seq: String)(f: T => Unit)(using sb: StringBuilder): Unit =
338+
xs.zipWithIndex.foreach { (x, i) =>
339+
if i != 0 then sb.append(seq)
340+
f(x)
341+
}
342+
343+
private def processTree(tree: Tree)(using sb: StringBuilder): Unit =
344+
tree match {
345+
case tree: ApplyTree =>
346+
processTree(tree.function)
347+
sb.append("(")
348+
rep(tree.arguments, ", ")(processTree)
349+
sb.append(")")
350+
case tree: FunctionTree =>
351+
sb.append("{")
352+
sb.append("(")
353+
rep(tree.parameters, ", ")(processTree)
354+
sb.append(") =>")
355+
processTree(tree.body)
356+
sb.append("}")
357+
case tree: IdTree =>
358+
sb.append(pprintRef(tree.symbol))
359+
case tree: LiteralTree =>
360+
sb.append(pprint(tree.constant))
361+
case tree: MacroExpansionTree =>
362+
sb.append("(`macro-expandee` : `")
363+
sb.append(pprint(tree.tpe))
364+
sb.append(")")
365+
case tree: OriginalTree =>
366+
if (tree.range == originalRange && originalRange.nonEmpty) then
367+
sb.append("*")
368+
else
369+
sb.append("orig(")
370+
sb.append(source.substring(tree.range))
371+
sb.append(")")
372+
case tree: SelectTree =>
373+
processTree(tree.qualifier)
374+
sb.append(".")
375+
tree.id match
376+
case Some(tree) => processTree(tree)
377+
case None => ()
378+
case tree: TypeApplyTree =>
379+
processTree(tree.function)
380+
sb.append("[")
381+
rep(tree.typeArguments, ", ")((t) => sb.append(pprint(t)))
382+
sb.append("]")
383+
384+
case _ =>
385+
sb.append("<?>")
386+
}
387+
388+
389+
end SyntheticPrinter

compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import core.Flags._
1010
import core.NameKinds
1111
import core.StdNames.nme
1212
import SymbolInformation.{Kind => k}
13+
import dotty.tools.dotc.util.SourceFile
14+
import dotty.tools.dotc.util.Spans.Span
15+
import dotty.tools.dotc.ast.tpd
16+
import dotty.tools.dotc.{semanticdb => s}
1317

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

@@ -26,6 +30,30 @@ object Scala3:
2630

2731
private val WILDCARDTypeName = nme.WILDCARD.toTypeName
2832

33+
def range(span: Span, treeSource: SourceFile)(using Context): Option[Range] =
34+
def lineCol(offset: Int) = (treeSource.offsetToLine(offset), treeSource.column(offset))
35+
val (startLine, startCol) = lineCol(span.start)
36+
val (endLine, endCol) = lineCol(span.end)
37+
Some(Range(startLine, startCol, endLine, endCol))
38+
39+
def namePresentInSource(sym: Symbol, span: Span, source:SourceFile)(using Context): Boolean =
40+
if !span.exists then false
41+
else
42+
val content = source.content()
43+
val (start, end) =
44+
if content.lift(span.end - 1).exists(_ == '`') then
45+
(span.start + 1, span.end - 1)
46+
else (span.start, span.end)
47+
val nameInSource = content.slice(start, end).mkString
48+
// for secondary constructors `this`
49+
if sym.isConstructor && nameInSource == nme.THISkw.toString then
50+
true
51+
else
52+
val target =
53+
if sym.isPackageObject then sym.owner
54+
else sym
55+
nameInSource == target.name.stripModuleClassSuffix.lastPart.toString
56+
2957
sealed trait FakeSymbol {
3058
private[Scala3] var sname: Option[String] = None
3159
}
@@ -423,20 +451,22 @@ object Scala3:
423451
def hasLength = range.endLine > range.startLine || range.endCharacter > range.startCharacter
424452
end RangeOps
425453

426-
/** Sort symbol occurrences by their start position. */
427-
given OccurrenceOrdering: Ordering[SymbolOccurrence] = (x, y) =>
428-
x.range -> y.range match
454+
private def compareRange(x: Option[Range], y: Option[Range]): Int = x -> y match
429455
case None -> _ | _ -> None => 0
430456
case Some(a) -> Some(b) =>
431457
val byLine = Integer.compare(a.startLine, b.startLine)
432458
if (byLine != 0)
433459
byLine
434460
else // byCharacter
435461
Integer.compare(a.startCharacter, b.startCharacter)
436-
end OccurrenceOrdering
462+
463+
/** Sort symbol occurrences by their start position. */
464+
given Ordering[SymbolOccurrence] = (x, y) => compareRange(x.range, y.range)
437465

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

468+
given Ordering[Synthetic] = (x, y) => compareRange(x.range, y.range)
469+
440470
/**
441471
* A comparator for identifier like "Predef" or "Function10".
442472
*

0 commit comments

Comments
 (0)