Skip to content

Commit 5afe621

Browse files
authored
use new zinc 1.8 api for VirtualFile (#18137)
this prepares the compiler to be able to call new API's in Zinc, by implementing infrastructure to call back into Zinc with `VirtualFile`. Also remains compatible with Zinc 1.3, to do this we introduce a new interface `dotty.tools.dotc.sbt.interfaces.IncrementalCallback`, this abstracts over `VirtualFile`, and so the sbt-bridge handles the entire mapping between `VirtualFile` to `AbstractFile`, and then back from `SourceFile` to either `VirtualFile` or `java.io.File`, depending on the implementation. Even if we remove Zinc 1.3 support, we probably want to keep the new interface because all the VirtualFile mapping logic can be contained in sbt-bridge
2 parents 19dbbf5 + e3de91c commit 5afe621

28 files changed

+500
-205
lines changed

compiler/src/dotty/tools/backend/jvm/CodeGen.scala

+5-7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import scala.tools.asm
3030
import scala.tools.asm.tree._
3131
import tpd._
3232
import dotty.tools.io.AbstractFile
33+
import dotty.tools.dotc.util
3334
import dotty.tools.dotc.util.NoSourcePosition
3435

3536

@@ -106,7 +107,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)(
106107
}
107108

108109
// Creates a callback that will be evaluated in PostProcessor after creating a file
109-
private def onFileCreated(cls: ClassNode, claszSymbol: Symbol, sourceFile: interfaces.SourceFile): AbstractFile => Unit = clsFile => {
110+
private def onFileCreated(cls: ClassNode, claszSymbol: Symbol, sourceFile: util.SourceFile): AbstractFile => Unit = clsFile => {
110111
val (fullClassName, isLocal) = atPhase(sbtExtractDependenciesPhase) {
111112
(ExtractDependencies.classNameAsString(claszSymbol), claszSymbol.isLocal)
112113
}
@@ -115,12 +116,9 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)(
115116
if (ctx.compilerCallback != null)
116117
ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(clsFile), className)
117118

118-
if (ctx.sbtCallback != null) {
119-
val jSourceFile = sourceFile.jfile.orElse(null)
120-
val cb = ctx.sbtCallback
121-
if (isLocal) cb.generatedLocalClass(jSourceFile, clsFile.file)
122-
else cb.generatedNonLocalClass(jSourceFile, clsFile.file, className, fullClassName)
123-
}
119+
ctx.withIncCallback: cb =>
120+
if (isLocal) cb.generatedLocalClass(sourceFile, clsFile.jpath)
121+
else cb.generatedNonLocalClass(sourceFile, clsFile.jpath, className, fullClassName)
124122
}
125123

126124
/** Convert a `dotty.tools.io.AbstractFile` into a

compiler/src/dotty/tools/dotc/core/Contexts.scala

+24-14
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,25 @@ import scala.annotation.internal.sharable
3434

3535
import DenotTransformers.DenotTransformer
3636
import dotty.tools.dotc.profile.Profiler
37+
import dotty.tools.dotc.sbt.interfaces.IncrementalCallback
3738
import util.Property.Key
3839
import util.Store
39-
import xsbti.AnalysisCallback
4040
import plugins._
4141
import java.util.concurrent.atomic.AtomicInteger
4242
import java.nio.file.InvalidPathException
4343

4444
object Contexts {
4545

46-
private val (compilerCallbackLoc, store1) = Store.empty.newLocation[CompilerCallback]()
47-
private val (sbtCallbackLoc, store2) = store1.newLocation[AnalysisCallback]()
48-
private val (printerFnLoc, store3) = store2.newLocation[Context => Printer](new RefinedPrinter(_))
49-
private val (settingsStateLoc, store4) = store3.newLocation[SettingsState]()
50-
private val (compilationUnitLoc, store5) = store4.newLocation[CompilationUnit]()
51-
private val (runLoc, store6) = store5.newLocation[Run | Null]()
52-
private val (profilerLoc, store7) = store6.newLocation[Profiler]()
53-
private val (notNullInfosLoc, store8) = store7.newLocation[List[NotNullInfo]]()
54-
private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null]()
55-
private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner](TypeAssigner)
46+
private val (compilerCallbackLoc, store1) = Store.empty.newLocation[CompilerCallback]()
47+
private val (incCallbackLoc, store2) = store1.newLocation[IncrementalCallback | Null]()
48+
private val (printerFnLoc, store3) = store2.newLocation[Context => Printer](new RefinedPrinter(_))
49+
private val (settingsStateLoc, store4) = store3.newLocation[SettingsState]()
50+
private val (compilationUnitLoc, store5) = store4.newLocation[CompilationUnit]()
51+
private val (runLoc, store6) = store5.newLocation[Run | Null]()
52+
private val (profilerLoc, store7) = store6.newLocation[Profiler]()
53+
private val (notNullInfosLoc, store8) = store7.newLocation[List[NotNullInfo]]()
54+
private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null]()
55+
private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner](TypeAssigner)
5656

5757
private val initialStore = store10
5858

@@ -164,8 +164,18 @@ object Contexts {
164164
/** The compiler callback implementation, or null if no callback will be called. */
165165
def compilerCallback: CompilerCallback = store(compilerCallbackLoc)
166166

