scala / scala-dev

Scala 2 team issues. Not for user-facing bugs or directly actionable user-facing improvements. For build/test/infra and for longer-term planning and idea tracking. Our bug tracker is at https://github.com/scala/bug/issues
Apache License 2.0
130 stars 14 forks source link

Fix AnnotationInfo when using defaults #884

Open lrytz opened 4 days ago

lrytz commented 4 days ago
scala> class ann(x: Int = 1, y: Int = 2) extends annotation.StaticAnnotation
class ann

scala> @ann(y = Nil.size) class K
        ^
       warning: Usage of named or default arguments transformed this annotation
       constructor call into a block. The corresponding AnnotationInfo
       will contain references to local values and default getters instead
       of the actual argument trees
class K

scala> typeOf[K].typeSymbol.annotations.head
val res0: $r.intp.global.AnnotationInfo = ann(x$2, x$1)

scala> typeOf[K].typeSymbol.annotations.head.original
val res1: $r.intp.global.Tree = <empty>

Before typing, the annotation is represented as new ann(y = Nil.size). To construct the AnnotationInfo, that expression is type checked like ordinary code, resulting in

{
  val x$1 = Nil.size
  val x$2 = ann.<init>$default$1
  new ann(x$2, x$1)
}

The AnnotationInfo only has ann(x$2, x$1), the block is thrown away it seems, there's now way get to the actual arguments.

Ideas

For the last one, the question is how to get to the default value AST. One option is to make the compiler attach it to to a symbol, so the class ann above would become

class ann(@defaultArg(1) x: Int, @defaultArg(2) y: Int) extends StaticAnnotation
object ann {
  <synthetic> def <init>$default$1 : Int = 1
  <synthetic> def <init>$default$2 : Int = 2
}

When constructing the AnnotationInfo from new ann(<init>$default$1, Nil.size) we can get the AST for the default from the annotation parameter symbol.

Earlier tickets: https://github.com/scala/bug/issues/7656, https://github.com/scala/bug/issues/9612

I did some prototyping when discussing @apiStatus (https://github.com/scala/scala/pull/8820 / https://github.com/scala/scala/compare/2.13.x...lrytz:constAnnDefaults). The goal here was to allow subclasses of annotations to define certain defaults, which would be great for @unused: https://github.com/scala/bug/issues/12367.

Fixing that at the same time would be good.

som-snytt commented 4 days ago

There was a comment on dotty that they might re-engineer defaults to be inline. Your bullet 3 is very appealing, or to coin a phrase, "it would be great if".

lrytz commented 5 hours ago

A workaround, at least for the case when the annotation parameters have distinct types, is defining constructor overloads:

class ann(x: Int, y: String) extends annotation.StaticAnnotation {
  def this(x: Int) = this(x, null)
  def this(s: String) = this(null, s)
}
scala> @ann(s = "ka") class K

scala> typeOf[K].typeSymbol.annotations.head
val res0: $r.intp.global.AnnotationInfo = ann("ka")