Skip to content

Commit

Permalink
Add support for VirtualFile in Scala 3
Browse files Browse the repository at this point in the history
This commit add support for `VirtualFiles`, that has recently been added
to Zinc, to the `scala3-compiler-bridge`. It fixes a bug that was
reported in sbt/sbt#6007.

Also it fixes a bug in the bug in the returned `CompileAnalysis` by
populating the `SourceInfos`. The reported warnings were not returned
back in `sbt-dotty` after a succefull compilation.
  • Loading branch information
adpi2 committed Nov 26, 2020
1 parent 91e77ea commit 2936ebb
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Zinc - The incremental compiler for Scala.
* Copyright Lightbend, Inc. and Mark Harrah
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package xsbt

import xsbti.{ PathBasedFile, VirtualFile }
import dotty.tools.io

private trait AbstractZincFile extends io.AbstractFile {
def underlying: VirtualFile
}

private final class ZincPlainFile(val underlying: PathBasedFile)
extends io.PlainFile(io.Path(underlying.toPath))
with AbstractZincFile

private final class ZincVirtualFile(val underlying: VirtualFile)
extends io.VirtualFile(underlying.name, underlying.id)
with AbstractZincFile {
io.Streamable.closing(output)(_.write(io.Streamable.bytes(underlying.input))) // fill in the content
}

