Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Init Scala3 compiler bridge #947

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,25 @@ lazy val compilerBridge211 = compilerBridge.jvm(scala211)
lazy val compilerBridge212 = compilerBridge.jvm(scala212)
lazy val compilerBridge213 = compilerBridge.jvm(scala213)

lazy val scala3CompilerBridge = project
.in(internalPath / "scala3-compiler-bridge")
.dependsOn(
compilerInterface.jvm(autoScalaLibrary = false)
)
.settings(
name := "scala3-compiler-bridge",
autoScalaLibrary := false,
scalaVersion := `scala-3.0`,
baseSettings,
compilerVersionDependentScalacOptions,
libraryDependencies += scalaCompiler.value % "provided",
exportJars := true,
// Those scalacOptions are ignored by the scala3-compiler
Compile / scalacOptions --=
Seq("-Xlint", "-Ywarn-dead-code", "-Ywarn-numeric-widen", "-Ywarn-value-discard")
)
.disablePlugins(ScalafmtPlugin)

/**
* Tests for the compiler bridge.
* This is split into a separate subproject because testing introduces more dependencies
Expand Down Expand Up @@ -597,6 +616,11 @@ lazy val zincClasspath = (projectMatrix in internalPath / "zinc-classpath")
"sbt.internal.inc.classpath.NativeCopyConfig.*"
),
exclude[IncompatibleMethTypeProblem]("sbt.internal.inc.classpath.NativeCopyConfig.*"),
exclude[MissingTypesProblem]("sbt.internal.inc.classpath.DualLoader"),
exclude[DirectMissingMethodProblem]("sbt.internal.inc.classpath.DualLoader.this"),
exclude[IncompatibleMethTypeProblem]("sbt.internal.inc.classpath.DualLoader.this"),
exclude[MissingClassProblem]("sbt.internal.inc.classpath.DifferentLoaders"),
exclude[MissingClassProblem]("sbt.internal.inc.classpath.NullLoader")
),
)
.jvmPlatform(scalaVersions = scala212_213)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xsbt.CompilerBridge
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
@@ -0,0 +1,39 @@
/*
* 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.{ 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)
*/
final class CompilerBridge extends xsbti.compile.CompilerInterface2 {
override def run(
sources: Array[VirtualFile],
changes: DependencyChanges,
options: Array[String],
output: Output,
callback: AnalysisCallback,
delegate: Reporter,
progress: CompileProgress,
log: Logger
): Unit = {
val cached = new CachedCompilerImpl(options, output)
val sourceFiles = sources.view
.sortBy(_.id)
.map(AbstractZincFile(_))
.map(SourceFile(_, scala.io.Codec.UTF8))
.toList
cached.run(sourceFiles, callback, log, delegate)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* 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 java.io.File
import java.util.Optional

import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.reporting.{ AbstractReporter, Diagnostic }
import dotty.tools.dotc.interfaces
import dotty.tools.dotc.util.{ SourceFile, SourcePosition }
import xsbti.{ Position, Problem, Severity }

private final class DelegatingReporter(
private[this] var delegate: xsbti.Reporter
) extends AbstractReporter {
import DelegatingReporter._
def dropDelegate(): Unit = { delegate = null }

override def printSummary(using ctx: Context): Unit = delegate.printSummary()

override def doReport(diagnostic: Diagnostic)(using Context): Unit = {
val severity = convert(diagnostic.level)
val position = convert(diagnostic.pos)
val message = diagnostic.msg
val explain = if (Diagnostic.shouldExplain(diagnostic) && message.explanation.nonEmpty) {
explanation(message)
} else ""
val rendered = messageAndPos(message, diagnostic.pos, diagnosticLevel(diagnostic)) + explain
delegate.log(CompileProblem(position, message.message, severity, rendered))
}
}

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

private def convert(level: Int): xsbti.Severity = {
level match {
case interfaces.Diagnostic.ERROR => Severity.Error
case interfaces.Diagnostic.WARNING => Severity.Warn
case interfaces.Diagnostic.INFO => Severity.Info
case level => throw new IllegalArgumentException(s"Bad diagnostic level: $level")
}
}

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
}

class PositionImpl(
pos: SourcePosition,
src: SourceFile
) extends xsbti.Position {
override def line: Optional[Integer] = {
if (src.content.isEmpty)
Optional.empty
else {
val line = pos.line
if (line == -1) Optional.empty
else Optional.of(line + 1)
}
}
override def lineContent: String = {
if (src.content.isEmpty) ""
else {
val line = pos.lineContent
if (line.endsWith("\r\n"))
line.substring(0, line.length - 2)
else if (line.endsWith("\n") || line.endsWith("\u000c"))
line.substring(0, line.length - 1)
else line
}
}
override def offset: Optional[Integer] = Optional.of(pos.point)
override def sourcePath: Optional[String] = {
if (!src.exists) Optional.empty
else {
pos.source.file match {
case AbstractZincFile(virtualFile) => Optional.of(virtualFile.id)
case abstractFile => Optional.ofNullable(abstractFile.path)
}
}
}
override def sourceFile: Optional[File] = {
if (!src.exists) Optional.empty
else Optional.ofNullable(src.file.file)
}
override def pointer: Optional[Integer] = {
if (src.content.isEmpty) Optional.empty
else Optional.of(pos.point - src.startOfLine(pos.point))
}
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)
val fixedPointer = Math.min(pointer.get, lineContent.length)
val result = lineContent
.take(fixedPointer)
.map {
case '\t' => '\t'
case _ => ' '
}
Optional.of(result)
}
}
}

private final case class CompileProblem(
position: Position,
message: String,
severity: Severity,
_rendered: String
) extends Problem {
override def category = ""
override def rendered: Optional[String] = Optional.of(_rendered)
}
}
16 changes: 16 additions & 0 deletions internal/scala3-compiler-bridge/src/main/scala/xsbt/Log.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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

object Log {
def debug(log: xsbti.Logger, msg: => String) = log.debug(() => msg)
}
Loading