Skip to content

Commit 7e7ee82

Browse files
committed
Add a dotty-interfaces package
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 #1011).
1 parent 94b41d5 commit 7e7ee82

19 files changed

+397
-71
lines changed

bin/dotc

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ ReplMain=test.DottyRepl
4242

4343

4444

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

78+
checkjar $INTERFACES_JAR interfaces/package interfaces
7779
checkjar $MAIN_JAR package src
7880
checkjar $TEST_JAR test:package test
7981

@@ -198,6 +200,8 @@ classpathArgs () {
198200
else
199201
toolchain="$SCALA_LIBRARY_JAR:$SCALA_REFLECT_JAR:$SCALA_COMPILER_JAR:$JLINE_JAR"
200202
fi
203+
bcpJars="$INTERFACES_JAR:$MAIN_JAR"
204+
cpJars="$INTERFACES_JAR:$MAIN_JAR:$TEST_JAR"
201205

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

209213
if [[ -n $bootcp ]]; then
210-
boot_classpath="$(cygpath --path --$format "$toolchain:$MAIN_JAR")"
211-
classpath="$(cygpath --path --$format "$MAIN_JAR:$TEST_JAR")"
214+
boot_classpath="$(cygpath --path --$format "$toolchain:$bcpJars")"
215+
classpath="$(cygpath --path --$format "$cpJars")"
212216
cpArgs="-Xbootclasspath/a:$boot_classpath -classpath $classpath"
213217
else
214-
classpath="$(cygpath --path --$format "$toolchain:$MAIN_JAR:$TEST_JAR")"
218+
classpath="$(cygpath --path --$format "$toolchain:$cpJars")"
215219
cpArgs="-classpath $classpath"
216220
fi
217221
else
218222
if [[ -n $bootcp ]]; then
219-
cpArgs="-Xbootclasspath/a:$toolchain:$MAIN_JAR -classpath $MAIN_JAR:$TEST_JAR"
223+
cpArgs="-Xbootclasspath/a:$toolchain:$bcpJars -classpath $cpJars"
220224
else
221-
cpArgs="-classpath $toolchain:$MAIN_JAR:$TEST_JAR"
225+
cpArgs="-classpath $toolchain:$cpJars"
222226
fi
223227
fi
224228
echo ${cpArgs}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
import java.io.File;
4+
import java.util.Optional;
5+
6+
/** An abstract file may either be a file on disk or a virtual file.
7+
*
8+
* Do not rely on the identity of instances of this class.
9+
*
10+
* User code should not implement this interface, but it may have to
11+
* manipulate objects of this type.
12+
*/
13+
public interface AbstractFile {
14+
/** The name of this file, note that two files may have the same name. */
15+
String name();
16+
17+
/** The path of this file, this might be a virtual path of an unspecified format. */
18+
String path();
19+
20+
/** If this is a real file on disk, a `java.io.File` that corresponds to this file.
21+
* Otherwise, an empty `Optional`.
22+
*/
23+
Optional<File> jfile();
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
/** Set of callbacks called in response to events during the compilation process.
4+
*
5+
* You should implement this interface if you want to react to one or more of
6+
* these events.
7+
*
8+
* @see the method `process` of `dotty.tools.dotc.Driver` for more information.
9+
*/
10+
public interface CompilerCallback {
11+
/** Called when a class has been generated.
12+
*
13+
* @param source The source file corresponding to this class.
14+
* Example: ./src/library/scala/collection/Seq.scala
15+
* @param generatedClass The generated classfile for this class.
16+
* Example: ./scala/collection/Seq$.class
17+
* @param className The name of this class.
18+
* Example: scala.collection.Seq$
19+
*/
20+
default void onClassGenerated(SourceFile source, AbstractFile generatedClass, String className) {};
21+
22+
/** Called when every class for this file has been generated.
23+
*
24+
* @param source The source file.
25+
* Example: ./src/library/scala/collection/Seq.scala
26+
*/
27+
default void onSourceCompiled(SourceFile source) {};
28+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
import java.util.Optional;
4+
5+
/** A diagnostic is a message emitted during the compilation process.
6+
*
7+
* It can either be an error, a warning or an information.
8+
*
9+
* User code should not implement this interface, but it may have to
10+
* manipulate objects of this type.
11+
*/
12+
public interface Diagnostic {
13+
public static final int ERROR = 2;
14+
public static final int WARNING = 1;
15+
public static final int INFO = 0;
16+
17+
/** The message to report */
18+
String message();
19+
20+
/** Level of the diagnostic, can be either ERROR, WARNING or INFO */
21+
int level();
22+
23+
/** The position in a source file of the code that caused this diagnostic
24+
* to be emitted. */
25+
Optional<SourcePosition> position();
26+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
/** Summary of the diagnostics emitted by a Reporter.
4+
*
5+
* User code should not implement this interface, but it may have to
6+
* manipulate objects of this type.
7+
*/
8+
public interface ReporterResult {
9+
/** Have we emitted any error ? */
10+
boolean hasErrors();
11+
/** Number of errors that have been emitted */
12+
int errorCount();
13+
14+
/** Have we emitted any warning ? */
15+
boolean hasWarnings();
16+
/** Number of warnings that have been emitted */
17+
int warningCount();
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
/** Report errors, warnings and info messages during the compilation process
4+
*
5+
* You should implement this interface if you want to handle the diagnostics
6+
* returned by the compiler yourself.
7+
*
8+
* @see the method `process` of `dotty.tools.dotc.Driver` for more information.
9+
*/
10+
public interface SimpleReporter {
11+
/** Report a diagnostic. */
12+
void report(Diagnostic diag);
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
import java.io.File;
4+
5+
/** A source file.
6+
*
7+
* User code should not implement this interface, but it may have to
8+
* manipulate objects of this type.
9+
*/
10+
public interface SourceFile extends AbstractFile {
11+
/** The content of this file as seen by the compiler. */
12+
char[] content();
13+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package dotty.tools.dotc.interfaces;
2+
3+
/** A position in a source file.
4+
*
5+
* A position is a range between a start offset and an end offset, as well as a
6+
* point inside this range.
7+
*
8+
* As a convenience, we also provide methods that return the line and the column
9+
* corresponding to each offset.
10+
*
11+
* User code should not implement this interface, but it may have to
12+
* manipulate objects of this type.
13+
*/
14+
public interface SourcePosition {
15+
/** Content of the line which contains the point */
16+
String lineContent();
17+
18+
/** Offset to the point */
19+
int point();
20+
/** Line number of the point, starting at 0 */
21+
int line();
22+
/** Column number of the point, starting at 0 */
23+
int column();
24+
25+
/** Offset to the range start */
26+
int start();
27+
/** Line number of the range start, starting at 0 */
28+
int startLine();
29+
/** Column number of the range start, starting at 0 */
30+
int startColumn();
31+
32+
/** Offset to the range end */
33+
int end();
34+
/** Line number of the range end, starting at 0 */
35+
int endLine();
36+
/** Column number of the range end, starting at 0 */
37+
int endColumn();
38+
39+
/** The source file corresponding to this position.
40+
* The values returned by `point()`, `start()` and `end()`
41+
* are indices in the array returned by `source().content()`.
42+
*/
43+
SourceFile source();
44+
}

project/Build.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,16 @@ object DottyBuild extends Build {
3535
)
3636
}
3737

38+
lazy val `dotty-interfaces` = project.in(file("interfaces")).
39+
settings(
40+
// Do not append Scala versions to the generated artifacts
41+
crossPaths := false,
42+
// Do not depend on the Scala library
43+
autoScalaLibrary := false
44+
)
45+
3846
lazy val dotty = project.in(file(".")).
47+
dependsOn(`dotty-interfaces`).
3948
settings(
4049
// set sources to src/, tests to test/ and resources to resources/
4150
scalaSource in Compile := baseDirectory.value / "src",
@@ -105,7 +114,7 @@ object DottyBuild extends Build {
105114
parallelExecution in Test := false,
106115

107116
// http://grokbase.com/t/gg/simple-build-tool/135ke5y90p/sbt-setting-jvm-boot-paramaters-for-scala
108-
javaOptions <++= (managedClasspath in Runtime, packageBin in Compile) map { (attList, bin) =>
117+
javaOptions <++= (dependencyClasspath in Runtime, packageBin in Compile) map { (attList, bin) =>
109118
// put the Scala {library, reflect} in the classpath
110119
val path = for {
111120
file <- attList.map(_.data)

src/dotty/tools/backend/jvm/GenBCode.scala

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import dotty.tools.dotc
1313
import dotty.tools.dotc.backend.jvm.DottyPrimitives
1414
import dotty.tools.dotc.transform.Erasure
1515

16+
import dotty.tools.dotc.interfaces
17+
import java.util.Optional
18+
1619
import scala.reflect.ClassTag
1720
import dotty.tools.dotc.core._
1821
import Periods._
@@ -51,7 +54,17 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter
5154

5255
var tree: Tree = _
5356

54-
val sourceJFile: JFile = ctx.compilationUnit.source.file.file
57+
val sourceFile = ctx.compilationUnit.source
58+
59+
/** Convert a `scala.reflect.io.AbstractFile` into a
60+
* `dotty.tools.dotc.interfaces.AbstractFile`.
61+
*/
62+
private[this] def convertAbstractFile(absfile: scala.reflect.io.AbstractFile): interfaces.AbstractFile =
63+
new interfaces.AbstractFile {
64+
override def name = absfile.name
65+
override def path = absfile.path
66+
override def jfile = Optional.ofNullable(absfile.file)
67+
}
5568

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

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

309322
if (ctx.compilerCallback != null)
310-
ctx.compilerCallback.onSourceCompiled(sourceJFile)
323+
ctx.compilerCallback.onSourceCompiled(sourceFile)
311324

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

376-
val outJFile = outFile.file
377389
val className = jclassName.replace('/', '.')
378390
if (ctx.compilerCallback != null)
379-
ctx.compilerCallback.onClassGenerated(sourceJFile, outJFile, className)
391+
ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(outFile), className)
380392
}
381393
catch {
382394
case e: FileConflictException =>

src/dotty/tools/dotc/CompilerCallback.scala

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)