private object AbstractZincFile {
def apply(virtualFile: VirtualFile): AbstractZincFile = virtualFile match {
case file: PathBasedFile => new ZincPlainFile(file)
case _ => new ZincVirtualFile(virtualFile)
}

def unapply(file: io.AbstractFile): Option[VirtualFile] = file match {
case wrapper: AbstractZincFile => Some(wrapper.underlying)
case _ => None
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Zinc - The incremental compiler for Scala.
* Copyright Lightbend, Inc. and Mark Harrah
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package xsbt

import dotty.tools.dotc.Driver
import dotty.tools.dotc.config.{Properties, Settings}
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.util.SourceFile
import xsbt.Log.debug
import xsbti.{ AnalysisCallback, Logger, Problem, Reporter }
import xsbti.compile.{ Output, SingleOutput }

private final class CachedCompilerImpl(
options: Array[String],
output: Output
) extends Driver {
private val args = output match {
case output: SingleOutput =>
options ++ Seq("-d", output.getOutputDirectoryAsPath.toString)
case _ =>
throw new IllegalArgumentException(
s"output should be a SingleOutput, was a ${output.getClass.getName}"
)
}

/**
* `sourcesRequired` is set to false because `args` do not contain the sources
* The sources are passed programmatically to the Scala 3 compiler
*/
override def sourcesRequired: Boolean = false

def run(
sources: List[SourceFile],
callback: AnalysisCallback,
log: Logger,
delegate: Reporter,
): Unit = synchronized {
val reporter = new DelegatingReporter(delegate)

try {
debug(log, infoOnCachedCompiler)

val initialCtx = initCtx.fresh
.setReporter(reporter)
.setSbtCallback(callback)

given Context = setup(args,initialCtx)(1)

if (shouldStopWithInfo) {
throw new InterfaceCompileFailed(options, Array(), StopInfoError)
}

val isCancelled =
if (!delegate.hasErrors) {
debug(log, prettyPrintCompilationArguments)
val run = newCompiler.newRun
run.compileSources(sources)
// allConditionalWarning is not available in Scala 3
// processUnreportedWarnings(run)
delegate.problems.foreach(
p => callback.problem(p.category, p.position, p.message, p.severity, true)
)

run.isCancelled
} else false

delegate.printSummary()

if (delegate.hasErrors) {
debug(log, "Compilation failed (CompilerInterface)")
throw new InterfaceCompileFailed(args, delegate.problems, "Compilation failed")
}

// the case where we cancelled compilation _after_ some compilation errors got reported
// will be handled by line above so errors still will be reported properly just potentially not
// all of them (because we cancelled the compilation)
if (isCancelled) {
debug(log, "Compilation cancelled (CompilerInterface)")
throw new InterfaceCompileCancelled(args, "Compilation has been cancelled")
}
} finally {
reporter.dropDelegate()
}
}

private def shouldStopWithInfo(using ctx: Context) = {
val settings = ctx.settings
import settings._
Set(help, Xhelp, Yhelp, showPlugins, XshowPhases).exists(_.value(using ctx))
}

private val StopInfoError: String =
"Compiler option supplied that disabled Zinc compilation."

private def infoOnCachedCompiler: String = {
val compilerId = hashCode().toLong.toHexString
s"[zinc] Running cached compiler $compilerId for Scala compiler ${Properties.versionString}"
}

private def prettyPrintCompilationArguments =
options.mkString("[zinc] The Scala compiler is invoked with:\n\t", "\n\t", "")
}

class InterfaceCompileFailed(
val arguments: Array[String],
val problems: Array[Problem],
override val toString: String
) extends xsbti.CompileFailed

class InterfaceCompileCancelled(val arguments: Array[String], override val toString: String)
extends xsbti.CompileCancelled
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@
*/

package xsbt

import dotty.tools.dotc.Main
import dotty.tools.dotc.config.Properties
import dotty.tools.dotc.core.Contexts.ContextBase
import xsbt.Log.debug
import xsbti.{ AnalysisCallback, Logger, PathBasedFile, Problem, Reporter, VirtualFile }
import xsbti.compile._
import xsbti.{ AnalysisCallback, Logger, Reporter, VirtualFile }
import xsbti.compile.{ DependencyChanges, Output, CompileProgress }
import dotty.tools.dotc.util.SourceFile

/**
* This is the entry point for the compiler bridge (implementation of CompilerInterface2)
Expand All @@ -33,65 +29,11 @@ final class CompilerBridge extends xsbti.compile.CompilerInterface2 {
log: Logger
): Unit = {
val cached = new CachedCompilerImpl(options, output)
cached.run(sources, callback, log, delegate)
val sourceFiles = sources.view
.sortBy(_.id)
.map(AbstractZincFile(_))
.map(SourceFile(_, scala.io.Codec.UTF8))
.toList
cached.run(sourceFiles, callback, log, delegate)
}
}

private final class CachedCompilerImpl(
args: Array[String],
output: Output
) {

private val outputArgs = output match {
case output: SingleOutput =>
Array("-d", output.getOutputDirectoryAsPath.toString)
case _ =>
throw new IllegalArgumentException(
s"output should be a SingleOutput, was a ${output.getClass.getName}"
);
}

private def commandArguments(sources: Array[VirtualFile]): Array[String] = {
val files = sources.map {
case pathBased: PathBasedFile => pathBased
case virtualFile =>
throw new IllegalArgumentException(
s"source should be a PathBasedFile, was ${virtualFile.getClass.getName}"
)
}
outputArgs ++ args ++ files.map(_.toPath.toString)
}

private def infoOnCachedCompiler(compilerId: String): String =
s"[zinc] Running cached compiler $compilerId for Scala compiler ${Properties.versionString}"

private def prettyPrintCompilationArguments(args: Array[String]) =
args.mkString("[zinc] The Scala compiler is invoked with:\n\t", "\n\t", "")

def run(
sources: Array[VirtualFile],
callback: AnalysisCallback,
log: Logger,
delegate: Reporter,
): Unit = synchronized {
debug(log, infoOnCachedCompiler(hashCode().toLong.toHexString))
debug(log, prettyPrintCompilationArguments(args))
val dreporter = new DelegatingReporter(delegate)
try {
val ctx = new ContextBase().initialCtx.fresh
.setSbtCallback(callback)
.setReporter(dreporter)
val reporter = Main.process(commandArguments(sources), ctx)
if (reporter.hasErrors)
throw new InterfaceCompileFailed(args, Array(), "Compilation failed")
} finally {
dreporter.dropDelegate()
}
}
}

class InterfaceCompileFailed(
val arguments: Array[String],
val problems: Array[Problem],
override val toString: String
) extends xsbti.CompileFailed
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ private final class DelegatingReporter(

private object DelegatingReporter {
private def convert(pos: SourcePosition): Position = {
if (pos.exists) new PositionImpl(pos, pos.source)
if (pos.exists)
new PositionImpl(pos, pos.source)
else EmptyPosition
}

Expand All @@ -56,20 +57,20 @@ private object DelegatingReporter {
}

object EmptyPosition extends xsbti.Position {
override def line(): Optional[Integer] = Optional.empty
override def lineContent(): String = ""
override def offset(): Optional[Integer] = Optional.empty
override def pointer(): Optional[Integer] = Optional.empty
override def pointerSpace(): Optional[String] = Optional.empty
override def sourcePath(): Optional[String] = Optional.empty
override def sourceFile(): Optional[File] = Optional.empty
override def line: Optional[Integer] = Optional.empty
override def lineContent: String = ""
override def offset: Optional[Integer] = Optional.empty
override def pointer: Optional[Integer] = Optional.empty
override def pointerSpace: Optional[String] = Optional.empty
override def sourcePath: Optional[String] = Optional.empty
override def sourceFile: Optional[File] = Optional.empty
}

class PositionImpl(
pos: SourcePosition,
src: SourceFile
) extends xsbti.Position {
def line: Optional[Integer] = {
override def line: Optional[Integer] = {
if (src.content.isEmpty)
Optional.empty
else {
Expand All @@ -78,7 +79,7 @@ private object DelegatingReporter {
else Optional.of(line + 1)
}
}
def lineContent: String = {
override def lineContent: String = {
if (src.content.isEmpty) ""
else {
val line = pos.lineContent
Expand All @@ -89,20 +90,25 @@ private object DelegatingReporter {
else line
}
}
def offset: Optional[Integer] = Optional.of(pos.point)
def sourcePath: Optional[String] = {
override def offset: Optional[Integer] = Optional.of(pos.point)
override def sourcePath: Optional[String] = {
if (!src.exists) Optional.empty
else Optional.ofNullable(src.file.path)
else {
pos.source.file match {
case AbstractZincFile(virtualFile) => Optional.of(virtualFile.id)
case abstractFile => Optional.ofNullable(abstractFile.path)
}
}
}
def sourceFile: Optional[File] = {
override def sourceFile: Optional[File] = {
if (!src.exists) Optional.empty
else Optional.ofNullable(src.file.file)
}
def pointer: Optional[Integer] = {
override def pointer: Optional[Integer] = {
if (src.content.isEmpty) Optional.empty
else Optional.of(pos.point - src.startOfLine(pos.point))
}
def pointerSpace: Optional[String] = {
override def pointerSpace: Optional[String] = {
if (src.content.isEmpty) Optional.empty
else {
// Don't crash if pointer is out-of-bounds (happens with some macros)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,10 @@ case class ProjectStructure(
val dropQuotes =
if (expected.startsWith("\"")) expected.drop(1).dropRight(1)
else expected
assert(problemMessage.contains(dropQuotes), s"'$problemMessage' doesn't contain '$dropQuotes'.")
assert(
problemMessage.contains(dropQuotes),
s"'$problemMessage' doesn't contain '$dropQuotes'."
)
case None =>
throw new TestFailed(
s"Problem not found: $index (there are ${problems.length} problem with severity $severity)."
Expand Down
1 change: 0 additions & 1 deletion project/HouseRulesPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ object HouseRulesPlugin extends AutoPlugin {
scalacOptions += "-language:higherKinds",
scalacOptions += "-language:implicitConversions",
scalacOptions ++= "-Xfuture".ifScala213OrMinus.value.toList,
scalacOptions += "-Xlint",
scalacOptions ++= "-Xfatal-warnings"
.ifScala(v => {
sys.props.get("sbt.build.fatal") match {
Expand Down

0 comments on commit 2936ebb

Please sign in to comment.