Skip to content

Commit 75e6bb6

Browse files
committed
Export diagnostics (including unused warnings) to SemanticDB
1 parent 57c2b66 commit 75e6bb6

File tree

7 files changed

+151
-59
lines changed

7 files changed

+151
-59
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Compiler {
3838
List(new CheckUnused.PostTyper) :: // Check for unused elements
3939
List(new YCheckPositions) :: // YCheck positions
4040
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
41-
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
41+
List(new semanticdb.ExtractSemanticDB.PostTyper) :: // Extract info into .semanticdb files
4242
List(new PostTyper) :: // Additional checks and cleanups after type checking
4343
List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only)
4444
List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks
@@ -51,6 +51,7 @@ class Compiler {
5151
List(new Inlining) :: // Inline and execute macros
5252
List(new PostInlining) :: // Add mirror support for inlined code
5353
List(new CheckUnused.PostInlining) :: // Check for unused elements
54+
List(new semanticdb.ExtractSemanticDB.PostInlining) :: // Extract info into .semanticdb files
5455
List(new Staging) :: // Check staging levels and heal staged types
5556
List(new Splicing) :: // Replace level 1 splices with holes
5657
List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures

compiler/src/dotty/tools/dotc/reporting/Reporter.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,14 @@ abstract class Reporter extends interfaces.ReporterResult {
109109

110110
private var errors: List[Error] = Nil
111111

112+
private var warnings: List[Warning] = Nil
113+
112114
/** All errors reported by this reporter (ignoring outer reporters) */
113115
def allErrors: List[Error] = errors
114116

117+
/** All errors reported by this reporter (ignoring outer reporters) */
118+
def allWarnings: List[Warning] = warnings
119+
115120
/** Were sticky errors reported? Overridden in StoreReporter. */
116121
def hasStickyErrors: Boolean = false
117122

@@ -157,7 +162,9 @@ abstract class Reporter extends interfaces.ReporterResult {
157162
markReported(d)
158163
withMode(Mode.Printing)(doReport(d))
159164
d match {
160-
case _: Warning => _warningCount += 1
165+
case w: Warning =>
166+
warnings = w :: warnings
167+
_warningCount += 1
161168
case e: Error =>
162169
errors = e :: errors
163170
_errorCount += 1
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package dotty.tools.dotc.semanticdb
2+
3+
import dotty.tools.dotc.reporting.Diagnostic
4+
import dotty.tools.dotc.{semanticdb => s}
5+
import dotty.tools.dotc.interfaces.Diagnostic.{ERROR, INFO, WARNING}
6+
import dotty.tools.dotc.core.Contexts.Context
7+
8+
object DiagnosticOps:
9+
extension (d: Diagnostic)
10+
def toSemanticDiagnostic(using Context): s.Diagnostic =
11+
val severity = d.level match
12+
case ERROR => s.Diagnostic.Severity.ERROR
13+
case WARNING => s.Diagnostic.Severity.WARNING
14+
case INFO => s.Diagnostic.Severity.INFORMATION
15+
case _ => s.Diagnostic.Severity.INFORMATION
16+
s.Diagnostic(
17+
range = Scala3.range(d.pos.span, d.pos.source),
18+
severity = severity,
19+
message = d.msg.message
20+
)

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

Lines changed: 89 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,17 @@ import scala.PartialFunction.condOpt
2525

2626
import dotty.tools.dotc.{semanticdb => s}
2727
import dotty.tools.io.{AbstractFile, JarArchive}
28+
import dotty.tools.dotc.util.Property
29+
import dotty.tools.dotc.semanticdb.DiagnosticOps.*
30+
2831

2932
/** Extract symbol references and uses to semanticdb files.
3033
* See https://scalameta.org/docs/semanticdb/specification.html#symbol-1
3134
* for a description of the format.
32-
* TODO: Also extract type information
3335
*/
34-
class ExtractSemanticDB extends Phase:
35-
import Scala3.{_, given}
36+
class ExtractSemanticDB private (phaseMode: ExtractSemanticDB.PhaseMode, suffix: String, _key: Property.Key[TextDocument]) extends Phase:
3637

37-
override val phaseName: String = ExtractSemanticDB.name
38+
override def phaseName: String = ExtractSemanticDB.phaseNamePrefix + suffix
3839

3940
override val description: String = ExtractSemanticDB.description
4041

@@ -48,16 +49,94 @@ class ExtractSemanticDB extends Phase:
4849

4950
override def run(using Context): Unit =
5051
val unit = ctx.compilationUnit
51-
val extractor = Extractor()
52-
extractor.extract(unit.tpdTree)
53-
ExtractSemanticDB.write(unit.source, extractor.occurrences.toList, extractor.symbolInfos.toList, extractor.synthetics.toList)
52+
if (phaseMode == ExtractSemanticDB.PhaseMode.PostTyper)
53+
val extractor = ExtractSemanticDB.Extractor()
54+
extractor.extract(unit.tpdTree)
55+
unit.tpdTree.putAttachment(_key, extractor.toTextDocument(unit.source))
56+
else
57+
unit.tpdTree.getAttachment(_key) match
58+
case None => ???
59+
case Some(doc) =>
60+
val warnings = ctx.reporter.allWarnings.collect {
61+
case w if w.pos.source == ctx.source => w.toSemanticDiagnostic
62+
}
63+
ExtractSemanticDB.write(unit.source, doc.copy(diagnostics = warnings))
64+
end ExtractSemanticDB
65+
66+
object ExtractSemanticDB:
67+
import java.nio.file.Path
68+
import java.nio.file.Files
69+
import java.nio.file.Paths
70+
71+
val phaseNamePrefix: String = "extractSemanticDB"
72+
val description: String = "extract info into .semanticdb files"
73+
74+
enum PhaseMode:
75+
case PostTyper
76+
case PostInlining
77+
78+
/**
79+
* The key used to retrieve the "unused entity" analysis metadata,
80+
* from the compilation `Context`
81+
*/
82+
private val _key = Property.StickyKey[TextDocument]
83+
84+
class PostTyper extends ExtractSemanticDB(PhaseMode.PostTyper, "PostTyper", _key)
85+
86+
class PostInlining extends ExtractSemanticDB(PhaseMode.PostInlining, "PostInlining", _key)
87+
88+
private def semanticdbTarget(using Context): Option[Path] =
89+
Option(ctx.settings.semanticdbTarget.value)
90+
.filterNot(_.isEmpty)
91+
.map(Paths.get(_))
92+
93+
private def outputDirectory(using Context): AbstractFile = ctx.settings.outputDir.value
94+
95+
private def absolutePath(path: Path): Path = path.toAbsolutePath.normalize
96+
97+
private def write(
98+
source: SourceFile,
99+
doc: TextDocument
100+
)(using Context): Unit =
101+
val relPath = SourceFile.relativePath(source, ctx.settings.sourceroot.value)
102+
val outpath = absolutePath(semanticdbTarget.getOrElse(outputDirectory.jpath))
103+
.resolve("META-INF")
104+
.resolve("semanticdb")
105+
.resolve(relPath)
106+
.resolveSibling(source.name + ".semanticdb")
107+
Files.createDirectories(outpath.getParent())
108+
val docs = TextDocuments(List(doc))
109+
val out = Files.newOutputStream(outpath)
110+
try
111+
val stream = internal.SemanticdbOutputStream.newInstance(out)
112+
docs.writeTo(stream)
113+
stream.flush()
114+
finally
115+
out.close()
116+
end write
117+
54118

55119
/** Extractor of symbol occurrences from trees */
56-
class Extractor extends TreeTraverser:
120+
private class Extractor extends TreeTraverser:
121+
import Scala3.{_, given}
57122
given s.SemanticSymbolBuilder = s.SemanticSymbolBuilder()
58123
val synth = SyntheticsExtractor()
59124
given converter: s.TypeOps = s.TypeOps()
60125

126+
127+
def toTextDocument(source: SourceFile)(using Context): TextDocument =
128+
val relPath = SourceFile.relativePath(source, ctx.settings.sourceroot.value)
129+
TextDocument(
130+
schema = Schema.SEMANTICDB4,
131+
language = Language.SCALA,
132+
uri = Tools.mkURIstring(Paths.get(relPath)),
133+
text = "",
134+
md5 = internal.MD5.compute(String(source.content)),
135+
symbols = symbolInfos.toList,
136+
occurrences = occurrences.toList,
137+
synthetics = synthetics.toList,
138+
)
139+
61140
/** The bodies of synthetic locals */
62141
private val localBodies = mutable.HashMap[Symbol, Tree]()
63142

@@ -468,52 +547,5 @@ class ExtractSemanticDB extends Phase:
468547
registerSymbol(vparam.symbol, symkinds)
469548
traverse(vparam.tpt)
470549
tparams.foreach(tp => traverse(tp.rhs))
471-
472-
473-
object ExtractSemanticDB:
474-
import java.nio.file.Path
475-
import java.nio.file.Files
476-
import java.nio.file.Paths
477-
478-
val name: String = "extractSemanticDB"
479-
val description: String = "extract info into .semanticdb files"
480-
481-
private def semanticdbTarget(using Context): Option[Path] =
482-
Option(ctx.settings.semanticdbTarget.value)
483-
.filterNot(_.isEmpty)
484-
.map(Paths.get(_))
485-
486-
private def outputDirectory(using Context): AbstractFile = ctx.settings.outputDir.value
487-
488-
def write(
489-
source: SourceFile,
490-
occurrences: List[SymbolOccurrence],
491-
symbolInfos: List[SymbolInformation],
492-
synthetics: List[Synthetic],
493-
)(using Context): Unit =
494-
def absolutePath(path: Path): Path = path.toAbsolutePath.normalize
495-
val relPath = SourceFile.relativePath(source, ctx.settings.sourceroot.value)
496-
val outpath = absolutePath(semanticdbTarget.getOrElse(outputDirectory.jpath))
497-
.resolve("META-INF")
498-
.resolve("semanticdb")
499-
.resolve(relPath)
500-
.resolveSibling(source.name + ".semanticdb")
501-
Files.createDirectories(outpath.getParent())
502-
val doc: TextDocument = TextDocument(
503-
schema = Schema.SEMANTICDB4,
504-
language = Language.SCALA,
505-
uri = Tools.mkURIstring(Paths.get(relPath)),
506-
text = "",
507-
md5 = internal.MD5.compute(String(source.content)),
508-
symbols = symbolInfos,
509-
occurrences = occurrences,
510-
synthetics = synthetics,
511-
)
512-
val docs = TextDocuments(List(doc))
513-
val out = Files.newOutputStream(outpath)
514-
try
515-
val stream = internal.SemanticdbOutputStream.newInstance(out)
516-
docs.writeTo(stream)
517-
stream.flush()
518-
finally
519-
out.close()
550+
end Extractor
551+
end ExtractSemanticDB

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@ object Scala3:
484484

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

487+
given Ordering[Diagnostic] = (x, y) => compareRange(x.range, y.range)
488+
487489
given Ordering[Synthetic] = (x, y) => compareRange(x.range, y.range)
488490

489491
/**

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ object Tools:
6969
sb.append("Language => ").append(languageString(doc.language)).nl
7070
sb.append("Symbols => ").append(doc.symbols.length).append(" entries").nl
7171
sb.append("Occurrences => ").append(doc.occurrences.length).append(" entries").nl
72+
if doc.diagnostics.nonEmpty then
73+
sb.append("Diagnostics => ").append(doc.diagnostics.length).append(" entries").nl
7274
if doc.synthetics.nonEmpty then
7375
sb.append("Synthetics => ").append(doc.synthetics.length).append(" entries").nl
7476
sb.nl
@@ -78,6 +80,10 @@ object Tools:
7880
sb.append("Occurrences:").nl
7981
doc.occurrences.sorted.foreach(processOccurrence)
8082
sb.nl
83+
if doc.diagnostics.nonEmpty then
84+
sb.append("Diagnostics:").nl
85+
doc.diagnostics.sorted.foreach(d => processDiag(d))
86+
sb.nl
8187
if doc.synthetics.nonEmpty then
8288
sb.append("Synthetics:").nl
8389
doc.synthetics.sorted.foreach(s => processSynth(s, synthPrinter))
@@ -108,6 +114,20 @@ object Tools:
108114
private def processSynth(synth: Synthetic, printer: SyntheticPrinter)(using sb: StringBuilder): Unit =
109115
sb.append(printer.pprint(synth)).nl
110116

117+
private def processDiag(d: Diagnostic)(using sb: StringBuilder): Unit =
118+
d.range match
119+
case Some(range) => processRange(sb, range)
120+
case _ => sb.append("[):")
121+
sb.append(" ")
122+
d.severity match
123+
case Diagnostic.Severity.ERROR => sb.append("[error]")
124+
case Diagnostic.Severity.WARNING => sb.append("[warning]")
125+
case Diagnostic.Severity.INFORMATION => sb.append("[info]")
126+
case _ => sb.append("[unknown]")
127+
sb.append(" ")
128+
sb.append(d.message)
129+
sb.nl
130+
111131
private def processOccurrence(occ: SymbolOccurrence)(using sb: StringBuilder, sourceFile: SourceFile): Unit =
112132
occ.range match
113133
case Some(range) =>

tests/semanticdb/metac.expect

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,7 @@ Text => empty
923923
Language => Scala
924924
Symbols => 181 entries
925925
Occurrences => 148 entries
926+
Diagnostics => 1 entries
926927
Synthetics => 6 entries
927928

928929
Symbols:
@@ -1258,6 +1259,9 @@ Occurrences:
12581259
[68:9..68:16): Neptune <- _empty_/Enums.Planet.Neptune.
12591260
[68:25..68:31): Planet -> _empty_/Enums.Planet#
12601261

1262+
Diagnostics:
1263+
[48:13..48:13): [warning] `:` after symbolic operator is deprecated; use backticks around operator instead
1264+
12611265
Synthetics:
12621266
[52:9..52:13):Refl => *.unapply[Option[B]]
12631267
[52:31..52:50):identity[Option[B]] => *[Function1[A, Option[B]]]
@@ -1537,6 +1541,7 @@ Text => empty
15371541
Language => Scala
15381542
Symbols => 29 entries
15391543
Occurrences => 65 entries
1544+
Diagnostics => 1 entries
15401545
Synthetics => 3 entries
15411546

15421547
Symbols:
@@ -1637,6 +1642,11 @@ Occurrences:
16371642
[26:57..26:58): A -> a/b/Givens.foo().(A)
16381643
[26:59..26:64): empty -> a/b/Givens.Monoid#empty().
16391644

1645+
Diagnostics:
1646+
[24:53..24:63): [warning] An inline given alias with a function value as right-hand side can significantly increase
1647+
generated code size. You should either drop the `inline` or rewrite the given with an
1648+
explicit `apply` method.
1649+
16401650
Synthetics:
16411651
[12:17..12:25):sayHello => *[Int]
16421652
[13:19..13:29):sayGoodbye => *[Int]

0 commit comments

Comments
 (0)