Skip to content

Commit 2c65b9e

Browse files
authored
Merge pull request scalacenter#389 from sjrd/java-annotations
Read Java annotations.
2 parents 976a049 + d7aede0 commit 2c65b9e

20 files changed

+618
-105
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ lazy val testSources = crossProject(JSPlatform, JVMPlatform)
6868
.settings(
6969
publish / skip := true,
7070
scalacOptions += "-Xfatal-warnings",
71+
javacOptions += "-parameters",
7172
)
7273

7374
lazy val tastyQuery =

tasty-query/shared/src/main/scala/tastyquery/Annotations.scala

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ object Annotations:
2727

2828
/** The symbol of the constructor used in the annotation.
2929
*
30-
* This operation is not supported for annotations read from Scala 2.
30+
* This operation is not supported for annotations read from Java or Scala 2.
3131
* It will throw an `UnsupportedOperationException`.
3232
*/
3333
def annotConstructor(using Context): TermSymbol =
@@ -111,6 +111,24 @@ object Annotations:
111111

112112
new Annotation(tree)
113113
end apply
114+
115+
private[tastyquery] def fromAnnotTypeAndArgs(annotationType: Type, args: List[TermTree]): Annotation =
116+
val pos = SourcePosition.NoPosition
117+
118+
/* Create a TermTree for the annotation that is "good enough" for the main
119+
* methods of `Annotation` to work, notably `symbol` and `arguments`.
120+
* We have to cheat for the constructor, as we do not have its Signature.
121+
* Instead we use an unsigned `nme.Constructor`. This is invalid and will
122+
* cause `Annotation.annotConstructor` to fail, but we do not really have
123+
* a choice.
124+
*/
125+
val annotationTree: TermTree =
126+
val newNode = New(TypeWrapper(annotationType)(pos))(pos)
127+
val selectCtorNode = Select(newNode, nme.Constructor)(None)(pos) // cheating here
128+
Apply(selectCtorNode, args)(pos)
129+
130+
Annotation(annotationTree)
131+
end fromAnnotTypeAndArgs
114132
end Annotation
115133

116134
private def computeAnnotSymbol(tree: TermTree)(using Context): ClassSymbol =
@@ -140,13 +158,15 @@ object Annotations:
140158
def invalid(): Nothing =
141159
throw InvalidProgramStructureException(s"Cannot find annotation constructor in $tree")
142160

143-
def unsupportedScala2(): Nothing =
144-
throw UnsupportedOperationException(s"Cannot compute the annotation constructor of a Scala 2 annotation: $tree")
161+
def unsupported(): Nothing =
162+
throw UnsupportedOperationException(
163+
s"Cannot compute the annotation constructor of a Java or Scala 2 annotation: $tree"
164+
)
145165

146166
@tailrec
147167
def loop(tree: TermTree): TermSymbol = tree match
148168
case Apply(fun, _) => loop(fun)
149-
case tree @ Select(New(tpt), name) => if name == nme.Constructor then unsupportedScala2() else tree.symbol.asTerm
169+
case tree @ Select(New(tpt), name) => if name == nme.Constructor then unsupported() else tree.symbol.asTerm
150170
case TypeApply(fun, _) => loop(fun)
151171
case Block(_, expr) => loop(expr)
152172
case _ => invalid()

