From a7cec5fcbd018e900a7cabdbc90b60b6d7695530 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 4 Jul 2023 13:49:03 +0200 Subject: [PATCH] use new zinc api for virtualfile --- .../src/dotty/tools/backend/jvm/CodeGen.scala | 10 ++-- .../tools/dotc/config/ScalaSettings.scala | 1 + .../src/dotty/tools/dotc/core/Contexts.scala | 32 ++++++++----- .../src/dotty/tools/dotc/sbt/APIUtils.scala | 2 +- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 10 ++-- .../tools/dotc/sbt/ExtractDependencies.scala | 26 ++++++---- .../dotty/tools/dotc/util/SourceFile.scala | 27 +++++++++++ .../src/dotty/tools/io/AbstractFile.scala | 3 ++ project/Build.scala | 11 +++-- project/Dependencies.scala | 3 +- .../tools/xsbt/CompilerBridgeDriver.java | 48 ++++++++++++------- .../dotty/tools/xsbt/DelegatingReporter.java | 14 ++++-- .../src/dotty/tools/xsbt/PositionBridge.java | 21 +++----- sbt-bridge/src/dotty/tools/xsbt/Problem.java | 19 +++++--- .../src/dotty/tools/xsbt/ZincPlainFile.java | 3 +- .../src/dotty/tools/xsbt/ZincVirtualFile.java | 22 ++++----- sbt-bridge/src/xsbt/CachedCompilerImpl.java | 2 +- sbt-bridge/src/xsbt/DottydocRunner.java | 2 +- .../xsbt/ScalaCompilerForUnitTesting.scala | 13 +++-- sbt-bridge/test/xsbt/TestVirtualFile.scala | 14 +++++- sbt-bridge/test/xsbti/TestCallback.scala | 31 ++++++------ sbt-test/sbt-bridge/zinc-13-compat/test | 2 +- .../compiler-plugin/plugin/DivideZero.scala | 4 +- .../compactify/src/main/scala/Nested.scala | 4 +- sbt-test/source-dependencies/compactify/test | 2 +- 25 files changed, 198 insertions(+), 128 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index c9f9e4e23d90..fdd104951c20 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -30,7 +30,7 @@ import scala.tools.asm import scala.tools.asm.tree._ import tpd._ import dotty.tools.io.AbstractFile -import dotty.tools.dotc.util.NoSourcePosition +import dotty.tools.dotc.util.{NoSourcePosition, SourceFile} class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( val bTypes: BTypesFromSymbols[int.type]) { self => @@ -106,7 +106,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( } // Creates a callback that will be evaluated in PostProcessor after creating a file - private def onFileCreated(cls: ClassNode, claszSymbol: Symbol, sourceFile: interfaces.SourceFile): AbstractFile => Unit = clsFile => { + private def onFileCreated(cls: ClassNode, claszSymbol: Symbol, sourceFile: SourceFile): AbstractFile => Unit = clsFile => { val (fullClassName, isLocal) = atPhase(sbtExtractDependenciesPhase) { (ExtractDependencies.classNameAsString(claszSymbol), claszSymbol.isLocal) } @@ -116,10 +116,10 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(clsFile), className) if (ctx.sbtCallback != null) { - val jSourceFile = sourceFile.jfile.orElse(null) + val jSourceFile = sourceFile.underlyingZincFile val cb = ctx.sbtCallback - if (isLocal) cb.generatedLocalClass(jSourceFile, clsFile.file) - else cb.generatedNonLocalClass(jSourceFile, clsFile.file, className, fullClassName) + if (isLocal) cb.generatedLocalClass(jSourceFile, clsFile.jpath) + else cb.generatedNonLocalClass(jSourceFile, clsFile.jpath, className, fullClassName) } } diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 27389736757a..6672a13fdd16 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -325,6 +325,7 @@ private sealed trait YSettings: val YdebugTypeError: Setting[Boolean] = BooleanSetting("-Ydebug-type-error", "Print the stack trace when a TypeError is caught", false) val YdebugError: Setting[Boolean] = BooleanSetting("-Ydebug-error", "Print the stack trace when any error is caught.", false) val YdebugUnpickling: Setting[Boolean] = BooleanSetting("-Ydebug-unpickling", "Print the stack trace when an error occurs when reading Tasty.", false) + val YdebugVirtualFiles: Setting[Boolean] = BooleanSetting("-Ydebug-virtual-files", "Debug usage of virtual files, e.g. remote cache in sbt", false) val YtermConflict: Setting[String] = ChoiceSetting("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error") val Ylog: Setting[List[String]] = PhasesSetting("-Ylog", "Log operations during") val YlogClasspath: Setting[Boolean] = BooleanSetting("-Ylog-classpath", "Output information about what classpath is being applied.") diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index e0e43169820a..9c7da5de1947 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -39,22 +39,25 @@ import util.Store import xsbti.AnalysisCallback import plugins._ import java.util.concurrent.atomic.AtomicInteger +import java.util.Map as JMap import java.nio.file.InvalidPathException + object Contexts { - private val (compilerCallbackLoc, store1) = Store.empty.newLocation[CompilerCallback]() - private val (sbtCallbackLoc, store2) = store1.newLocation[AnalysisCallback]() - private val (printerFnLoc, store3) = store2.newLocation[Context => Printer](new RefinedPrinter(_)) - private val (settingsStateLoc, store4) = store3.newLocation[SettingsState]() - private val (compilationUnitLoc, store5) = store4.newLocation[CompilationUnit]() - private val (runLoc, store6) = store5.newLocation[Run | Null]() - private val (profilerLoc, store7) = store6.newLocation[Profiler]() - private val (notNullInfosLoc, store8) = store7.newLocation[List[NotNullInfo]]() - private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null]() - private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner](TypeAssigner) + private val (compilerCallbackLoc, store1) = Store.empty.newLocation[CompilerCallback]() + private val (sbtCallbackLoc, store2) = store1.newLocation[AnalysisCallback]() + private val (printerFnLoc, store3) = store2.newLocation[Context => Printer](new RefinedPrinter(_)) + private val (settingsStateLoc, store4) = store3.newLocation[SettingsState]() + private val (compilationUnitLoc, store5) = store4.newLocation[CompilationUnit]() + private val (runLoc, store6) = store5.newLocation[Run | Null]() + private val (profilerLoc, store7) = store6.newLocation[Profiler]() + private val (notNullInfosLoc, store8) = store7.newLocation[List[NotNullInfo]]() + private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null]() + private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner](TypeAssigner) + private val (zincVirtualFilesLoc, store11) = store10.newLocation[JMap[String, xsbti.VirtualFile] | Null]() - private val initialStore = store10 + private val initialStore = store11 /** The current context */ inline def ctx(using ctx: Context): Context = ctx @@ -164,9 +167,12 @@ object Contexts { /** The compiler callback implementation, or null if no callback will be called. */ def compilerCallback: CompilerCallback = store(compilerCallbackLoc) - /** The sbt callback implementation if we are run from sbt, null otherwise */ + /** The Zinc callback implementation if we are run from Zinc, null otherwise */ def sbtCallback: AnalysisCallback = store(sbtCallbackLoc) + /** A map from absolute path to VirtualFile if we are run from Zinc, null otherwise */ + def zincVirtualFiles: JMap[String, xsbti.VirtualFile] | Null = store(zincVirtualFilesLoc) + /** The current plain printer */ def printerFn: Context => Printer = store(printerFnLoc) @@ -665,6 +671,8 @@ object Contexts { def setCompilerCallback(callback: CompilerCallback): this.type = updateStore(compilerCallbackLoc, callback) def setSbtCallback(callback: AnalysisCallback): this.type = updateStore(sbtCallbackLoc, callback) + def setZincVirtualFiles(map: JMap[String, xsbti.VirtualFile]): this.type = + updateStore(zincVirtualFilesLoc, map) def setPrinterFn(printer: Context => Printer): this.type = updateStore(printerFnLoc, printer) def setSettings(settingsState: SettingsState): this.type = updateStore(settingsStateLoc, settingsState) def setRun(run: Run | Null): this.type = updateStore(runLoc, run) diff --git a/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala b/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala index aa98f79c8e3b..2c82d3d57b11 100644 --- a/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala +++ b/compiler/src/dotty/tools/dotc/sbt/APIUtils.scala @@ -37,7 +37,7 @@ object APIUtils { def registerDummyClass(classSym: ClassSymbol)(using Context): Unit = { if (ctx.sbtCallback != null) { val classLike = emptyClassLike(classSym) - ctx.sbtCallback.api(ctx.compilationUnit.source.file.file, classLike) + ctx.sbtCallback.api(ctx.compilationUnit.source.underlyingZincFile, classLike) } } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index f54baeb7256c..b4adf30ebb4b 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -65,9 +65,9 @@ class ExtractAPI extends Phase { override def run(using Context): Unit = { val unit = ctx.compilationUnit - val sourceFile = unit.source.file + val sourceFile = unit.source if (ctx.sbtCallback != null) - ctx.sbtCallback.startSource(sourceFile.file) + ctx.sbtCallback.startSource(sourceFile.underlyingZincFile) val apiTraverser = new ExtractAPICollector val classes = apiTraverser.apiSource(unit.tpdTree) @@ -75,7 +75,7 @@ class ExtractAPI extends Phase { if (ctx.settings.YdumpSbtInc.value) { // Append to existing file that should have been created by ExtractDependencies - val pw = new PrintWriter(File(sourceFile.jpath).changeExtension("inc").toFile + val pw = new PrintWriter(File(sourceFile.file.jpath).changeExtension("inc").toFile .bufferedWriter(append = true), true) try { classes.foreach(source => pw.println(DefaultShowAPI(source))) @@ -85,8 +85,8 @@ class ExtractAPI extends Phase { if ctx.sbtCallback != null && !ctx.compilationUnit.suspendedAtInliningPhase // already registered before this unit was suspended then - classes.foreach(ctx.sbtCallback.api(sourceFile.file, _)) - mainClasses.foreach(ctx.sbtCallback.mainClass(sourceFile.file, _)) + classes.foreach(ctx.sbtCallback.api(sourceFile.underlyingZincFile, _)) + mainClasses.foreach(ctx.sbtCallback.mainClass(sourceFile.underlyingZincFile, _)) } } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index 5e7bedba5e4b..cf886f553ff0 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -4,6 +4,7 @@ package sbt import scala.language.unsafeNulls import java.io.File +import java.nio.file.Path import java.util.{Arrays, EnumSet} import dotty.tools.dotc.ast.tpd @@ -19,6 +20,7 @@ import dotty.tools.dotc.core.Denotations.StaleSymbol import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.transform.SymUtils._ import dotty.tools.dotc.util.{SrcPos, NoSourcePosition} +import dotty.tools.uncheckedNN import dotty.tools.io import dotty.tools.io.{AbstractFile, PlainFile, ZipArchive} import xsbti.UseScope @@ -56,7 +58,7 @@ class ExtractDependencies extends Phase { override def isRunnable(using Context): Boolean = { def forceRun = ctx.settings.YdumpSbtInc.value || ctx.settings.YforceSbtPhases.value - super.isRunnable && (ctx.sbtCallback != null || forceRun) + super.isRunnable && (ctx.sbtCallback != null && ctx.sbtCallback.enabled() || forceRun) } // Check no needed. Does not transform trees @@ -91,7 +93,7 @@ class ExtractDependencies extends Phase { } finally pw.close() } - if (ctx.sbtCallback != null) { + if (ctx.sbtCallback != null && ctx.sbtCallback.enabled()) { collector.usedNames.foreach { case (clazz, usedNames) => val className = classNameAsString(clazz) @@ -112,17 +114,17 @@ class ExtractDependencies extends Phase { */ def recordDependency(dep: ClassDependency)(using Context): Unit = { val fromClassName = classNameAsString(dep.from) - val sourceFile = ctx.compilationUnit.source.file.file + val zincSourceFile = ctx.compilationUnit.source.underlyingZincFile - def binaryDependency(file: File, binaryClassName: String) = - ctx.sbtCallback.binaryDependency(file, binaryClassName, fromClassName, sourceFile, dep.context) + def binaryDependency(file: Path, binaryClassName: String) = + ctx.sbtCallback.binaryDependency(file, binaryClassName, fromClassName, zincSourceFile, dep.context) def processExternalDependency(depFile: AbstractFile, binaryClassName: String) = { depFile match { case ze: ZipArchive#Entry => // The dependency comes from a JAR ze.underlyingSource match - case Some(zip) if zip.file != null => - binaryDependency(zip.file, binaryClassName) + case Some(zip) if zip.jpath != null => + binaryDependency(zip.jpath, binaryClassName) case _ => case pf: PlainFile => // The dependency comes from a class file // FIXME: pf.file is null for classfiles coming from the modulepath @@ -131,8 +133,8 @@ class ExtractDependencies extends Phase { // java.io.File, this means that we cannot record dependencies coming // from the modulepath. For now this isn't a big deal since we only // support having the standard Java library on the modulepath. - if pf.file != null then - binaryDependency(pf.file, binaryClassName) + if pf.jpath != null then + binaryDependency(pf.jpath, binaryClassName) case _ => internalError(s"Ignoring dependency $depFile of unknown class ${depFile.getClass}}", dep.from.srcPos) } @@ -140,6 +142,10 @@ class ExtractDependencies extends Phase { val depFile = dep.to.associatedFile if (depFile != null) { + def depIsSameSource = + val depVF: xsbti.VirtualFile | Null = ctx.zincVirtualFiles.uncheckedNN.get(depFile.absolutePath) + depVF != null && depVF.id() == zincSourceFile.id() + // Cannot ignore inheritance relationship coming from the same source (see sbt/zinc#417) def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance val depClassFile = @@ -148,7 +154,7 @@ class ExtractDependencies extends Phase { if (depClassFile != null) { // Dependency is external -- source is undefined processExternalDependency(depClassFile, dep.to.binaryClassName) - } else if (allowLocal || depFile.file != sourceFile) { + } else if (allowLocal || !depIsSameSource /* old: depFile.file != sourceFile.file */) { // We cannot ignore dependencies coming from the same source file because // the dependency info needs to propagate. See source-dependencies/trait-trait-211. val toClassName = classNameAsString(dep.to) diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index 3462036d7ba6..ca307104bad4 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -64,6 +64,33 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends import SourceFile._ private var myContent: Array[Char] | Null = null + private var myUnderlyingZincFile: xsbti.VirtualFile | Null = null + + def underlyingZincFile(using Context): xsbti.VirtualFile = + val local = myUnderlyingZincFile + if local == null then + // usually without -sourcepath then the `underlying` will be set by Zinc. + val maybeUnderlying = file.underlying + val underlying0 = + if maybeUnderlying == null then + // When we have `-sourcepath` set then the file could come from the filesystem, + // rather than a zinc managed file, so then we need to check if we have a virtual file for it. + // TODO: we should consider in the future if there is a use case for sourcepath to possibly be + // made of virtual files. + val fromLookup = ctx.zincVirtualFiles.uncheckedNN.get(file.absolutePath) + if fromLookup != null then + fromLookup + else + sys.error(s"no underlying file for ${file.absolutePath}, possible paths = ${ctx.zincVirtualFiles.keySet}") + else maybeUnderlying + if ctx.settings.YdebugVirtualFiles.value then + val isVirtual = !underlying0.isInstanceOf[xsbti.PathBasedFile] + println(s"found underlying zinc file ${underlying0.id} for ${file.absolutePath} [virtual = $isVirtual]") + + myUnderlyingZincFile = underlying0 + underlying0 + else + local /** The contents of the original source file. Note that this can be empty, for example when * the source is read from Tasty. */ diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index 09779953fc76..fd9ce4c76faf 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -118,6 +118,9 @@ abstract class AbstractFile extends Iterable[AbstractFile] { /** Returns the underlying Path if any and null otherwise. */ def jpath: JPath + /** Overridden in sbt-bridge ZincPlainFile and ZincVirtualFile */ + def underlying: xsbti.VirtualFile | Null = null + /** An underlying source, if known. Mostly, a zip/jar file. */ def underlyingSource: Option[AbstractFile] = None diff --git a/project/Build.scala b/project/Build.scala index 677532fd7cef..2e2bff082805 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -556,7 +556,7 @@ object Build { // get libraries onboard libraryDependencies ++= Seq( "org.scala-lang.modules" % "scala-asm" % "9.5.0-scala-1", // used by the backend - Dependencies.oldCompilerInterface, // we stick to the old version to avoid deprecation warnings + Dependencies.compilerInterface, "org.jline" % "jline-reader" % "3.19.0", // used by the REPL "org.jline" % "jline-terminal" % "3.19.0", "org.jline" % "jline-terminal-jna" % "3.19.0", // needed for Windows @@ -673,7 +673,8 @@ object Build { val dottyTastyInspector = jars("scala3-tasty-inspector") val dottyInterfaces = jars("scala3-interfaces") val tastyCore = jars("tasty-core") - run(insertClasspathInArgs(args1, List(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore).mkString(File.pathSeparator))) + val compilerInterface = findArtifactPath(externalDeps, "compiler-interface") + run(insertClasspathInArgs(args1, List(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore, compilerInterface).mkString(File.pathSeparator))) } else run(args) }, @@ -712,7 +713,8 @@ object Build { val dottyTastyInspector = jars("scala3-tasty-inspector") val tastyCore = jars("tasty-core") val asm = findArtifactPath(externalDeps, "scala-asm") - extraClasspath ++= Seq(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore) + val compilerInterface = findArtifactPath(externalDeps, "compiler-interface") + extraClasspath ++= Seq(dottyCompiler, dottyInterfaces, asm, dottyStaging, dottyTastyInspector, tastyCore, compilerInterface) } val fullArgs = main :: (if (printTasty) args else insertClasspathInArgs(args, extraClasspath.mkString(File.pathSeparator))) @@ -1138,8 +1140,7 @@ object Build { // when sbt reads the settings. Test / test := (LocalProject("scala3-sbt-bridge-tests") / Test / test).value, - // The `newCompilerInterface` is backward compatible with the `oldCompilerInterface` - libraryDependencies += Dependencies.newCompilerInterface % Provided + libraryDependencies += Dependencies.compilerInterface % Provided ) // We use a separate project for the bridge tests since they can only be run diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f22601346803..4b2a9e2815be 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -28,6 +28,5 @@ object Dependencies { "com.vladsch.flexmark" % "flexmark-ext-yaml-front-matter" % flexmarkVersion, ) - val newCompilerInterface = "org.scala-sbt" % "compiler-interface" % "1.9.0" - val oldCompilerInterface = "org.scala-sbt" % "compiler-interface" % "1.3.5" + val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.9.0" } diff --git a/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java b/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java index 12291120b157..885feacf2767 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java +++ b/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.Comparator; +import java.util.HashMap; import java.util.Arrays; public class CompilerBridgeDriver extends Driver { @@ -51,14 +52,40 @@ public boolean sourcesRequired() { } synchronized public void run(VirtualFile[] sources, AnalysisCallback callback, Logger log, Reporter delegate) { - DelegatingReporter reporter = new DelegatingReporter(delegate); + // convert sources to a HashMap from this.id to itself + HashMap sourcesMap = new HashMap<>(); + + VirtualFile[] sortedSources = new VirtualFile[sources.length]; + System.arraycopy(sources, 0, sortedSources, 0, sources.length); + Arrays.sort(sortedSources, (x0, x1) -> x0.id().compareTo(x1.id())); + + ListBuffer sourcesBuffer = new ListBuffer<>(); + + for (int i = 0; i < sources.length; i++) { + VirtualFile source = sortedSources[i]; + AbstractFile abstractFile = asDottyFile(source); + sourcesBuffer.append(abstractFile); + sourcesMap.put(abstractFile.absolutePath(), source); + } + + DelegatingReporter reporter = new DelegatingReporter(delegate, sourceFile -> { + String pathId = sourceFile.file().absolutePath(); + VirtualFile source = sourcesMap.get(pathId); + + if (source != null) + return source.id(); + else + return pathId; + }); + try { log.debug(this::infoOnCachedCompiler); Contexts.Context initialCtx = initCtx() .fresh() .setReporter(reporter) - .setSbtCallback(callback); + .setSbtCallback(callback) + .setZincVirtualFiles(sourcesMap); Contexts.Context context = setup(args, initialCtx).map(t -> t._2).getOrElse(() -> initialCtx); @@ -70,28 +97,13 @@ synchronized public void run(VirtualFile[] sources, AnalysisCallback callback, L log.debug(this::prettyPrintCompilationArguments); Compiler compiler = newCompiler(context); - VirtualFile[] sortedSources = new VirtualFile[sources.length]; - System.arraycopy(sources, 0, sortedSources, 0, sources.length); - Arrays.sort( - sortedSources, - new Comparator() { - @Override - public int compare(VirtualFile x0, VirtualFile x1) { - return x0.id().compareTo(x1.id()); - } - } - ); - - ListBuffer sourcesBuffer = new ListBuffer<>(); - for (VirtualFile file: sortedSources) - sourcesBuffer.append(asDottyFile(file)); doCompile(compiler, sourcesBuffer.toList(), context); for (xsbti.Problem problem: delegate.problems()) { callback.problem(problem.category(), problem.position(), problem.message(), problem.severity(), true); } - } else { + } else { delegate.printSummary(); } diff --git a/sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java b/sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java index dba1889b393f..f45e3768378c 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java +++ b/sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java @@ -19,12 +19,16 @@ import xsbti.Position; import xsbti.Severity; +import java.util.function.*; + final public class DelegatingReporter extends AbstractReporter { private xsbti.Reporter delegate; + private final Function lookup; - public DelegatingReporter(xsbti.Reporter delegate) { + public DelegatingReporter(xsbti.Reporter delegate, Function lookup) { super(); this.delegate = delegate; + this.lookup = lookup; } public void dropDelegate() { @@ -53,7 +57,7 @@ public void doReport(Diagnostic dia, Context ctx) { messageBuilder.append(System.lineSeparator()).append(explanation(message, ctx)); } - delegate.log(new Problem(position, messageBuilder.toString(), severity, rendered.toString(), diagnosticCode, actions)); + delegate.log(new Problem(position, messageBuilder.toString(), severity, rendered.toString(), diagnosticCode, actions, lookup)); } private static Severity severityOf(int level) { @@ -68,9 +72,9 @@ private static Severity severityOf(int level) { return severity; } - private static Position positionOf(SourcePosition pos) { - if (pos.exists()){ - return new PositionBridge(pos, pos.source()); + private Position positionOf(SourcePosition pos) { + if (pos.exists()) { + return new PositionBridge(pos, lookup.apply(pos.source())); } else { return PositionBridge.noPosition; } diff --git a/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java b/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java index 6b3c25e2e27c..eb01da25ba1c 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java +++ b/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java @@ -12,10 +12,12 @@ import java.io.File; import java.util.Optional; +import java.util.function.Function; public class PositionBridge implements Position { private final SourcePosition pos; private final SourceFile src; + private final String pathId; public static final Position noPosition = new Position() { public Optional sourceFile() { @@ -45,9 +47,10 @@ public String toString() { } }; - public PositionBridge(SourcePosition pos, SourceFile src) { + public PositionBridge(SourcePosition pos, String path) { this.pos = pos; - this.src = src; + this.src = pos.source(); + this.pathId = path; } @Override @@ -82,17 +85,7 @@ public Optional offset() { @Override public Optional sourcePath() { - if (!src.exists()) - return Optional.empty(); - - AbstractFile sourceFile = pos.source().file(); - if (sourceFile instanceof ZincPlainFile) { - return Optional.of(((ZincPlainFile) sourceFile).underlying().id()); - } else if (sourceFile instanceof ZincVirtualFile) { - return Optional.of(((ZincVirtualFile) sourceFile).underlying().id()); - } else { - return Optional.of(sourceFile.path()); - } + return Optional.of(pathId); } @Override @@ -131,7 +124,7 @@ public String toString() { else return path; } - + @Override public Optional startOffset() { if (src.content().length == 0) diff --git a/sbt-bridge/src/dotty/tools/xsbt/Problem.java b/sbt-bridge/src/dotty/tools/xsbt/Problem.java index 9bde5ce76ebb..9eed04056752 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/Problem.java +++ b/sbt-bridge/src/dotty/tools/xsbt/Problem.java @@ -2,11 +2,14 @@ import java.util.List; import java.util.Optional; +import java.util.function.Function; + import static java.util.stream.Collectors.toList; import dotty.tools.dotc.reporting.CodeAction; import dotty.tools.dotc.rewrites.Rewrites.ActionPatch; import dotty.tools.dotc.util.SourcePosition; +import dotty.tools.dotc.util.SourceFile; import scala.jdk.javaapi.CollectionConverters; import scala.jdk.javaapi.OptionConverters; @@ -14,6 +17,7 @@ import xsbti.Position; import xsbti.Severity; + final public class Problem implements xsbti.Problem { private final Position _position; private final String _message; @@ -21,8 +25,10 @@ final public class Problem implements xsbti.Problem { private final Optional _rendered; private final String _diagnosticCode; private final List _actions; + private final Function _lookup; - public Problem(Position position, String message, Severity severity, String rendered, String diagnosticCode, List actions) { + public Problem(Position position, String message, Severity severity, String rendered, String diagnosticCode, List actions, + Function lookup) { super(); this._position = position; this._message = message; @@ -30,6 +36,7 @@ public Problem(Position position, String message, Severity severity, String rend this._rendered = Optional.of(rendered); this._diagnosticCode = diagnosticCode; this._actions = actions; + this._lookup = lookup; } public String category() { @@ -78,23 +85,23 @@ public List actions() { // never getting called. return _actions .stream() - .map(action -> new Action(action.title(), OptionConverters.toJava(action.description()), toWorkspaceEdit(CollectionConverters.asJava(action.patches())))) + .map(action -> new Action(action.title(), OptionConverters.toJava(action.description()), toWorkspaceEdit(CollectionConverters.asJava(action.patches()), _lookup))) .collect(toList()); } } - private static WorkspaceEdit toWorkspaceEdit(List patches) { + private static WorkspaceEdit toWorkspaceEdit(List patches, Function lookup) { return new WorkspaceEdit( patches .stream() - .map(patch -> new TextEdit(positionOf(patch.srcPos()), patch.replacement())) + .map(patch -> new TextEdit(positionOf(patch.srcPos(), lookup), patch.replacement())) .collect(toList()) ); } - private static Position positionOf(SourcePosition pos) { + private static Position positionOf(SourcePosition pos, Function lookup) { if (pos.exists()){ - return new PositionBridge(pos, pos.source()); + return new PositionBridge(pos, lookup.apply(pos.source())); } else { return PositionBridge.noPosition; } diff --git a/sbt-bridge/src/dotty/tools/xsbt/ZincPlainFile.java b/sbt-bridge/src/dotty/tools/xsbt/ZincPlainFile.java index 68b3494cb84b..dad8a80ef798 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/ZincPlainFile.java +++ b/sbt-bridge/src/dotty/tools/xsbt/ZincPlainFile.java @@ -15,7 +15,8 @@ public ZincPlainFile(PathBasedFile underlying) { this._underlying = underlying; } + @Override public PathBasedFile underlying() { return _underlying; } -} \ No newline at end of file +} diff --git a/sbt-bridge/src/dotty/tools/xsbt/ZincVirtualFile.java b/sbt-bridge/src/dotty/tools/xsbt/ZincVirtualFile.java index a79686270f34..74813f87fb20 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/ZincVirtualFile.java +++ b/sbt-bridge/src/dotty/tools/xsbt/ZincVirtualFile.java @@ -20,20 +20,20 @@ public ZincVirtualFile(VirtualFile underlying) throws IOException { this._underlying = underlying; // fill in the content - OutputStream output = output(); - try { - Streamable.Bytes bytes = new Streamable.Bytes() { - @Override - public InputStream inputStream() { - return underlying.input(); - } - }; - output.write(bytes.toByteArray()); - } finally { - output.close(); + try (OutputStream output = output()) { + try (InputStream input = underlying.input()) { + Streamable.Bytes bytes = new Streamable.Bytes() { + @Override + public InputStream inputStream() { + return input; + } + }; + output.write(bytes.toByteArray()); + } } } + @Override public VirtualFile underlying() { return _underlying; } diff --git a/sbt-bridge/src/xsbt/CachedCompilerImpl.java b/sbt-bridge/src/xsbt/CachedCompilerImpl.java index 0b876475e51e..1310c76f70db 100644 --- a/sbt-bridge/src/xsbt/CachedCompilerImpl.java +++ b/sbt-bridge/src/xsbt/CachedCompilerImpl.java @@ -62,7 +62,7 @@ synchronized public void run(File[] sources, DependencyChanges changes, Analysis Context ctx = new ContextBase().initialCtx().fresh() .setSbtCallback(callback) - .setReporter(new DelegatingReporter(delegate)); + .setReporter(new DelegatingReporter(delegate, source -> source.file().absolutePath())); dotty.tools.dotc.reporting.Reporter reporter = Main.process(commandArguments(sources), ctx); if (reporter.hasErrors()) { diff --git a/sbt-bridge/src/xsbt/DottydocRunner.java b/sbt-bridge/src/xsbt/DottydocRunner.java index e4c35a317e71..a91ff087cea9 100644 --- a/sbt-bridge/src/xsbt/DottydocRunner.java +++ b/sbt-bridge/src/xsbt/DottydocRunner.java @@ -53,7 +53,7 @@ public void run() { args = retained.toArray(new String[retained.size()]); Context ctx = new ContextBase().initialCtx().fresh() - .setReporter(new DelegatingReporter(delegate)); + .setReporter(new DelegatingReporter(delegate, source -> source.file().absolutePath())); try { Class dottydocMainClass = Class.forName("dotty.tools.dottydoc.Main"); diff --git a/sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala b/sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala index e58f9fefd92d..51f10e90f932 100644 --- a/sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala +++ b/sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala @@ -19,7 +19,6 @@ import TestCallback.ExtractedClassDependencies * source code using Scala compiler. */ class ScalaCompilerForUnitTesting { - import scala.language.reflectiveCalls /** * Compiles given source code using Scala compiler and returns API representation @@ -122,7 +121,7 @@ class ScalaCompilerForUnitTesting { * The sequence of temporary files corresponding to passed snippets and analysis * callback is returned as a result. */ - def compileSrcs(groupedSrcs: List[List[String]]): (Seq[File], TestCallback) = { + def compileSrcs(groupedSrcs: List[List[String]]): (Seq[VirtualFile], TestCallback) = { val temp = IO.createTemporaryDirectory val analysisCallback = new TestCallback val classesDir = new File(temp, "classes") @@ -137,13 +136,13 @@ class ScalaCompilerForUnitTesting { prepareSrcFile(temp, fileName, src) } - val virtualSrcFiles = srcFiles.map(file => TestVirtualFile(file.toPath)).toArray + val virtualSrcFiles = srcFiles.toArray val classesDirPath = classesDir.getAbsolutePath.toString val output = new SingleOutput: def getOutputDirectory() = classesDir bridge.run( - virtualSrcFiles.toArray, + virtualSrcFiles, new TestDependencyChanges, Array("-Yforce-sbt-phases", "-classpath", classesDirPath, "-usejavacp", "-d", classesDirPath), output, @@ -158,14 +157,14 @@ class ScalaCompilerForUnitTesting { (files.flatten.toSeq, analysisCallback) } - def compileSrcs(srcs: String*): (Seq[File], TestCallback) = { + def compileSrcs(srcs: String*): (Seq[VirtualFile], TestCallback) = { compileSrcs(List(srcs.toList)) } - private def prepareSrcFile(baseDir: File, fileName: String, src: String): File = { + private def prepareSrcFile(baseDir: File, fileName: String, src: String): VirtualFile = { val srcFile = new File(baseDir, fileName) IO.write(srcFile, src) - srcFile + new TestVirtualFile(srcFile.toPath) } } diff --git a/sbt-bridge/test/xsbt/TestVirtualFile.scala b/sbt-bridge/test/xsbt/TestVirtualFile.scala index db00038272a8..2c7729d0a6cd 100644 --- a/sbt-bridge/test/xsbt/TestVirtualFile.scala +++ b/sbt-bridge/test/xsbt/TestVirtualFile.scala @@ -1,6 +1,6 @@ package xsbt -import xsbti.PathBasedFile +import xsbti.{PathBasedFile, VirtualFileRef} import java.nio.file.{Files, Path} import scala.io.Source import scala.io.Codec @@ -8,7 +8,17 @@ import scala.io.Codec class TestVirtualFile(path: Path) extends PathBasedFile: override def contentHash(): Long = ??? override def input(): java.io.InputStream = Files.newInputStream(path) - override def id(): String = name() + lazy val absolutePath: String = path.toAbsolutePath.toString() + override def id(): String = absolutePath override def name(): String = path.toFile.getName override def names(): Array[String] = ??? override def toPath(): Path = path + + + override def hashCode(): Int = absolutePath.hashCode() + + override def equals(x: Any): Boolean = this.eq(x.asInstanceOf[AnyRef]) || x.match { + case vf: VirtualFileRef => vf.id() == id() + } + + diff --git a/sbt-bridge/test/xsbti/TestCallback.scala b/sbt-bridge/test/xsbti/TestCallback.scala index a0919dc69bc4..0f48ad9a18ef 100644 --- a/sbt-bridge/test/xsbti/TestCallback.scala +++ b/sbt-bridge/test/xsbti/TestCallback.scala @@ -14,40 +14,37 @@ class TestCallback extends AnalysisCallback { case class TestUsedName(name: String, scopes: EnumSet[UseScope]) val classDependencies = new ArrayBuffer[(String, String, DependencyContext)] - val binaryDependencies = new ArrayBuffer[(File, String, String, File, DependencyContext)] - val products = new ArrayBuffer[(File, File)] + val binaryDependencies = new ArrayBuffer[(Path, String, String, VirtualFileRef, DependencyContext)] + val products = new ArrayBuffer[(VirtualFileRef, Path)] val usedNamesAndScopes = scala.collection.mutable.Map.empty[String, Set[TestUsedName]].withDefaultValue(Set.empty) - val classNames = scala.collection.mutable.Map.empty[File, Set[(String, String)]].withDefaultValue(Set.empty) - val apis: scala.collection.mutable.Map[File, Seq[ClassLike]] = scala.collection.mutable.Map.empty + val classNames = scala.collection.mutable.Map.empty[VirtualFileRef, Set[(String, String)]].withDefaultValue(Set.empty) + val apis: scala.collection.mutable.Map[VirtualFileRef, Seq[ClassLike]] = scala.collection.mutable.Map.empty def usedNames = usedNamesAndScopes.view.mapValues(_.map(_.name)).toMap - override def startSource(source: File): Unit = { + override def startSource(source: File): Unit = ??? + override def startSource(source: VirtualFile): Unit = { assert(!apis.contains(source), s"startSource can be called only once per source file: $source") apis(source) = Seq.empty } - override def startSource(source: VirtualFile): Unit = ??? - override def binaryDependency(binary: File, name: String, fromClassName: String, source: File, context: DependencyContext): Unit = { + override def binaryDependency(binary: File, name: String, fromClassName: String, source: File, context: DependencyContext): Unit = ??? + override def binaryDependency(binary: Path, name: String, fromClassName: String, source: VirtualFileRef, context: DependencyContext): Unit = { binaryDependencies += ((binary, name, fromClassName, source, context)) } - override def binaryDependency(binary: Path, name: String, fromClassName: String, source: VirtualFileRef, context: DependencyContext): Unit = ??? - override def generatedNonLocalClass(source: File, - module: File, - binaryClassName: String, - srcClassName: String): Unit = { + override def generatedNonLocalClass(source: File, module: File, binaryClassName: String, srcClassName: String): Unit = ??? + override def generatedNonLocalClass(source: VirtualFileRef, module: Path, binaryClassName: String, srcClassName: String): Unit = { products += ((source, module)) classNames(source) += ((srcClassName, binaryClassName)) () } - override def generatedNonLocalClass(source: VirtualFileRef, module: Path, binaryClassName: String, srcClassName: String): Unit = ??? - override def generatedLocalClass(source: File, module: File): Unit = { + override def generatedLocalClass(source: File, module: File): Unit = ??? + override def generatedLocalClass(source: VirtualFileRef, module: Path): Unit = { products += ((source, module)) () } - override def generatedLocalClass(source: VirtualFileRef, module: Path): Unit = ??? override def classDependency(onClassName: String, sourceClassName: String, context: DependencyContext): Unit = { if (onClassName != sourceClassName) classDependencies += ((onClassName, sourceClassName, context)) @@ -57,10 +54,10 @@ class TestCallback extends AnalysisCallback usedNamesAndScopes(className) += TestUsedName(name, scopes) } - override def api(source: File, classApi: ClassLike): Unit = { + override def api(source: File, classApi: ClassLike): Unit = ??? + override def api(source: VirtualFileRef, classApi: ClassLike): Unit = { apis(source) = classApi +: apis(source) } - override def api(source: VirtualFileRef, classApi: ClassLike): Unit = ??? override def problem(category: String, pos: xsbti.Position, message: String, severity: xsbti.Severity, reported: Boolean): Unit = () override def dependencyPhaseCompleted(): Unit = () diff --git a/sbt-test/sbt-bridge/zinc-13-compat/test b/sbt-test/sbt-bridge/zinc-13-compat/test index f7b3295e155c..d37c2434b1a3 100644 --- a/sbt-test/sbt-bridge/zinc-13-compat/test +++ b/sbt-test/sbt-bridge/zinc-13-compat/test @@ -1,3 +1,3 @@ # this little app test that scala3-sbt-bridge is compatible with Zinc 1.3 # this is necessary to maintain the compatibility with Bloop (see https://github.com/lampepfl/dotty/issues/10816) -> run +-> run diff --git a/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala b/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala index b7f1b1007420..c6fac6b796c0 100644 --- a/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala +++ b/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala @@ -19,8 +19,8 @@ class DivideZero extends PluginPhase with StandardPlugin { val phaseName = name - override val runsAfter = Set(Staging.name) - override val runsBefore = Set(Pickler.name) + override val runsAfter = Set(Pickler.name) + override val runsBefore = Set(Staging.name) def init(options: List[String]): List[PluginPhase] = this :: Nil diff --git a/sbt-test/source-dependencies/compactify/src/main/scala/Nested.scala b/sbt-test/source-dependencies/compactify/src/main/scala/Nested.scala index 798868d72640..4b1597d287d4 100644 --- a/sbt-test/source-dependencies/compactify/src/main/scala/Nested.scala +++ b/sbt-test/source-dependencies/compactify/src/main/scala/Nested.scala @@ -38,4 +38,6 @@ class TopLevel2 object TopLevel3 -class TopLevel4 \ No newline at end of file +class TopLevel4 + +object TopLevelModuleSuffix$ diff --git a/sbt-test/source-dependencies/compactify/test b/sbt-test/source-dependencies/compactify/test index b56be3e5d4aa..64be9af369b5 100644 --- a/sbt-test/source-dependencies/compactify/test +++ b/sbt-test/source-dependencies/compactify/test @@ -5,4 +5,4 @@ -> outputEmpty $ delete src/main/scala/For.scala src/main/scala/Nested.scala > compile -> outputEmpty \ No newline at end of file +> outputEmpty