Skip to content

Commit 9411539

Browse files
committed
Fix #1741: sbt.ExtractAPI: extract annotations
This is necessary for correct incremental recompilation but is also used by sbt to find tests to run (for junit they should be annotated @org.junit.Test). I added an sbt scripted test to verify that JUnit now works, to run it: $ sbt > scripted discovery/test-discovery
1 parent 3d0accd commit 9411539

File tree

7 files changed

+64
-6
lines changed

7 files changed

+64
-6
lines changed

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package sbt
33

44
import ast.{Trees, tpd}
55
import core._, core.Decorators._
6-
import Contexts._, Flags._, Phases._, Trees._, Types._, Symbols._
6+
import Annotations._, Contexts._, Flags._, Phases._, Trees._, Types._, Symbols._
77
import Names._, NameOps._, StdNames._
88
import typer.Inliner
99

@@ -333,7 +333,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
333333
// TODO: Never dealias. We currently have to dealias because
334334
// sbt main class discovery relies on the signature of the main
335335
// method being fully dealiased. See https://github.com/sbt/zinc/issues/102
336-
val tp2 = if (!tp.isHK) tp.dealias else tp
336+
val tp2 = if (!tp.isHK) tp.dealiasKeepAnnots else tp
337337
tp2 match {
338338
case NoPrefix | NoType =>
339339
Constants.emptyType
@@ -411,9 +411,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
411411
case ConstantType(constant) =>
412412
new api.Constant(apiType(constant.tpe), constant.stringValue)
413413
case AnnotatedType(tpe, annot) =>
414-
// TODO: Annotation support
415-
ctx.debuglog(i"sbt-api: skipped annotation in $tp2")
416-
apiType(tpe)
414+
new api.Annotated(apiType(tpe), Array(apiAnnotation(annot)))
417415
case tp: ThisType =>
418416
apiThis(tp.cls)
419417
case tp: ParamType =>
@@ -498,7 +496,6 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
498496
sym.is(Implicit), sym.is(Lazy), sym.is(Macro), sym.is(SuperAccessor))
499497
}
500498

501-
// TODO: Support other annotations
502499
def apiAnnotations(s: Symbol): List[api.Annotation] = {
503500
val annots = new mutable.ListBuffer[api.Annotation]
504501

@@ -513,6 +510,27 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
513510
annots += marker(Inliner.bodyToInline(s).show(printTypesCtx).toString)
514511
}
515512

513+
// In the Scala2 ExtractAPI phase we only extract annotations that extend
514+
// StaticAnnotation, but in Dotty we currently pickle all annotations so we
515+
// extract everything (except inline body annotations which are handled
516+
// above).
517+
s.annotations.filter(_.symbol != defn.BodyAnnot) foreach { annot =>
518+
annots += apiAnnotation(annot)
519+
}
520+
516521
annots.toList
517522
}
523+
524+
def apiAnnotation(annot: Annotation): api.Annotation = {
525+
// FIXME: To faithfully extract an API we should extract the annotation tree,
526+
// sbt instead wants us to extract the annotation type and its arguments,
527+
// to do this properly we would need a way to hash trees and types in dotty itself,
528+
// instead we pretty-print the annotation tree.
529+
// However, we still need to extract the annotation type in the way sbt expect
530+
// because sbt uses this information to find tests to run (for example
531+
// junit tests are annotated @org.junit.Test).
532+
new api.Annotation(
533+
apiType(annot.tree.tpe), // Used by sbt to find tests to run
534+
Array(new api.AnnotationArgument("FULLTREE", annot.tree.show.toString)))
535+
}
518536
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object A {
2+
def three: Int = 3 // Ah!
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import sbt._
2+
import Keys._
3+
4+
object DottyInjectedPlugin extends AutoPlugin {
5+
override def requires = plugins.JvmPlugin
6+
override def trigger = allRequirements
7+
8+
override val projectSettings = Seq(
9+
scalaVersion := "0.1-SNAPSHOT",
10+
scalaOrganization := "ch.epfl.lamp",
11+
scalacOptions += "-language:Scala2",
12+
scalaBinaryVersion := "2.11",
13+
autoScalaLibrary := false,
14+
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
15+
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-sbt-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
16+
)
17+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object A {
2+
def three: Int = 4 // Hmm
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import org.junit.Test
2+
import org.junit.Assert.assertEquals
3+
4+
class TestA {
5+
@Test def testThree = {
6+
assertEquals(A.three, 3)
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
> compile
2+
# Intentionally failing test
3+
-> test
4+
# Fix the bug!
5+
$ copy-file changes/A2.scala src/main/scala/A.scala
6+
> compile
7+
# The test should pass now
8+
> test

0 commit comments

Comments
 (0)