Permalink
Browse files

SI-2577, SI-6860: annotation type inference.

This is less than ideal:

  scala> class Bippy[T] extends annotation.StaticAnnotation
  defined class Bippy

  scala> def f: Int @Bippy = 5
  f: Int @Bippy[T]

Turns out we can infer such types. Now it says:

  scala> def f: Int @Bippy = 5
  f: Int @Bippy[Nothing]

This should put to rest many an issue with parameterized
annotations.
  • Loading branch information...
1 parent 72f36cb commit 832fc9a67e5aa85bdde61883527d3ac9554094d7 @paulp paulp committed Jan 15, 2013
@@ -3458,31 +3458,28 @@ trait Typers extends Adaptations with Tags {
}
// begin typedAnnotation
- val (fun, argss) = {
- def extract(fun: Tree, outerArgss: List[List[Tree]]):
- (Tree, List[List[Tree]]) = fun match {
- case Apply(f, args) =>
- extract(f, args :: outerArgss)
- case Select(New(tpt), nme.CONSTRUCTOR) =>
- (fun, outerArgss)
- case _ =>
- reportAnnotationError(UnexpectedTreeAnnotation(fun))
- (setError(fun), outerArgss)
- }
- extract(ann, List())
- }
+ val treeInfo.Applied(fun0, targs, argss) = treeInfo.dissectApplied(ann)
+ val typedFun0 = typed(fun0, forFunMode(mode), WildcardType)
+ val typedFunPart = (
+ // If there are dummy type arguments in typeFun part, it suggests we
+ // must type the actual constructor call, not only the select. The value
+ // arguments are how the type arguments will be inferred.
+ if (targs.isEmpty && typedFun0.exists(t => isDummyAppliedType(t.tpe)))
+ logResult(s"Retyped $typedFun0 to find type args")(typed(argss.foldLeft(fun0)(Apply(_, _))))
+ else
+ typedFun0
+ )
+ val typedFun @ Select(New(annTpt), _) = treeInfo.dissectApplied(typedFunPart).core
+ val annType = annTpt.tpe
- val res = if (fun.isErroneous) ErroneousAnnotation
+ val res = if (typedFun.isErroneous) ErroneousAnnotation
else {
- val typedFun @ Select(New(tpt), _) = typed(fun, mode.forFunMode, WildcardType)
- val annType = tpt.tpe
-
if (typedFun.isErroneous) ErroneousAnnotation
else if (annType.typeSymbol isNonBottomSubClass ClassfileAnnotationClass) {
// annotation to be saved as java classfile annotation
val isJava = typedFun.symbol.owner.isJavaDefined
if (!annType.typeSymbol.isNonBottomSubClass(annClass)) {
- reportAnnotationError(AnnotationTypeMismatchError(tpt, annClass.tpe, annType))
+ reportAnnotationError(AnnotationTypeMismatchError(annTpt, annType, annType))
} else if (argss.length > 1) {
reportAnnotationError(MultipleArgumentListForAnnotationError(ann))
} else {
@@ -3534,7 +3531,7 @@ trait Typers extends Adaptations with Tags {
val typedAnn = if (selfsym == NoSymbol) {
// local dummy fixes SI-5544
val localTyper = newTyper(context.make(ann, context.owner.newLocalDummy(ann.pos)))
- localTyper.typed(ann, mode, annClass.tpe)
+ localTyper.typed(ann, mode, annType)
}
else {
// Since a selfsym is supplied, the annotation should have an extra
@@ -3548,7 +3545,7 @@ trait Typers extends Adaptations with Tags {
// sometimes does. The problem is that "self" ident's within
// annot.constr will retain the old symbol from the previous typing.
val func = Function(funcparm :: Nil, ann.duplicate)
- val funcType = appliedType(FunctionClass(1), selfsym.info, annClass.tpe_*)
+ val funcType = appliedType(FunctionClass(1), selfsym.info, annType)
val Function(arg :: Nil, rhs) = typed(func, mode, funcType)
rhs.substituteSymbols(arg.symbol :: Nil, selfsym :: Nil)
@@ -24,5 +24,5 @@ package scala
* @since 2.1
*/
class throws[T <: Throwable](cause: String = "") extends scala.annotation.StaticAnnotation {
- def this(clazz: Class[T]) = this()
+ def this(clazz: Class[T]) = this("")
}
@@ -12,7 +12,7 @@ import scala.collection.immutable.ListMap
/** AnnotationInfo and its helpers */
trait AnnotationInfos extends api.Annotations { self: SymbolTable =>
- import definitions.{ ThrowsClass, StaticAnnotationClass, isMetaAnnotation }
+ import definitions.{ ThrowsClass, ThrowableClass, StaticAnnotationClass, isMetaAnnotation }
// Common annotation code between Symbol and Type.
// For methods altering the annotation list, on Symbol it mutates
@@ -334,16 +334,19 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable =>
* as well as “new-stye” `@throws[Exception]("cause")` annotations.
*/
object ThrownException {
- def unapply(ann: AnnotationInfo): Option[Symbol] =
+ def unapply(ann: AnnotationInfo): Option[Symbol] = {
ann match {
case AnnotationInfo(tpe, _, _) if tpe.typeSymbol != ThrowsClass =>
None
// old-style: @throws(classOf[Exception]) (which is throws[T](classOf[Exception]))
case AnnotationInfo(_, List(Literal(Constant(tpe: Type))), _) =>
Some(tpe.typeSymbol)
// new-style: @throws[Exception], @throws[Exception]("cause")
- case AnnotationInfo(TypeRef(_, _, args), _, _) =>
- Some(args.head.typeSymbol)
+ case AnnotationInfo(TypeRef(_, _, arg :: _), _, _) =>
+ Some(arg.typeSymbol)
+ case AnnotationInfo(TypeRef(_, _, Nil), _, _) =>
+ Some(ThrowableClass)
}
+ }
}
}
@@ -605,6 +605,12 @@ abstract class TreeInfo {
}
loop(tree)
}
+
+ override def toString = {
+ val tstr = if (targs.isEmpty) "" else targs.mkString("[", ", ", "]")
+ val astr = argss map (args => args.mkString("(", ", ", ")")) mkString ""
+ s"$core$tstr$astr"
+ }
}
/** Returns a wrapper that knows how to destructure and analyze applications.
@@ -2944,12 +2944,19 @@ trait Types extends api.Types { self: SymbolTable =>
else existentialAbstraction(existentialsInType(tp), tp)
)
- def containsExistential(tpe: Type) =
- tpe exists typeIsExistentiallyBound
+ def containsDummyTypeArg(tp: Type) = tp exists isDummyTypeArg
+ def isDummyTypeArg(tp: Type) = tp.typeSymbol.isTypeParameter
+ def isDummyAppliedType(tp: Type) = tp match {
+ case TypeRef(_, _, args) if args.nonEmpty => args exists isDummyTypeArg
+ case _ => false
+ }
def existentialsInType(tpe: Type) =
tpe withFilter typeIsExistentiallyBound map (_.typeSymbol)
+ def containsExistential(tpe: Type) =
+ tpe exists typeIsExistentiallyBound
+
/** Precondition: params.nonEmpty. (args.nonEmpty enforced structurally.)
*/
class HKTypeVar(
@@ -0,0 +1,31 @@
+
+class B[T](x: (T, T)) {
+ def this(xx: (T, Any, Any)) = this((xx._1, xx._1))
+}
+class BAnn[T](x: (T, T)) extends scala.annotation.StaticAnnotation {
+ def this(xx: (T, Any, Any)) = this((xx._1, xx._1))
+}
+class CAnn[T](x: (T, T)) extends scala.annotation.StaticAnnotation {
+ def this(xx: Class[T]) = this((xx.newInstance(), xx.newInstance()))
+}
+
+class A1 {
+ val b1 = new B((1, 2, 3))
+ val b2 = new B((1, 2))
+ val b3 = new B[Int]((1, 2, 3))
+ val b4 = new B[Int]((1, 2))
+}
+
+class A2 {
+ @BAnn((1, 2, 3)) val b1 = null
+ @BAnn((1, 2)) val b2 = null
+ @BAnn[Int]((1, 2, 3)) val b3 = null
+ @BAnn[Int]((1, 2)) val b4 = null
+}
+
+class A3 {
+ @CAnn(classOf[Int]) val b1 = null
+ @CAnn((1, 2)) val b2 = null
+ @CAnn[Int](classOf[Int]) val b3 = null
+ @CAnn[Int]((1, 2)) val b4 = null
+}
@@ -0,0 +1 @@
+Nothing
View
@@ -0,0 +1,17 @@
+case class annot[T]() extends scala.annotation.StaticAnnotation
+
+// type inference should infer @annot[Nothing] instead of @annot[T]
+// note the T is not in scope here!
+class Foo[@annot U]
+
+object Test {
+ import scala.reflect.runtime.universe._
+ val x = new Foo
+
+ def main(args: Array[String]): Unit = {
+ val targ = typeOf[x.type].widen match {
+ case TypeRef(_, _, arg :: _) => arg
+ }
+ println(targ)
+ }
+}
@@ -0,0 +1,4 @@
+Bippy[String]
+Bippy[String]
+throws[Nothing]
+throws[RuntimeException]
View
@@ -0,0 +1,20 @@
+class Bippy[T](val value: T) extends annotation.StaticAnnotation
+
+class A {
+ @Bippy("hi") def f1: Int = 1
+ @Bippy[String]("hi") def f2: Int = 2
+
+ @throws("what do I throw?") def f3 = throw new RuntimeException
+ @throws[RuntimeException]("that's good to know!") def f4 = throw new RuntimeException
+}
+
+object Test {
+ import scala.reflect.runtime.universe._
+
+ def main(args: Array[String]): Unit = {
+ val members = typeOf[A].declarations.toList
+ val tpes = members flatMap (_.annotations) map (_.tpe)
+
+ tpes.map(_.toString).sorted foreach println
+ }
+}

0 comments on commit 832fc9a

Please sign in to comment.