Skip to content

Commit

Permalink
only force some annotations
Browse files Browse the repository at this point in the history
this is because we can get away with not forcing most annotations.

Keep forcing scala.annotation.internal.Child.

so a problematic annotation will stay unforced unless the user uses
some macro to force all annotations on all methods.
  • Loading branch information
bishabosha committed Jul 6, 2022
1 parent 9a1f40b commit b208ced
Show file tree
Hide file tree
Showing 21 changed files with 289 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ class TreeUnpickler[Tasty <: TastyUniverse](
rdr.readTerm()(ctx)
}
)(annotCtx.retractMode(IndexScopedStats))
DeferredAnnotation.fromTree(mkTree)
DeferredAnnotation.fromTree(annotSym)(mkTree)
}

private def traceAnnotation(annotStart: Addr, annotSym: Symbol, annotee: Symbol) = TraceInfo[Tree](
Expand Down
12 changes: 6 additions & 6 deletions src/compiler/scala/tools/nsc/tasty/bridge/AnnotationOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,19 @@ trait AnnotationOps { self: TastyUniverse =>
}
}

sealed abstract class DeferredAnnotation {
sealed abstract class DeferredAnnotation(annotSym: Symbol) {

private[bridge] def eager(annotee: Symbol)(implicit ctx: Context): u.AnnotationInfo
protected def eager(annotee: Symbol)(implicit ctx: Context): u.AnnotationInfo
private[bridge] final def lzy(annotee: Symbol)(implicit ctx: Context): u.LazyAnnotationInfo = {
u.AnnotationInfo.lazily(eager(annotee))
u.AnnotationInfo.lazily(annotSym, eager(annotee))
}
}

object DeferredAnnotation {

def fromTree(tree: Symbol => Context => Tree): DeferredAnnotation = {
new DeferredAnnotation {
private[bridge] final def eager(annotee: Symbol)(implicit ctx: Context): u.AnnotationInfo = {
def fromTree(annotSym: Symbol)(tree: Symbol => Context => Tree): DeferredAnnotation = {
new DeferredAnnotation(annotSym) {
protected final def eager(annotee: Symbol)(implicit ctx: Context): u.AnnotationInfo = {
val atree = tree(annotee)(ctx)
mkAnnotation(atree)
}
Expand Down
11 changes: 6 additions & 5 deletions src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ trait ContextOps { self: TastyUniverse =>
inIndexStatsContext(inInnerScopeContext(op)(_))(ctx)
}

/**Forces lazy annotations, if one is `scala.annotation.internal.Child` then it will add the referenced type as a
* sealed child.
/**Analyses critical annotations, critical annotations will be forced as they are necessary to
* the reading of TASTy. E.g. `scala.annotation.internal.Child` is a critical annotation that
* must be forced to add its first type argument as a sealed child.
*/
private def analyseAnnotations(sym: Symbol)(implicit ctx: Context): Unit = {

Expand All @@ -137,8 +138,7 @@ trait ContextOps { self: TastyUniverse =>
var problematic: List[String] = Nil

for (annot <- sym.annotations) {
annot.completeInfo()
if (annot.tpe.typeSymbolDirect === defn.ChildAnnot) {
if (annot.symbol === defn.ChildAnnot) {
val child = {
val child0 = lookupChild(annot.tpe.typeArgs.head)
if (child0 eq sym) {
Expand All @@ -161,6 +161,7 @@ trait ContextOps { self: TastyUniverse =>
if ((annot.symbol eq defn.TargetNameAnnotationClass) ||
(annot.symbol eq defn.StaticMethodAnnotationClass)) {
problematic ::= inOwner { implicit ctx =>
annot.completeInfo() // these should be safe to force
unsupportedMessage(s"annotation on $sym: @$annot")
}
}
Expand Down Expand Up @@ -196,7 +197,7 @@ trait ContextOps { self: TastyUniverse =>
}
else {
log(s"eagerly adding annotations to ${showSym(sym)}")
analyseAnnotations(sym.setAnnotations(annots.map(_.eager(sym))))
analyseAnnotations(sym.setAnnotations(annots.map(_.lzy(sym))))
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions test/tasty/neg/src-2/TestForcedIllegalAnnotations.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
TestForcedIllegalAnnotations_fail.scala:4: error: Unsupported Scala 3 match expression in an annotation of method forcedMatchInAnnot; note that complex trees are not yet supported for Annotations; found in method forcedMatchInAnnot in class tastytest.ForcedIllegalAnnotations.Match.
def test1 = new ForcedIllegalAnnotations.Match() // error: Unsupported Scala 3 match expression in an annotation of method forcedMatchInAnnot
^
TestForcedIllegalAnnotations_fail.scala:5: error: Unsupported Scala 3 block expression in an annotation of method forcedBlockInAnnot; note that complex trees are not yet supported for Annotations; found in method forcedBlockInAnnot in class tastytest.ForcedIllegalAnnotations.Block.
def test2 = new ForcedIllegalAnnotations.Block() // error: Unsupported Scala 3 block expression in an annotation of method forcedBlockInAnnot
^
TestForcedIllegalAnnotations_fail.scala:5: error: Unsupported Scala 3 match expression in an annotation of method forcedMatchInAnnot; note that complex trees are not yet supported for Annotations; found in method forcedMatchInAnnot in class tastytest.ForcedIllegalAnnotations.Match.
new ForcedIllegalAnnotations.Match().forcedMatchInAnnot() // error: match expression in annotation arguments
^
TestForcedIllegalAnnotations_fail.scala:8: error: Unsupported Scala 3 block expression in an annotation of method forcedBlockInAnnot; note that complex trees are not yet supported for Annotations; found in method forcedBlockInAnnot in class tastytest.ForcedIllegalAnnotations.Block.
new ForcedIllegalAnnotations.Block().forcedBlockInAnnot() // error: block expression in annotation arguments.
^
2 errors
7 changes: 5 additions & 2 deletions test/tasty/neg/src-2/TestForcedIllegalAnnotations_fail.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package tastytest

object TestForcedIllegalAnnotations {
def test1 = new ForcedIllegalAnnotations.Match() // error: Unsupported Scala 3 match expression in an annotation of method forcedMatchInAnnot
def test2 = new ForcedIllegalAnnotations.Block() // error: Unsupported Scala 3 block expression in an annotation of method forcedBlockInAnnot
def test1 =
new ForcedIllegalAnnotations.Match().forcedMatchInAnnot() // error: match expression in annotation arguments

def test2 =
new ForcedIllegalAnnotations.Block().forcedBlockInAnnot() // error: block expression in annotation arguments.
}
6 changes: 3 additions & 3 deletions test/tasty/neg/src-2/TestSelectWithTarget.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
TestSelectWithTarget_fail.scala:10: error: Unsupported Scala 3 selection of method foo with @targetName("fooString"); found in method selectFooString in object tastytest.SelectWithTarget.
def test = SelectWithTarget.selectFooString
^
TestSelectWithTarget_fail.scala:13: error: Unsupported Scala 3 selection of method foo with @targetName("fooString"); found in method selectFooString in object tastytest.SelectWithTarget.
def test = TestSelectWithTargetPre.forceAnnots[SelectWithTarget.type, SelectWithTarget.defAnnot]
^
1 error
15 changes: 9 additions & 6 deletions test/tasty/neg/src-2/TestSelectWithTarget_fail.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package tastytest

object TestSelectWithTarget {

// We error when an annotation selects a
// method overloaded with a targetAnnot
// until we can erase them correctly.
// e.g. the annotation arguments may be
// reflected by a macro into real trees
def test = SelectWithTarget.selectFooString
// We error when an annotation selects a method overloaded with a targetAnnot
// until we can erase them correctly. e.g. the annotation arguments may be
// reflected by a macro into real trees.

// here it is necessary to call a macro to force the `defAnnot` annotation,
// alternatively we could use the `@deprecated` annotation as the compiler
// will analyse that, but this illustrates that most annotations can be
// harmless unless we specificially need to analyse them.
def test = TestSelectWithTargetPre.forceAnnots[SelectWithTarget.type, SelectWithTarget.defAnnot]

}
25 changes: 25 additions & 0 deletions test/tasty/neg/src-2/TestSelectWithTarget_pre.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package tastytest

import scala.language.experimental.macros

import scala.reflect.macros.blackbox.Context

object TestSelectWithTargetPre {

/** forces annotations of type `A` on methods from class `T` */
def forceAnnots[T, A]: Unit = macro Macros.forceAnnotsImpl[T, A]

object Macros {
def forceAnnotsImpl[T, A](c: Context)(implicit T: c.WeakTypeTag[T], A: c.WeakTypeTag[A]): c.Expr[Unit] = {
import c.universe._
for {
method <- weakTypeOf[T].members.filter(_.isMethod)
annot <- method.annotations.find(_.tree.tpe =:= weakTypeOf[A])
} {
annot.tree
}
c.Expr[Unit](q"()")
}
}

}
40 changes: 40 additions & 0 deletions test/tasty/pos/pre/tastytest/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,48 @@ package object tastytest {
def compiletimeHasChild[T](child: String): Unit = macro Macros.hasChildImpl[T]
def compiletimeHasNestedChildren[T](expected: String*): Unit = macro Macros.hasChildrenImpl[T]

/** forces annotations of type `A` on methods from class `T` */
def forceAnnots[T, A, S <: String with Singleton]: Unit = macro Macros.AnnotsBundle.forceAnnotsImpl[T, A, S]

object Macros {

class AnnotsBundle(val c: Context) {
import c.universe._

private def annotType(annot: Annotation): Type = annot.tree.tpe match {
case TypeBounds(lo, hi) => hi
case tpe => tpe
}

private def toExplore[T](implicit T: c.WeakTypeTag[T]): List[Symbol] = (
weakTypeOf[T].typeSymbol
+: weakTypeOf[T].typeSymbol.asInstanceOf[ClassSymbol].primaryConstructor
+: weakTypeOf[T].members.filter(_.isMethod).toList.flatMap(method =>
method :: method.asInstanceOf[MethodSymbol].paramLists.flatten
)
)

private def stringAssert[S <: String with Singleton](implicit S: c.WeakTypeTag[S]): String =
weakTypeOf[S] match {
case ConstantType(Constant(str: String)) => str
case _ => ???
}

def forceAnnotsImpl[T, A, S <: String with Singleton](implicit T: c.WeakTypeTag[T], A: c.WeakTypeTag[A], S: c.WeakTypeTag[S]): c.Expr[Unit] = {
val trees = {
for {
defn <- toExplore[T]
annot <- defn.annotations.filter(annotType(_).typeSymbol == weakTypeOf[A].typeSymbol)
} yield {
s"${annot.tree}"
}
}
val annotStr = trees.head
assert(annotStr == stringAssert[S], s"actually, was $annotStr")
c.Expr[Unit](q"()")
}
}

def hasChildrenImpl[T](c: Context)(expected: c.Expr[String]*)(implicit T: c.WeakTypeTag[T]): c.Expr[Unit] = {
import c.universe._

Expand Down
35 changes: 28 additions & 7 deletions test/tasty/pos/src-2/tastytest/TestAnnotated.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
package tastytest

object TestAnnotated {
def test2 = new RootAnnotated {}
def test2 = forceAnnots[RootAnnotated, rootAnnot, "new tastytest.rootAnnot(1)"]
def test3 = {
val o = new OuterClassAnnotated {}
o.foo
forceAnnots[
OuterClassAnnotated,
basicAnnot[String],
"new <: tastytest.basicAnnot[String](OuterClassAnnotated.this.xyz)"
]
}
def test4 = {
forceAnnots[
ParameterizedAnnotated,
basicAnnot[Int],
"new <: tastytest.basicAnnot[Int](tastytest#ParameterizedAnnotated.type.value)"
]
}
def test4 = new ParameterizedAnnotated(23).foo
def test5 = {
val o = new OuterAnnotated {}
o.foo
forceAnnots[OuterAnnotated, o.innerAnnot, "new OuterAnnotated.this.innerAnnot(new Inner())"]
}
def test6 = {
forceAnnots[
SelectInAnnotated.AmbiguousAnnotated,
SelectInAnnotated.ambig.annot,
"new tastytest.SelectInAnnotated.ambig.annot(tastytest.SelectInAnnotated.e.type)"
]
}
def test7 = {
forceAnnots[
SelectInAnnotatedinParent.AmbiguousAnnotated,
SelectInAnnotatedinParent.ambig.annotBox,
"new tastytest.SelectInAnnotatedinParent.ambig.annotBox(0.0)"
]
}
def test6 = new SelectInAnnotated.AmbiguousAnnotated {}
def test7 = new SelectInAnnotatedinParent.AmbiguousAnnotated {}
}
8 changes: 7 additions & 1 deletion test/tasty/pos/src-2/tastytest/TestArrayAnnot.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package tastytest

object TestArrayAnnot {
def test = new Tagged()
def test = {
forceAnnots[
Tagged,
SuppressWarnings,
"new SuppressWarnings(Array.type.apply[String]((Array[String]{\"xyz\", \"foo\"}: String*))(reflect#ClassTag.type.apply[String](classOf[java.lang.String])))"
]
}
}
10 changes: 9 additions & 1 deletion test/tasty/pos/src-2/tastytest/TestFromJavaObjectConsume.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ package tastytest

object TestFromJavaObjectConsume {

def test = new FromJavaObjectConsume.Foo {}
def test1 = new FromJavaObjectConsume.Foo {}

def test2 = {
forceAnnots[
FromJavaObjectConsume.Foo,
basicAnnot[Any],
"new <: tastytest.basicAnnot[Int](tastytest#FromJavaObjectBox.type.id[Int](23))"
]
}

}
6 changes: 5 additions & 1 deletion test/tasty/pos/src-2/tastytest/TestNestedAnnot.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package tastytest

object TestNestedAnnot {
def test = new TaggedMega.Tagged()
def test = {
val _ = new TaggedMega.Tagged()
compiletimeHasChild[TaggedMega.ForceChildren]("tastytest.TaggedMega.Nested.Nested2.Tags")
forceAnnots[TaggedMega.Tagged, TaggedMega.Nested.Nested2.Tags, "new tastytest#TaggedMega.Nested.Nested2.Tags(\"xyz,foo\")"]
}
}
8 changes: 8 additions & 0 deletions test/tasty/pos/src-2/tastytest/TestReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,12 @@ package tastytest

object TestReflection {
def test1 = assert(new Reflection().fieldAtIndex(1) == "Hello") // test reading `classOf` in the @throws annotation

def test2 = {
forceAnnots[
Reflection,
scala.throws[Throwable],
"new <: throws[IndexOutOfBoundsException](classOf[java.lang.IndexOutOfBoundsException])"
]
}
}
42 changes: 42 additions & 0 deletions test/tasty/run/pre/tastytest/package.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import scala.language.experimental.macros

import scala.util.Random

import scala.reflect.macros.blackbox.Context
Expand Down Expand Up @@ -37,8 +39,48 @@ package object tastytest {
def poly[T: c.WeakTypeTag]: Tree = q"${c.weakTypeOf[T].toString}"
}

/** forces annotations of type `A` on methods from class `T` */
def forceAnnots[T, A, S <: String with Singleton]: Unit = macro Macros.AnnotsBundle.forceAnnotsImpl[T, A, S]

object Macros {

class AnnotsBundle(val c: Context) {
import c.universe._

private def annotType(annot: Annotation): Type = annot.tree.tpe match {
case TypeBounds(lo, hi) => hi
case tpe => tpe
}

private def toExplore[T](implicit T: c.WeakTypeTag[T]): List[Symbol] = (
weakTypeOf[T].typeSymbol
+: weakTypeOf[T].typeSymbol.asInstanceOf[ClassSymbol].primaryConstructor
+: weakTypeOf[T].members.filter(_.isMethod).toList.flatMap(method =>
method :: method.asInstanceOf[MethodSymbol].paramLists.flatten
)
)

private def stringAssert[S <: String with Singleton](implicit S: c.WeakTypeTag[S]): String =
weakTypeOf[S] match {
case ConstantType(Constant(str: String)) => str
case _ => ???
}

def forceAnnotsImpl[T, A, S <: String with Singleton](implicit T: c.WeakTypeTag[T], A: c.WeakTypeTag[A], S: c.WeakTypeTag[S]): c.Expr[Unit] = {
val trees = {
for {
defn <- toExplore[T]
annot <- defn.annotations.filter(annotType(_).typeSymbol == weakTypeOf[A].typeSymbol)
} yield {
s"${annot.tree}"
}
}
val annotStr = trees.head
assert(annotStr == stringAssert[S], s"actually, was $annotStr")
c.Expr[Unit](q"()")
}
}

def hasStaticAnnotImpl[T, A](c: Context)(implicit T: c.WeakTypeTag[T], A: c.WeakTypeTag[A]): c.Expr[Boolean] = {
import c.universe._
if (weakTypeOf[T].members.filter(_.isMethod).exists(_.annotations.exists(_.tree.tpe =:= weakTypeOf[A]))) {
Expand Down
13 changes: 13 additions & 0 deletions test/tasty/run/src-2/tastytest/TestArrayCtors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,17 @@ object TestArrayCtors extends Suite("TestArrayCtors") {
test(assert(EmptyArrayCtor != null))
test(assert(EmptyArrayCtor2 != null))

def compiletimeAsserts = {
def test1 = forceAnnots[
ArrayCtors.EmptyArrayCtor.type,
ArrayCtors.arrayAnnot,
"new tastytest.ArrayCtors.arrayAnnot(Array.type.apply[tastytest.ArrayCtors.Module.type.type]((Array[tastytest.ArrayCtors.Module.type]{}: tastytest.ArrayCtors.Module.type*))(reflect#ClassTag.type.apply[tastytest.ArrayCtors.Module.type](classOf[tastytest.ArrayCtors$$Module])))"
]
def test2 = forceAnnots[
ArrayCtors.EmptyArrayCtor2.type,
ArrayCtors.arrayAnnot2,
"new tastytest.ArrayCtors.arrayAnnot2(Array.type.apply[Array[tastytest.ArrayCtors.Module.type.type]]((Array[Array[tastytest.ArrayCtors.Module.type]]{}: Array[tastytest.ArrayCtors.Module.type]*))(reflect#ClassTag.type.apply[tastytest.ArrayCtors.Module.type](classOf[tastytest.ArrayCtors$$Module]).wrap))"
]
}

}
13 changes: 13 additions & 0 deletions test/tasty/run/src-2/tastytest/TestCtorUsingClauses.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,17 @@ object TestCtorUsingClauses extends Suite("TestCtorUsingClauses") {

test(assert(new CtorUsingClauses.Annotated().j === 47))
test(assert(new CtorUsingClauses.AnnotatedOld().k === 97))

def compiletimeAsserts = {
def test1 = forceAnnots[
CtorUsingClauses.Annotated,
CtorUsingClauses.CtxAnnot,
"new tastytest.CtorUsingClauses.CtxAnnot(tastytest#CtorUsingClauses.CtorUsingClause.given_Int.type)"
]
def test2 = forceAnnots[
CtorUsingClauses.AnnotatedOld,
CtorUsingClauses.CtxAnnotOld,
"new tastytest.CtorUsingClauses.CtxAnnotOld(tastytest#CtorUsingClauses.CtorUsingClause.given_Int.type)"
]
}
}

0 comments on commit b208ced

Please sign in to comment.