tasty-query/shared/src/main/scala/tastyquery/Constants.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ object Constants {
166166

167167
def stringValue: String = if tag == NullTag then "null" else value.toString
168168

169+
/** The class type value of a `classOf` constant.
170+
*
171+
* This must be a "possibly parametrized class type" according to the
172+
* specification of the language. If the class is polymorphic, it may be
173+
* applied (making it a proper type) or not (in which case it is not a
174+
* proper type).
175+
*/
169176
def typeValue: Type = value.asInstanceOf[Type]
170177

171178
override def hashCode: Int = {

tasty-query/shared/src/main/scala/tastyquery/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ final class Definitions private[tastyquery] (ctx: Context, rootPackage: PackageS
118118
.withDeclaredType(tpe)
119119
.setAnnotations(Nil)
120120
.autoFillParamSymss()
121+
sym.paramSymss.foreach(_.merge.foreach(_.setAnnotations(Nil)))
121122
sym.checkCompleted()
122123
sym
123124
end createSpecialMethod
@@ -347,6 +348,7 @@ final class Definitions private[tastyquery] (ctx: Context, rootPackage: PackageS
347348
)
348349
applyMethod.autoFillParamSymss()
349350
applyMethod.setAnnotations(Nil)
351+
applyMethod.paramSymss.foreach(_.merge.foreach(_.setAnnotations(Nil)))
350352
applyMethod.checkCompleted()
351353

352354
cls.checkCompleted()

tasty-query/shared/src/main/scala/tastyquery/Symbols.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,6 @@ object Symbols {
489489
.createNotDeclaration(name, this)
490490
.withFlags(EmptyFlagSet, privateWithin = None)
491491
.withDeclaredType(paramType)
492-
.setAnnotations(Nil)
493492
}
494493
Left(paramSyms) :: autoComputeParamSymss(tpe.resultType)
495494

@@ -498,7 +497,6 @@ object Symbols {
498497
LocalTypeParamSymbol
499498
.create(name, this)
500499
.withFlags(EmptyFlagSet, privateWithin = None)
501-
.setAnnotations(Nil)
502500
}
503501
val paramSymRefs = paramSyms.map(_.localRef)
504502
def subst(t: TypeOrMethodic): t.ThisTypeMappableType =

tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/ClassfileParser.scala

Lines changed: 175 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
package tastyquery.reader.classfiles
22

3+
import scala.annotation.switch
4+
35
import scala.collection.mutable
46

5-
import tastyquery.Annotations.Annotation as TQAnnotation
7+
import tastyquery.Annotations.*
68
import tastyquery.Classpaths.*
79
import tastyquery.Contexts.*
10+
import tastyquery.Constants.*
811
import tastyquery.Exceptions.*
912
import tastyquery.Flags
1013
import tastyquery.Flags.*
1114
import tastyquery.Names.*
1215
import tastyquery.SourceLanguage
16+
import tastyquery.SourcePosition
1317
import tastyquery.Symbols.*
18+
import tastyquery.Trees.*
1419
import tastyquery.Types.*
1520

1621
import tastyquery.reader.ReaderContext
1722
import tastyquery.reader.ReaderContext.rctx
1823
import tastyquery.reader.pickles.{Unpickler, PickleReader}
1924

20-
import ClassfileReader.*
25+
import ClassfileReader.{Annotation as CFAnnotation, *}
2126
import ClassfileReader.Access.AccessFlags
2227
import Constants.*
2328

@@ -63,6 +68,8 @@ private[reader] object ClassfileParser {
6368
def resolve(binaryName: SimpleName)(using ReaderContext, InnerClasses): TypeRef =
6469
lookup(binaryName, isStatic = false).asTypeRef
6570

71+
def resolveStatic(binaryName: SimpleName)(using ReaderContext, InnerClasses): TermRef =
72+
lookup(binaryName, isStatic = true).asTermRef
6673
end Resolver
6774

6875
/** The inner classes local to a class file */
@@ -127,10 +134,10 @@ private[reader] object ClassfileParser {
127134

128135
val sigBytes = scalaSigAnnotation.tpe match {
129136
case annot.ScalaSignature =>
130-
val bytesArg = scalaSigAnnotation.values.head.asInstanceOf[AnnotationValue.Const]
137+
val bytesArg = scalaSigAnnotation.values.head._2.asInstanceOf[AnnotationValue.Const]
131138
pool.sigbytes(bytesArg.valueIdx)
132139
case annot.ScalaLongSignature =>
133-
val bytesArrArg = scalaSigAnnotation.values.head.asInstanceOf[AnnotationValue.Arr]
140+
val bytesArrArg = scalaSigAnnotation.values.head._2.asInstanceOf[AnnotationValue.Arr]
134141
val idxs = bytesArrArg.values.map(_.asInstanceOf[AnnotationValue.Const].valueIdx)
135142
pool.sigbytes(idxs)
136143
}
@@ -200,15 +207,31 @@ private[reader] object ClassfileParser {
200207
else false
201208
end isSignaturePolymorphic
202209

203-
def createMember(name: SimpleName, isMethod: Boolean, javaFlags: AccessFlags, memberSig: MemberSig): TermSymbol =
210+
def createMember(
211+
name: SimpleName,
212+
isMethod: Boolean,
213+
javaFlags: AccessFlags,
214+
descriptor: String,
215+
attributes: Map[SimpleName, Forked[DataStream]]
216+
): TermSymbol =
204217
// Select the right owner and create the symbol
205218
val owner = if javaFlags.isStatic then moduleClass else cls
206219
val sym = TermSymbol.create(name, owner)
207220
allRegisteredSymbols += sym
208221

222+
// Read parameter names
223+
val methodParamNames =
224+
if isMethod then readMethodParameters(attributes).map(_._1)
225+
else Nil
226+
227+
// Find the signature, or fall back to the descriptor
228+
val memberSig = attributes.get(attr.Signature) match
229+
case Some(stream) => stream.use(ClassfileReader.readSignature)
230+
case None => descriptor
231+
209232
// Parse the signature into a declared type for the symbol
210233
val declaredType =
211-
val parsedType = JavaSignatures.parseSignature(sym, isMethod, memberSig, allRegisteredSymbols)
234+
val parsedType = JavaSignatures.parseSignature(sym, isMethod, methodParamNames, memberSig, allRegisteredSymbols)
212235
val adaptedType =
213236
if isMethod && sym.name == nme.Constructor then cls.makePolyConstructorType(parsedType)
214237
else if isMethod && javaFlags.isVarargsIfMethod then patchForVarargs(sym, parsedType)
@@ -226,23 +249,46 @@ private[reader] object ClassfileParser {
226249
end flags
227250
sym.withFlags(flags, privateWithin(javaFlags))
228251

229-
// Auto fill the param symbols from the declared type
230-
sym.autoFillParamSymss()
231-
232-
sym.setAnnotations(Nil) // TODO Read Java annotations on fields and methods
252+
// Read and fill annotations
253+
val annots = readAnnotations(sym, attributes)
254+
sym.setAnnotations(annots)
255+
256+
// Handle parameters
257+
if isMethod then
258+
// Auto fill the param symbols from the declared type
259+
sym.autoFillParamSymss()
260+
261+
val termParamAnnots = readTermParamAnnotations(attributes)
262+
if termParamAnnots.isEmpty then
263+
// fast path
264+
sym.paramSymss.foreach(_.merge.foreach(_.setAnnotations(Nil)))
265+
else
266+
val termParamAnnotsIter = termParamAnnots.iterator
267+
268+
for paramSyms <- sym.paramSymss do
269+
paramSyms match
270+
case Left(termParamSyms) =>
271+
for termParamSym <- termParamSyms do
272+
val annots = if termParamAnnotsIter.hasNext then termParamAnnotsIter.next() else Nil
273+
termParamSym.setAnnotations(annots)
274+
case Right(typeParamSyms) =>
275+
// TODO Maybe one day we also read type annotations
276+
typeParamSyms.foreach(_.setAnnotations(Nil))
277+
end if
278+
end if
233279

234280
sym
235281
end createMember
236282

237283
def loadMembers(): Unit =
238284
structure.fields.use {
239-
ClassfileReader.readFields { (name, sigOrDesc, javaFlags) =>
240-
createMember(name, isMethod = false, javaFlags, sigOrDesc)
285+
ClassfileReader.readMembers { (javaFlags, name, descriptor, attributes) =>
286+
createMember(name, isMethod = false, javaFlags, descriptor, attributes)
241287
}
242288
}
243289
structure.methods.use {
244-
ClassfileReader.readMethods { (name, sigOrDesc, javaFlags) =>
245-
createMember(name, isMethod = true, javaFlags, sigOrDesc)
290+
ClassfileReader.readMembers { (javaFlags, name, descriptor, attributes) =>
291+
createMember(name, isMethod = true, javaFlags, descriptor, attributes)
246292
}
247293
}
248294
end loadMembers
@@ -254,7 +300,9 @@ private[reader] object ClassfileParser {
254300
val parents = attributes.get(attr.Signature) match
255301
case Some(stream) =>
256302
val sig = stream.use(ClassfileReader.readSignature)
257-
JavaSignatures.parseSignature(cls, isMethod = false, sig, allRegisteredSymbols).requireType match
303+
val parsedSig =
304+
JavaSignatures.parseSignature(cls, isMethod = false, methodParameterNames = Nil, sig, allRegisteredSymbols)
305+
parsedSig.requireType match
258306
case mix: AndType => mix.parts
259307
case sup => sup :: Nil
260308
case None =>
@@ -273,7 +321,6 @@ private[reader] object ClassfileParser {
273321

274322
cls.withGivenSelfType(None)
275323
cls.withFlags(clsFlags, clsPrivateWithin)
276-
cls.setAnnotations(Nil) // TODO Read Java annotations on classes
277324
initParents()
278325

279326
// Intercept special classes to create their magic methods
@@ -284,6 +331,9 @@ private[reader] object ClassfileParser {
284331

285332
loadMembers()
286333

334+
val annotations = readAnnotations(cls, attributes)
335+
cls.setAnnotations(annotations)
336+
287337
for sym <- allRegisteredSymbols do
288338
sym.checkCompleted()
289339
assert(sym.sourceLanguage == SourceLanguage.Java, s"$sym of ${sym.getClass()}")
@@ -324,6 +374,115 @@ private[reader] object ClassfileParser {
324374
None
325375
end ArrayTypeExtractor
326376

377+
private def readMethodParameters(attributes: AttributeMap)(
378+
using ConstantPool
379+
): List[(UnsignedTermName, AccessFlags)] =
380+
attributes.get(attr.MethodParameters) match
381+
case Some(stream) => stream.use(ClassfileReader.readMethodParameters())
382+
case None => Nil
383+
end readMethodParameters
384+
385+
private def readAnnotations(
386+
sym: TermOrTypeSymbol,
387+
attributes: AttributeMap
388+
)(using ConstantPool, ReaderContext, InnerClasses, Resolver): List[Annotation] =
389+
readAnnotations(sym, attributes.get(attr.RuntimeVisibleAnnotations))
390+
::: readAnnotations(sym, attributes.get(attr.RuntimeInvisibleAnnotations))
391+
end readAnnotations
392+
393+
private def readAnnotations(
394+
sym: TermOrTypeSymbol,
395+
annotationsStream: Option[Forked[DataStream]]
396+
)(using ConstantPool, ReaderContext, InnerClasses, Resolver): List[Annotation] =
397+
annotationsStream.fold(Nil)(readAnnotations(sym, _))
398+
end readAnnotations
399+
400+
private def readAnnotations(
401+
sym: TermOrTypeSymbol,
402+
annotationsStream: Forked[DataStream]
403+
)(using ConstantPool, ReaderContext, InnerClasses, Resolver): List[Annotation] =
404+
val classfileAnnots = annotationsStream.use(ClassfileReader.readAllAnnotations())
405+
classfileAnnots.map(classfileAnnotToAnnot(_))
406+
407+
private def readTermParamAnnotations(
408+
attributes: AttributeMap
409+
)(using ConstantPool, ReaderContext, InnerClasses, Resolver): List[List[Annotation]] =
410+
val runtimeVisible = attributes.get(attr.RuntimeVisibleParameterAnnotations) match
411+
case None => Nil
412+
case Some(stream) => stream.use(ClassfileReader.readAllParameterAnnotations())
413+
414+
val runtimeInvisible = attributes.get(attr.RuntimeInvisibleParameterAnnotations) match
415+
case None => Nil
416+
case Some(stream) => stream.use(ClassfileReader.readAllParameterAnnotations())
417+
418+
if runtimeVisible.isEmpty && runtimeInvisible.isEmpty then
419+
// fast path
420+
Nil
421+
else
422+
for (rtVisible, rtInvisible) <- runtimeVisible.zipAll(runtimeInvisible, Nil, Nil)
423+
yield (rtVisible ::: rtInvisible).map(classfileAnnotToAnnot(_))
424+
end readTermParamAnnotations
425+
426+
private def classfileAnnotToAnnot(
427+
classfileAnnot: CFAnnotation
428+
)(using ConstantPool, ReaderContext, InnerClasses, Resolver): Annotation =
429+
val annotationType = JavaSignatures.parseFieldDescriptor(classfileAnnot.tpe.name)
430+
431+
val args: List[TermTree] =
432+
for (name, value) <- classfileAnnot.values.toList yield
433+
val valueTree = annotationValueToTree(value)
434+
NamedArg(name, valueTree)(SourcePosition.NoPosition)
435+
436+
Annotation.fromAnnotTypeAndArgs(annotationType, args)
437+
end classfileAnnotToAnnot
438+
439+
private def annotationValueToTree(
440+
value: AnnotationValue
441+
)(using ConstantPool, ReaderContext, InnerClasses, Resolver): TermTree =
442+
import AnnotationValue.Tags
443+
444+
val pool = summon[ConstantPool]
445+
val pos = SourcePosition.NoPosition
446+
447+
value match
448+
case AnnotationValue.Const(tag, valueIdx) =>
449+
val constant = (tag: @switch) match
450+
case Tags.Byte => Constant(pool.integer(valueIdx).toByte)
451+
case Tags.Char => Constant(pool.integer(valueIdx).toChar)
452+
case Tags.Double => Constant(pool.double(valueIdx))
453+
case Tags.Float => Constant(pool.float(valueIdx))
454+
case Tags.Int => Constant(pool.integer(valueIdx))
455+
case Tags.Long => Constant(pool.long(valueIdx))
456+
case Tags.Short => Constant(pool.integer(valueIdx).toShort)
457+
case Tags.Boolean => Constant(pool.integer(valueIdx) != 0)
458+
case Tags.String => Constant(pool.utf8(valueIdx).name)
459+
Literal(constant)(pos)
460+
461+
case AnnotationValue.EnumConst(descriptor, constName) =>
462+
/* JVMS says that it can be any field descriptor,
463+
* but I don't see what we would do with a base type or array type.
464+
*/
465+
val binaryName = descriptor.name match
466+
case s"L$binaryName;" => binaryName
467+
case other => throw ClassfileFormatException(s"unexpected non-class field descriptor: $other")
468+
val enumClassStaticRef = resolver.resolveStatic(termName(binaryName))
469+
val constRef = TermRef(enumClassStaticRef, constName)
470+
Ident(constName)(constRef)(pos)
471+
472+
case AnnotationValue.ClassConst(descriptor) =>
473+
val classType = JavaSignatures.parseReturnDescriptor(descriptor.name)
474+
Literal(Constant(classType))(pos)
475+
476+
case AnnotationValue.NestedAnnotation(annotation) =>
477+
val nestedAnnot = classfileAnnotToAnnot(annotation)
478+
nestedAnnot.tree
479+
480+
case AnnotationValue.Arr(values) =>
481+
val valueTrees = values.map(annotationValueToTree(_)).toList
482+
val elemType = rctx.AnyType // TODO This will not be type-correct
483+
SeqLiteral(valueTrees, TypeWrapper(elemType)(pos))(pos)
484+
end annotationValueToTree
485+
327486
def detectClassKind(structure: Structure): ClassKind =
328487
import structure.given
329488

0 commit comments

Comments
 (0)