167-
/** The sbt callback implementation if we are run from sbt, null otherwise */
168-
def sbtCallback: AnalysisCallback = store(sbtCallbackLoc)
167+
/** The Zinc callback implementation if we are run from Zinc, null otherwise */
168+
def incCallback: IncrementalCallback | Null = store(incCallbackLoc)
169+
170+
/** Run `op` if there exists an incremental callback */
171+
inline def withIncCallback(inline op: IncrementalCallback => Unit): Unit =
172+
val local = incCallback
173+
if local != null then op(local)
174+
175+
def runZincPhases: Boolean =
176+
def forceRun = settings.YdumpSbtInc.value || settings.YforceSbtPhases.value
177+
val local = incCallback
178+
local != null && local.enabled || forceRun
169179

170180
/** The current plain printer */
171181
def printerFn: Context => Printer = store(printerFnLoc)
@@ -664,7 +674,7 @@ object Contexts {
664674
}
665675

666676
def setCompilerCallback(callback: CompilerCallback): this.type = updateStore(compilerCallbackLoc, callback)
667-
def setSbtCallback(callback: AnalysisCallback): this.type = updateStore(sbtCallbackLoc, callback)
677+
def setIncCallback(callback: IncrementalCallback): this.type = updateStore(incCallbackLoc, callback)
668678
def setPrinterFn(printer: Context => Printer): this.type = updateStore(printerFnLoc, printer)
669679
def setSettings(settingsState: SettingsState): this.type = updateStore(settingsStateLoc, settingsState)
670680
def setRun(run: Run | Null): this.type = updateStore(runLoc, run)

