Skip to content

Commit

Permalink
Add a dotty-interfaces package
Browse files Browse the repository at this point in the history
We introduce a new entry point for the compiler in
`dotty.tools.dotc.Driver`:
```
def process(args: Array[String], simple: interfaces.SimpleReporter,
  callback: interfaces.CompilerCallback): interfaces.ReporterResult
```
Except for `args` which is just an array, the argument types and return
type of this method are Java interfaces defined in a new package called
`dotty-interfaces` which has a stable ABI. This means that you can
programmatically run a compiler with a custom reporter and callbacks
without having to recompile it against every version of dotty: you only
need to have `dotty-interfaces` present at compile-time and call the
`process` method using Java reflection.

See `test/test/InterfaceEntryPointTest.scala` for a concrete example.

This design is based on discussions with the IntelliJ IDEA Scala plugin
team. Thanks to Nikolay Tropin for the discussions and his PR
proposal (see scala#1011).
  • Loading branch information
smarter committed Feb 28, 2016
1 parent 94b41d5 commit 7e7ee82
Show file tree
Hide file tree
Showing 19 changed files with 397 additions and 71 deletions.
18 changes: 11 additions & 7 deletions bin/dotc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ ReplMain=test.DottyRepl



# autodetecting the compiler jar. this is location where sbt 'packages' it
# autodetecting the compiler jars. this is the location where sbt 'packages' them
INTERFACES_JAR=$DOTTY_ROOT/interfaces/target/dotty-interfaces-$DOTTY_VERSION.jar
MAIN_JAR=$DOTTY_ROOT/target/scala-$SCALA_BINARY_VERSION/dotty_$SCALA_BINARY_VERSION-$DOTTY_VERSION.jar
TEST_JAR=$DOTTY_ROOT/target/scala-$SCALA_BINARY_VERSION/dotty_$SCALA_BINARY_VERSION-$DOTTY_VERSION-tests.jar
DOTTY_JAR=$DOTTY_ROOT/dotty.jar
Expand All @@ -62,7 +63,7 @@ function checkjar {
echo "The required jar file was built successfully."
fi
else
NEW_FILES="$(find "$DOTTY_ROOT/$3" -iname "*.scala" -newer "$1")"
NEW_FILES="$(find "$DOTTY_ROOT/$3" \( -iname "*.scala" -o -iname "*.java" \) -newer "$1")"
if [ ! -z "$NEW_FILES" ];
then
echo "new files detected. rebuilding"
Expand All @@ -74,6 +75,7 @@ function checkjar {
fi
}

checkjar $INTERFACES_JAR interfaces/package interfaces
checkjar $MAIN_JAR package src
checkjar $TEST_JAR test:package test

Expand Down Expand Up @@ -198,6 +200,8 @@ classpathArgs () {
else
toolchain="$SCALA_LIBRARY_JAR:$SCALA_REFLECT_JAR:$SCALA_COMPILER_JAR:$JLINE_JAR"
fi
bcpJars="$INTERFACES_JAR:$MAIN_JAR"
cpJars="$INTERFACES_JAR:$MAIN_JAR:$TEST_JAR"

if [[ -n "$cygwin" ]]; then
if [[ "$OS" = "Windows_NT" ]] && cygpath -m .>/dev/null 2>/dev/null ; then
Expand All @@ -207,18 +211,18 @@ classpathArgs () {
fi

if [[ -n $bootcp ]]; then
boot_classpath="$(cygpath --path --$format "$toolchain:$MAIN_JAR")"
classpath="$(cygpath --path --$format "$MAIN_JAR:$TEST_JAR")"
boot_classpath="$(cygpath --path --$format "$toolchain:$bcpJars")"
classpath="$(cygpath --path --$format "$cpJars")"
cpArgs="-Xbootclasspath/a:$boot_classpath -classpath $classpath"
else
classpath="$(cygpath --path --$format "$toolchain:$MAIN_JAR:$TEST_JAR")"
classpath="$(cygpath --path --$format "$toolchain:$cpJars")"
cpArgs="-classpath $classpath"
fi
else
if [[ -n $bootcp ]]; then
cpArgs="-Xbootclasspath/a:$toolchain:$MAIN_JAR -classpath $MAIN_JAR:$TEST_JAR"
cpArgs="-Xbootclasspath/a:$toolchain:$bcpJars -classpath $cpJars"
else
cpArgs="-classpath $toolchain:$MAIN_JAR:$TEST_JAR"
cpArgs="-classpath $toolchain:$cpJars"
fi
fi
echo ${cpArgs}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dotty.tools.dotc.interfaces;

import java.io.File;
import java.util.Optional;

/** An abstract file may either be a file on disk or a virtual file.
*
* Do not rely on the identity of instances of this class.
*
* User code should not implement this interface, but it may have to
* manipulate objects of this type.
*/
public interface AbstractFile {
/** The name of this file, note that two files may have the same name. */
String name();

/** The path of this file, this might be a virtual path of an unspecified format. */
String path();

/** If this is a real file on disk, a `java.io.File` that corresponds to this file.
* Otherwise, an empty `Optional`.
*/
Optional<File> jfile();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dotty.tools.dotc.interfaces;

/** Set of callbacks called in response to events during the compilation process.
*
* You should implement this interface if you want to react to one or more of
* these events.
*
* @see the method `process` of `dotty.tools.dotc.Driver` for more information.
*/
public interface CompilerCallback {
/** Called when a class has been generated.
*
* @param source The source file corresponding to this class.
* Example: ./src/library/scala/collection/Seq.scala
* @param generatedClass The generated classfile for this class.
* Example: ./scala/collection/Seq$.class
* @param className The name of this class.
* Example: scala.collection.Seq$
*/
default void onClassGenerated(SourceFile source, AbstractFile generatedClass, String className) {};

/** Called when every class for this file has been generated.
*
* @param source The source file.
* Example: ./src/library/scala/collection/Seq.scala
*/
default void onSourceCompiled(SourceFile source) {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dotty.tools.dotc.interfaces;

import java.util.Optional;

/** A diagnostic is a message emitted during the compilation process.
*
* It can either be an error, a warning or an information.
*
* User code should not implement this interface, but it may have to
* manipulate objects of this type.
*/
public interface Diagnostic {
public static final int ERROR = 2;
public static final int WARNING = 1;
public static final int INFO = 0;

/** The message to report */
String message();

/** Level of the diagnostic, can be either ERROR, WARNING or INFO */
int level();

/** The position in a source file of the code that caused this diagnostic
* to be emitted. */
Optional<SourcePosition> position();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dotty.tools.dotc.interfaces;

/** Summary of the diagnostics emitted by a Reporter.
*
* User code should not implement this interface, but it may have to
* manipulate objects of this type.
*/
public interface ReporterResult {
/** Have we emitted any error ? */
boolean hasErrors();
/** Number of errors that have been emitted */
int errorCount();

/** Have we emitted any warning ? */
boolean hasWarnings();
/** Number of warnings that have been emitted */
int warningCount();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dotty.tools.dotc.interfaces;

/** Report errors, warnings and info messages during the compilation process
*
* You should implement this interface if you want to handle the diagnostics
* returned by the compiler yourself.
*
* @see the method `process` of `dotty.tools.dotc.Driver` for more information.
*/
public interface SimpleReporter {
/** Report a diagnostic. */
void report(Diagnostic diag);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dotty.tools.dotc.interfaces;

import java.io.File;

/** A source file.
*
* User code should not implement this interface, but it may have to
* manipulate objects of this type.
*/
public interface SourceFile extends AbstractFile {
/** The content of this file as seen by the compiler. */
char[] content();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dotty.tools.dotc.interfaces;

/** A position in a source file.
*
* A position is a range between a start offset and an end offset, as well as a
* point inside this range.
*
* As a convenience, we also provide methods that return the line and the column
* corresponding to each offset.
*
* User code should not implement this interface, but it may have to
* manipulate objects of this type.
*/
public interface SourcePosition {
/** Content of the line which contains the point */
String lineContent();

/** Offset to the point */
int point();
/** Line number of the point, starting at 0 */
int line();
/** Column number of the point, starting at 0 */
int column();

/** Offset to the range start */
int start();
/** Line number of the range start, starting at 0 */
int startLine();
/** Column number of the range start, starting at 0 */
int startColumn();

/** Offset to the range end */
int end();
/** Line number of the range end, starting at 0 */
int endLine();
/** Column number of the range end, starting at 0 */
int endColumn();

/** The source file corresponding to this position.
* The values returned by `point()`, `start()` and `end()`
* are indices in the array returned by `source().content()`.
*/
SourceFile source();
}
11 changes: 10 additions & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ object DottyBuild extends Build {
)
}

lazy val `dotty-interfaces` = project.in(file("interfaces")).
settings(
// Do not append Scala versions to the generated artifacts
crossPaths := false,
// Do not depend on the Scala library
autoScalaLibrary := false
)

lazy val dotty = project.in(file(".")).
dependsOn(`dotty-interfaces`).
settings(
// set sources to src/, tests to test/ and resources to resources/
scalaSource in Compile := baseDirectory.value / "src",
Expand Down Expand Up @@ -105,7 +114,7 @@ object DottyBuild extends Build {
parallelExecution in Test := false,

// http://grokbase.com/t/gg/simple-build-tool/135ke5y90p/sbt-setting-jvm-boot-paramaters-for-scala
javaOptions <++= (managedClasspath in Runtime, packageBin in Compile) map { (attList, bin) =>
javaOptions <++= (dependencyClasspath in Runtime, packageBin in Compile) map { (attList, bin) =>
// put the Scala {library, reflect} in the classpath
val path = for {
file <- attList.map(_.data)
Expand Down
20 changes: 16 additions & 4 deletions src/dotty/tools/backend/jvm/GenBCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import dotty.tools.dotc
import dotty.tools.dotc.backend.jvm.DottyPrimitives
import dotty.tools.dotc.transform.Erasure

import dotty.tools.dotc.interfaces
import java.util.Optional

import scala.reflect.ClassTag
import dotty.tools.dotc.core._
import Periods._
Expand Down Expand Up @@ -51,7 +54,17 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter

var tree: Tree = _

val sourceJFile: JFile = ctx.compilationUnit.source.file.file
val sourceFile = ctx.compilationUnit.source

/** Convert a `scala.reflect.io.AbstractFile` into a
* `dotty.tools.dotc.interfaces.AbstractFile`.
*/
private[this] def convertAbstractFile(absfile: scala.reflect.io.AbstractFile): interfaces.AbstractFile =
new interfaces.AbstractFile {
override def name = absfile.name
override def path = absfile.path
override def jfile = Optional.ofNullable(absfile.file)
}

final class PlainClassBuilder(cunit: CompilationUnit) extends SyncAndTryBuilder(cunit)

Expand Down Expand Up @@ -307,7 +320,7 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter
// Statistics.stopTimer(BackendStats.bcodeTimer, bcodeStart)

if (ctx.compilerCallback != null)
ctx.compilerCallback.onSourceCompiled(sourceJFile)
ctx.compilerCallback.onSourceCompiled(sourceFile)

/* TODO Bytecode can be verified (now that all classfiles have been written to disk)
*
Expand Down Expand Up @@ -373,10 +386,9 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter
else getFileForClassfile(outFolder, jclassName, ".class")
bytecodeWriter.writeClass(jclassName, jclassName, jclassBytes, outFile)

val outJFile = outFile.file
val className = jclassName.replace('/', '.')
if (ctx.compilerCallback != null)
ctx.compilerCallback.onClassGenerated(sourceJFile, outJFile, className)
ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(outFile), className)
}
catch {
case e: FileConflictException =>
Expand Down
42 changes: 0 additions & 42 deletions src/dotty/tools/dotc/CompilerCallback.scala

This file was deleted.

Loading

0 comments on commit 7e7ee82

Please sign in to comment.