From 9411539eac8aaa8f052b7e6701d5d1d2b833d409 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 1 Dec 2016 23:54:39 +0100 Subject: 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 --- compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 30 +++++++++++++++++----- .../sbt-test/discovery/test-discovery/build.sbt | 1 + .../discovery/test-discovery/changes/A2.scala | 3 +++ .../project/DottyInjectedPlugin.scala | 17 ++++++++++++ .../test-discovery/src/main/scala/A.scala | 3 +++ .../test-discovery/src/test/scala/TestA.scala | 8 ++++++ sbt-bridge/sbt-test/discovery/test-discovery/test | 8 ++++++ 7 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 sbt-bridge/sbt-test/discovery/test-discovery/build.sbt create mode 100644 sbt-bridge/sbt-test/discovery/test-discovery/changes/A2.scala create mode 100644 sbt-bridge/sbt-test/discovery/test-discovery/project/DottyInjectedPlugin.scala create mode 100644 sbt-bridge/sbt-test/discovery/test-discovery/src/main/scala/A.scala create mode 100644 sbt-bridge/sbt-test/discovery/test-discovery/src/test/scala/TestA.scala create mode 100644 sbt-bridge/sbt-test/discovery/test-discovery/test diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index bc8528c05..1fffe6841 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -3,7 +3,7 @@ package sbt import ast.{Trees, tpd} import core._, core.Decorators._ -import Contexts._, Flags._, Phases._, Trees._, Types._, Symbols._ +import Annotations._, Contexts._, Flags._, Phases._, Trees._, Types._, Symbols._ import Names._, NameOps._, StdNames._ import typer.Inliner @@ -333,7 +333,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // TODO: Never dealias. We currently have to dealias because // sbt main class discovery relies on the signature of the main // method being fully dealiased. See https://github.com/sbt/zinc/issues/102 - val tp2 = if (!tp.isHK) tp.dealias else tp + val tp2 = if (!tp.isHK) tp.dealiasKeepAnnots else tp tp2 match { case NoPrefix | NoType => Constants.emptyType @@ -411,9 +411,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder case ConstantType(constant) => new api.Constant(apiType(constant.tpe), constant.stringValue) case AnnotatedType(tpe, annot) => - // TODO: Annotation support - ctx.debuglog(i"sbt-api: skipped annotation in $tp2") - apiType(tpe) + new api.Annotated(apiType(tpe), Array(apiAnnotation(annot))) case tp: ThisType => apiThis(tp.cls) case tp: ParamType => @@ -498,7 +496,6 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder sym.is(Implicit), sym.is(Lazy), sym.is(Macro), sym.is(SuperAccessor)) } - // TODO: Support other annotations def apiAnnotations(s: Symbol): List[api.Annotation] = { val annots = new mutable.ListBuffer[api.Annotation] @@ -513,6 +510,27 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder annots += marker(Inliner.bodyToInline(s).show(printTypesCtx).toString) } + // In the Scala2 ExtractAPI phase we only extract annotations that extend + // StaticAnnotation, but in Dotty we currently pickle all annotations so we + // extract everything (except inline body annotations which are handled + // above). + s.annotations.filter(_.symbol != defn.BodyAnnot) foreach { annot => + annots += apiAnnotation(annot) + } + annots.toList } + + def apiAnnotation(annot: Annotation): api.Annotation = { + // FIXME: To faithfully extract an API we should extract the annotation tree, + // sbt instead wants us to extract the annotation type and its arguments, + // to do this properly we would need a way to hash trees and types in dotty itself, + // instead we pretty-print the annotation tree. + // However, we still need to extract the annotation type in the way sbt expect + // because sbt uses this information to find tests to run (for example + // junit tests are annotated @org.junit.Test). + new api.Annotation( + apiType(annot.tree.tpe), // Used by sbt to find tests to run + Array(new api.AnnotationArgument("FULLTREE", annot.tree.show.toString))) + } } diff --git a/sbt-bridge/sbt-test/discovery/test-discovery/build.sbt b/sbt-bridge/sbt-test/discovery/test-discovery/build.sbt new file mode 100644 index 000000000..68b89ac35 --- /dev/null +++ b/sbt-bridge/sbt-test/discovery/test-discovery/build.sbt @@ -0,0 +1 @@ +libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" diff --git a/sbt-bridge/sbt-test/discovery/test-discovery/changes/A2.scala b/sbt-bridge/sbt-test/discovery/test-discovery/changes/A2.scala new file mode 100644 index 000000000..696daab5e --- /dev/null +++ b/sbt-bridge/sbt-test/discovery/test-discovery/changes/A2.scala @@ -0,0 +1,3 @@ +object A { + def three: Int = 3 // Ah! +} diff --git a/sbt-bridge/sbt-test/discovery/test-discovery/project/DottyInjectedPlugin.scala b/sbt-bridge/sbt-test/discovery/test-discovery/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000..ec22870f4 --- /dev/null +++ b/sbt-bridge/sbt-test/discovery/test-discovery/project/DottyInjectedPlugin.scala @@ -0,0 +1,17 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := "0.1-SNAPSHOT", + scalaOrganization := "ch.epfl.lamp", + scalacOptions += "-language:Scala2", + scalaBinaryVersion := "2.11", + autoScalaLibrary := false, + libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"), + scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-sbt-bridge" % "0.1.1-SNAPSHOT" % "component").sources() + ) +} diff --git a/sbt-bridge/sbt-test/discovery/test-discovery/src/main/scala/A.scala b/sbt-bridge/sbt-test/discovery/test-discovery/src/main/scala/A.scala new file mode 100644 index 000000000..81f77071e --- /dev/null +++ b/sbt-bridge/sbt-test/discovery/test-discovery/src/main/scala/A.scala @@ -0,0 +1,3 @@ +object A { + def three: Int = 4 // Hmm +} diff --git a/sbt-bridge/sbt-test/discovery/test-discovery/src/test/scala/TestA.scala b/sbt-bridge/sbt-test/discovery/test-discovery/src/test/scala/TestA.scala new file mode 100644 index 000000000..1d4f479d8 --- /dev/null +++ b/sbt-bridge/sbt-test/discovery/test-discovery/src/test/scala/TestA.scala @@ -0,0 +1,8 @@ +import org.junit.Test +import org.junit.Assert.assertEquals + +class TestA { + @Test def testThree = { + assertEquals(A.three, 3) + } +} diff --git a/sbt-bridge/sbt-test/discovery/test-discovery/test b/sbt-bridge/sbt-test/discovery/test-discovery/test new file mode 100644 index 000000000..2e035dee8 --- /dev/null +++ b/sbt-bridge/sbt-test/discovery/test-discovery/test @@ -0,0 +1,8 @@ +> compile +# Intentionally failing test +-> test +# Fix the bug! +$ copy-file changes/A2.scala src/main/scala/A.scala +> compile +# The test should pass now +> test -- cgit v1.2.3