compiler/src/dotty/tools/dotc/sbt/APIUtils.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ object APIUtils {
3535
* a dummy empty class can be registered instead, using this method.
3636
*/
3737
def registerDummyClass(classSym: ClassSymbol)(using Context): Unit = {
38-
if (ctx.sbtCallback != null) {
38+
ctx.withIncCallback { cb =>
3939
val classLike = emptyClassLike(classSym)
40-
ctx.sbtCallback.api(ctx.compilationUnit.source.file.file, classLike)
40+
cb.api(ctx.compilationUnit.source, classLike)
4141
}
4242
}
4343

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

+9-11
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ class ExtractAPI extends Phase {
4949
override def description: String = ExtractAPI.description
5050

5151
override def isRunnable(using Context): Boolean = {
52-
def forceRun = ctx.settings.YdumpSbtInc.value || ctx.settings.YforceSbtPhases.value
53-
super.isRunnable && (ctx.sbtCallback != null || forceRun)
52+
super.isRunnable && ctx.runZincPhases
5453
}
5554

5655
// Check no needed. Does not transform trees
@@ -65,28 +64,27 @@ class ExtractAPI extends Phase {
6564

6665
override def run(using Context): Unit = {
6766
val unit = ctx.compilationUnit
68-
val sourceFile = unit.source.file
69-
if (ctx.sbtCallback != null)
70-
ctx.sbtCallback.startSource(sourceFile.file)
67+
val sourceFile = unit.source
68+
ctx.withIncCallback: cb =>
69+
cb.startSource(sourceFile)
7170

7271
val apiTraverser = new ExtractAPICollector
7372
val classes = apiTraverser.apiSource(unit.tpdTree)
7473
val mainClasses = apiTraverser.mainClasses
7574

7675
if (ctx.settings.YdumpSbtInc.value) {
7776
// Append to existing file that should have been created by ExtractDependencies
78-
val pw = new PrintWriter(File(sourceFile.jpath).changeExtension("inc").toFile
77+
val pw = new PrintWriter(File(sourceFile.file.jpath).changeExtension("inc").toFile
7978
.bufferedWriter(append = true), true)
8079
try {
8180
classes.foreach(source => pw.println(DefaultShowAPI(source)))
8281
} finally pw.close()
8382
}
8483

85-
if ctx.sbtCallback != null &&
86-
!ctx.compilationUnit.suspendedAtInliningPhase // already registered before this unit was suspended
87-
then
88-
classes.foreach(ctx.sbtCallback.api(sourceFile.file, _))
89-
mainClasses.foreach(ctx.sbtCallback.mainClass(sourceFile.file, _))
84+
ctx.withIncCallback: cb =>
85+
if !ctx.compilationUnit.suspendedAtInliningPhase then // already registered before this unit was suspended
86+
classes.foreach(cb.api(sourceFile, _))
87+
mainClasses.foreach(cb.mainClass(sourceFile, _))
9088
}
9189
}
9290

compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala

+13-22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package sbt
44
import scala.language.unsafeNulls
55

66
import java.io.File
7+
import java.nio.file.Path
78
import java.util.{Arrays, EnumSet}
89

910
import dotty.tools.dotc.ast.tpd
@@ -55,8 +56,7 @@ class ExtractDependencies extends Phase {
5556
override def description: String = ExtractDependencies.description
5657

5758
override def isRunnable(using Context): Boolean = {
58-
def forceRun = ctx.settings.YdumpSbtInc.value || ctx.settings.YforceSbtPhases.value
59-
super.isRunnable && (ctx.sbtCallback != null || forceRun)
59+
super.isRunnable && ctx.runZincPhases
6060
}
6161

6262
// Check no needed. Does not transform trees
@@ -91,18 +91,16 @@ class ExtractDependencies extends Phase {
9191
} finally pw.close()
9292
}
9393

94-
if (ctx.sbtCallback != null) {
94+
ctx.withIncCallback: cb =>
9595
collector.usedNames.foreach {
9696
case (clazz, usedNames) =>
9797
val className = classNameAsString(clazz)
9898
usedNames.names.foreach {
9999
case (usedName, scopes) =>
100-
ctx.sbtCallback.usedName(className, usedName.toString, scopes)
100+
cb.usedName(className, usedName.toString, scopes)
101101
}
102102
}
103-
104103
collector.dependencies.foreach(recordDependency)
105-
}
106104
}
107105

108106
/*
@@ -112,27 +110,20 @@ class ExtractDependencies extends Phase {
112110
*/
113111
def recordDependency(dep: ClassDependency)(using Context): Unit = {
114112
val fromClassName = classNameAsString(dep.from)
115-
val sourceFile = ctx.compilationUnit.source.file.file
113+
val sourceFile = ctx.compilationUnit.source
116114

117-
def binaryDependency(file: File, binaryClassName: String) =
118-
ctx.sbtCallback.binaryDependency(file, binaryClassName, fromClassName, sourceFile, dep.context)
115+
def binaryDependency(file: Path, binaryClassName: String) =
116+
ctx.withIncCallback(_.binaryDependency(file, binaryClassName, fromClassName, sourceFile, dep.context))
119117

120118
def processExternalDependency(depFile: AbstractFile, binaryClassName: String) = {
121119
depFile match {
122120
case ze: ZipArchive#Entry => // The dependency comes from a JAR
123121
ze.underlyingSource match
124-
case Some(zip) if zip.file != null =>
125-
binaryDependency(zip.file, binaryClassName)
122+
case Some(zip) if zip.jpath != null =>
123+
binaryDependency(zip.jpath, binaryClassName)
126124
case _ =>
127-
case pf: PlainFile => // The dependency comes from a class file
128-
// FIXME: pf.file is null for classfiles coming from the modulepath
129-
// (handled by JrtClassPath) because they cannot be represented as
130-
// java.io.File, since the `binaryDependency` callback must take a
131-
// java.io.File, this means that we cannot record dependencies coming
132-
// from the modulepath. For now this isn't a big deal since we only
133-
// support having the standard Java library on the modulepath.
134-
if pf.file != null then
135-
binaryDependency(pf.file, binaryClassName)
125+
case pf: PlainFile => // The dependency comes from a class file, Zinc handles JRT filesystem
126+
binaryDependency(pf.jpath, binaryClassName)
136127
case _ =>
137128
internalError(s"Ignoring dependency $depFile of unknown class ${depFile.getClass}}", dep.from.srcPos)
138129
}
@@ -148,11 +139,11 @@ class ExtractDependencies extends Phase {
148139
if (depClassFile != null) {
149140
// Dependency is external -- source is undefined
150141
processExternalDependency(depClassFile, dep.to.binaryClassName)
151-
} else if (allowLocal || depFile.file != sourceFile) {
142+
} else if (allowLocal || depFile != sourceFile.file) {
152143
// We cannot ignore dependencies coming from the same source file because
153144
// the dependency info needs to propagate. See source-dependencies/trait-trait-211.
154145
val toClassName = classNameAsString(dep.to)
155-
ctx.sbtCallback.classDependency(toClassName, fromClassName, dep.context)
146+
ctx.withIncCallback(_.classDependency(toClassName, fromClassName, dep.context))
156147
}
157148
}
158149
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dotty.tools.dotc.sbt.interfaces;
2+
3+
import dotty.tools.dotc.util.SourceFile;
4+
5+
import java.util.EnumSet;
6+
import java.nio.file.Path;
7+
8+
/* User code should not implement this interface, it is intended to be a wrapper around xsbti.AnalysisCallback. */
9+
public interface IncrementalCallback {
10+
default void api(SourceFile sourceFile, xsbti.api.ClassLike classApi) {
11+
}
12+
13+
default void startSource(SourceFile sourceFile) {
14+
}
15+
16+
default void mainClass(SourceFile sourceFile, String className) {
17+
}
18+
19+
default boolean enabled() {
20+
return false;
21+
}
22+
23+
default void usedName(String className, String name, EnumSet<xsbti.UseScope> useScopes) {
24+
}
25+
26+
default void binaryDependency(Path onBinaryEntry, String onBinaryClassName, String fromClassName,
27+
SourceFile fromSourceFile, xsbti.api.DependencyContext context) {
28+
}
29+
30+
default void classDependency(String onClassName, String sourceClassName, xsbti.api.DependencyContext context) {
31+
}
32+
33+
default void generatedLocalClass(SourceFile source, Path classFile) {
34+
}
35+
36+
default void generatedNonLocalClass(SourceFile source, Path classFile, String binaryClassName,
37+
String srcClassName) {
38+
}
39+
}

project/Build.scala

+35-6
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,33 @@ object Build {
527527
recur(lines, false)
528528
}
529529

530+
/** replace imports of `com.google.protobuf.*` with compiler implemented version */
531+
def replaceProtobuf(lines: List[String]): List[String] = {
532+
def recur(ls: List[String]): List[String] = ls match {
533+
case l :: rest =>
534+
val lt = l.trim()
535+
if (lt.isEmpty || lt.startsWith("package ") || lt.startsWith("import ")) {
536+
val newLine =
537+
if (lt.startsWith("import com.google.protobuf.")) {
538+
if (lt == "import com.google.protobuf.CodedInputStream") {
539+
"import dotty.tools.dotc.semanticdb.internal.SemanticdbInputStream as CodedInputStream"
540+
} else if (lt == "import com.google.protobuf.CodedOutputStream") {
541+
"import dotty.tools.dotc.semanticdb.internal.SemanticdbOutputStream as CodedOutputStream"
542+
} else {
543+
l
544+
}
545+
} else {
546+
l
547+
}
548+
newLine :: recur(rest)
549+
} else {
550+
ls // don't check rest of file
551+
}
552+
case _ => ls
553+
}
554+
recur(lines)
555+
}
556+
530557
// Settings shared between scala3-compiler and scala3-compiler-bootstrapped
531558
lazy val commonDottyCompilerSettings = Seq(
532559
// Note: bench/profiles/projects.yml should be updated accordingly.
@@ -556,7 +583,7 @@ object Build {
556583
// get libraries onboard
557584
libraryDependencies ++= Seq(
558585
"org.scala-lang.modules" % "scala-asm" % "9.5.0-scala-1", // used by the backend
559-
Dependencies.oldCompilerInterface, // we stick to the old version to avoid deprecation warnings
586+
Dependencies.compilerInterface,
560587
"org.jline" % "jline-reader" % "3.19.0", // used by the REPL
561588
"org.jline" % "jline-terminal" % "3.19.0",
562589
"org.jline" % "jline-terminal-jna" % "3.19.0", // needed for Windows
@@ -673,7 +700,8 @@ object Build {
673700
val dottyTastyInspector = jars("scala3-tasty-inspector")
674701
val dottyInterfaces = jars("scala3-interfaces")
675702
val tastyCore = jars("tasty-core")
676-
run(insertClasspathInArgs(args1, List(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore).mkString(File.pathSeparator)))
703+
val compilerInterface = findArtifactPath(externalDeps, "compiler-interface")
704+
run(insertClasspathInArgs(args1, List(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore, compilerInterface).mkString(File.pathSeparator)))
677705
} else run(args)
678706
},
679707

@@ -712,7 +740,8 @@ object Build {
712740
val dottyTastyInspector = jars("scala3-tasty-inspector")
713741
val tastyCore = jars("tasty-core")
714742
val asm = findArtifactPath(externalDeps, "scala-asm")
715-
extraClasspath ++= Seq(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore)
743+
val compilerInterface = findArtifactPath(externalDeps, "compiler-interface")
744+
extraClasspath ++= Seq(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore, compilerInterface)
716745
}
717746

718747
val fullArgs = main :: (if (printTasty) args else insertClasspathInArgs(args, extraClasspath.mkString(File.pathSeparator)))
@@ -1137,8 +1166,7 @@ object Build {
11371166
// when sbt reads the settings.
11381167
Test / test := (LocalProject("scala3-sbt-bridge-tests") / Test / test).value,
11391168

1140-
// The `newCompilerInterface` is backward compatible with the `oldCompilerInterface`
1141-
libraryDependencies += Dependencies.newCompilerInterface % Provided
1169+
libraryDependencies += Dependencies.compilerInterface % Provided
11421170
)
11431171

11441172
// We use a separate project for the bridge tests since they can only be run
@@ -1220,7 +1248,8 @@ object Build {
12201248
val mtagsSharedSources = (targetDir ** "*.scala").get.toSet
12211249
mtagsSharedSources.foreach(f => {
12221250
val lines = IO.readLines(f)
1223-
IO.writeLines(f, insertUnsafeNullsImport(lines))
1251+
val substitutions = (replaceProtobuf(_)) andThen (insertUnsafeNullsImport(_))
1252+
IO.writeLines(f, substitutions(lines))
12241253
})
12251254
mtagsSharedSources
12261255
} (Set(mtagsSharedSourceJar)).toSeq

project/Dependencies.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,5 @@ object Dependencies {
2828
"com.vladsch.flexmark" % "flexmark-ext-yaml-front-matter" % flexmarkVersion,
2929
)
3030

31-
val newCompilerInterface = "org.scala-sbt" % "compiler-interface" % "1.9.0"
32-
val oldCompilerInterface = "org.scala-sbt" % "compiler-interface" % "1.3.5"
31+
val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.9.0"
3332
}

0 commit comments

Comments
 (0)