Skip to content

Fix emission of annotations #1763

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

Merged
merged 4 commits into from
Dec 16, 2016
Merged
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
23 changes: 10 additions & 13 deletions compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -206,18 +206,15 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
implicit val ConstantClassTag: ClassTag[Constant] = ClassTag[Constant](classOf[Constant])
implicit val ClosureTag: ClassTag[Closure] = ClassTag[Closure](classOf[Closure])

/* dont emit any annotations for now*/
def isRuntimeVisible(annot: Annotation): Boolean = {
annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match {
case Some(retentionAnnot) =>
retentionAnnot.tree.find(_.symbol == AnnotationRetentionRuntimeAttr).isDefined
case _ =>
// SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
// annotation is emitted with visibility `RUNTIME`
// dotty bug: #389
true
def isRuntimeVisible(annot: Annotation): Boolean =
if (toDenot(annot.atp.typeSymbol).hasAnnotation(AnnotationRetentionAttr))
retentionPolicyOf(annot) == AnnotationRetentionRuntimeAttr
else {
// SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
// annotation is emitted with visibility `RUNTIME`
// dotty bug: #389
true
}
}

def shouldEmitAnnotation(annot: Annotation): Boolean = {
annot.symbol.isJavaDefined &&
Expand All @@ -227,7 +224,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma

private def retentionPolicyOf(annot: Annotation): Symbol =
annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).
flatMap(_.argument(0).map(_.symbol)).getOrElse(AnnotationRetentionClassAttr)
flatMap(_.argumentConstant(0).map(_.symbolValue)).getOrElse(AnnotationRetentionClassAttr)

private def emitArgument(av: AnnotationVisitor,
name: String,
Expand Down Expand Up @@ -708,7 +705,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}
else Nil

def annotations: List[Annotation] = Nil
def annotations: List[Annotation] = toDenot(sym).annotations
def companionModuleMembers: List[Symbol] = {
// phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes,
// not local classes of the companion module (E in the exmaple) that were lifted by lambdalift.
Expand Down
30 changes: 24 additions & 6 deletions compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 =>
Expand Down Expand Up @@ -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]

Expand All @@ -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)))
}
}
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/TreeTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package dotc
package transform

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Annotations.ConcreteAnnotation
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.DenotTransformers.{InfoTransformer, DenotTransformer}
import dotty.tools.dotc.core.Denotations.SingleDenotation
Expand Down Expand Up @@ -181,10 +180,15 @@ object TreeTransforms {
abstract override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation =
super.transform(ref) match {
case ref1: SymDenotation if ref1.symbol.isDefinedInCurrentRun =>
val annotTrees = ref1.annotations.map(_.tree)
val annots = ref1.annotations
val annotTrees = annots.map(_.tree)
val annotTrees1 = annotTrees.mapConserve(annotationTransformer.macroTransform)
if (annotTrees eq annotTrees1) ref1
else ref1.copySymDenotation(annotations = annotTrees1.map(new ConcreteAnnotation(_)))
else {
val derivedAnnots = (annots, annotTrees1).zipped.map((annot, annotTree1) =>
annot.derivedAnnotation(annotTree1))
ref1.copySymDenotation(annotations = derivedAnnots)
}
case ref1 =>
ref1
}
Expand Down
1 change: 1 addition & 0 deletions sbt-bridge/sbt-test/discovery/test-discovery/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
3 changes: 3 additions & 0 deletions sbt-bridge/sbt-test/discovery/test-discovery/changes/A2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object A {
def three: Int = 3 // Ah!
}
Original file line number Diff line number Diff line change
@@ -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()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object A {
def three: Int = 4 // Hmm
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import org.junit.Test
import org.junit.Assert.assertEquals

class TestA {
@Test def testThree = {
assertEquals(A.three, 3)
}
}
8 changes: 8 additions & 0 deletions sbt-bridge/sbt-test/discovery/test-discovery/test
Original file line number Diff line number Diff line change
@@ -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