Kapt3: Parse generic signatures of classes.

Refactoring (move TreeMaker helper functions to KaptTreeMaker, mapValues() and others to utils.kt).
This commit is contained in:
Yan Zhulanow
2016-10-28 03:33:41 +03:00
committed by Yan Zhulanow
parent e4158a5571
commit efd25de13f
8 changed files with 448 additions and 96 deletions
@@ -22,7 +22,6 @@ import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.tree.JCTree.*
import com.sun.tools.javac.tree.TreeMaker
import com.sun.tools.javac.util.Context
import com.sun.tools.javac.util.Names
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -67,8 +66,8 @@ class JCTreeConverter(
}
private val fileManager = context.get(JavaFileManager::class.java) as JavacFileManager
private val treeMaker = TreeMaker.instance(context)
private val nameTable = Names.instance(context).table
private val treeMaker = TreeMaker.instance(context) as KaptTreeMaker
private val signatureParser = SignatureParser(treeMaker)
private var done = false
@@ -90,7 +89,7 @@ class JCTreeConverter(
val packageAnnotations = JavacList.nil<JCAnnotation>()
val packageName = ktFile.packageFqName.asString()
val packageClause = if (packageName.isEmpty()) null else convertFqName(packageName)
val packageClause = if (packageName.isEmpty()) null else treeMaker.convertFqName(packageName)
val imports = JavacList.nil<JCTree>()
val classes = JavacList.of<JCTree>(classDeclaration)
@@ -102,7 +101,7 @@ class JCTreeConverter(
}
/**
* Returns false for the inner classe or if the origin for the class was not found.
* Returns false for the inner classes or if the origin for the class was not found.
*/
private fun convertClass(clazz: ClassNode, isTopLevel: Boolean): JCClassDecl? {
if (isSynthetic(clazz.access)) return null
@@ -124,16 +123,18 @@ class JCTreeConverter(
return null
}
val simpleName = name(if (isDefaultImpls) "DefaultImpls" else descriptor.name.asString())
val simpleName = treeMaker.name(if (isDefaultImpls) "DefaultImpls" else descriptor.name.asString())
val typeParams = JavacList.nil<JCTypeParameter>()
val extending = if (clazz.superName == "java/lang/Object" || isEnum) null else convertFqName(clazz.superName)
val implementing = mapValues(clazz.interfaces) {
val interfaces = mapValues(clazz.interfaces) {
if (isAnnotation && it == "java/lang/annotation/Annotation") return@mapValues null
convertFqName(it)
treeMaker.convertFqName(it)
}
val superClass = treeMaker.convertFqName(clazz.superName)
val hasSuperClass = clazz.superName != "java/lang/Object" && !isEnum
val genericType = signatureParser.parseClassSignature(clazz.signature, superClass, interfaces)
val fields = mapValues<FieldNode, JCTree>(clazz.fields) { convertField(it) }
val methods = mapValues<MethodNode, JCTree>(clazz.methods) {
if (isEnum) {
@@ -149,21 +150,27 @@ class JCTreeConverter(
convertClass(innerClassNode, false)
}
return treeMaker.ClassDef(modifiers, simpleName, typeParams, extending, implementing, fields + methods + nestedClasses)
return treeMaker.ClassDef(
modifiers,
simpleName,
genericType.typeParameters,
if (hasSuperClass) genericType.superClass else null,
genericType.interfaces,
fields + methods + nestedClasses)
}
private fun convertField(field: FieldNode): JCVariableDecl? {
if (isSynthetic(field.access)) return null
val modifiers = convertModifiers(field.access, ElementKind.FIELD, field.visibleAnnotations, field.invisibleAnnotations)
val name = name(field.name)
val name = treeMaker.name(field.name)
val type = Type.getType(field.desc)
// Enum type must be an identifier (Javac requirement)
val typeExpression = if (isEnum(field.access)) {
convertSimpleName(type.className.substringAfterLast('.'))
treeMaker.convertSimpleName(type.className.substringAfterLast('.'))
} else {
convertType(type)
treeMaker.convertType(type)
}
val value = field.value
@@ -193,23 +200,23 @@ class JCTreeConverter(
ElementKind.METHOD, visibleAnnotations, method.invisibleAnnotations)
val isConstructor = method.name == "<init>"
val name = name(method.name)
val name = treeMaker.name(method.name)
val typeParameters = JavacList.nil<JCTypeParameter>()
val receiverParameter = null
val returnType = Type.getReturnType(method.desc)
val returnTypeExpr = if (isConstructor) null else convertType(returnType)
val returnTypeExpr = if (isConstructor) null else treeMaker.convertType(returnType)
val parametersInfo = method.getParametersInfo(containingClass)
@Suppress("NAME_SHADOWING")
val parameters = mapValues(parametersInfo) { info ->
val modifiers = convertModifiers(info.access, ElementKind.PARAMETER, info.visibleAnnotations, info.invisibleAnnotations)
val name = name(info.name)
val type = convertType(info.type)
val name = treeMaker.name(info.name)
val type = treeMaker.convertType(info.type)
treeMaker.VarDef(modifiers, name, type, null)
}
val thrown = mapValues(method.exceptions) { convertFqName(it) }
val thrown = mapValues(method.exceptions) { treeMaker.convertFqName(it) }
val defaultValue = method.annotationDefault?.let { convertLiteralExpression(it) }
@@ -229,7 +236,7 @@ class JCTreeConverter(
val args = mapValues(superClassConstructor.valueParameters) { param ->
convertLiteralExpression(getDefaultValue(typeMapper.mapType(param.type)))
}
val call = treeMaker.Apply(JavacList.nil(), convertSimpleName("super"), args)
val call = treeMaker.Apply(JavacList.nil(), treeMaker.convertSimpleName("super"), args)
JavacList.of<JCStatement>(treeMaker.Exec(call))
} else {
JavacList.nil<JCStatement>()
@@ -275,9 +282,9 @@ class JCTreeConverter(
val fqName = annotationType.className
if (BLACKLISTED_ANNOTATATIONS.any { fqName.startsWith(it) }) return null
val name = convertType(annotationType)
val name = treeMaker.convertType(annotationType)
val values = mapPairedValues<JCExpression>(annotation.values) { key, value ->
treeMaker.Assign(convertSimpleName(key), convertLiteralExpression(value))
treeMaker.Assign(treeMaker.convertSimpleName(key), convertLiteralExpression(value))
}
return treeMaker.Annotation(name, values)
}
@@ -300,11 +307,11 @@ class JCTreeConverter(
assert(value.size == 2)
val enumType = Type.getType(value[0] as String)
val valueName = value[1] as String
treeMaker.Select(convertType(enumType), name(valueName))
treeMaker.Select(treeMaker.convertType(enumType), treeMaker.name(valueName))
}
is List<*> -> treeMaker.NewArray(null, JavacList.nil(), mapValues(value) { convertLiteralExpression(it) })
is Type -> treeMaker.Select(convertType(value), name("class"))
is Type -> treeMaker.Select(treeMaker.convertType(value), treeMaker.name("class"))
is AnnotationNode -> convertAnnotation(value) ?: error("Annotation is filtered out")
else -> throw IllegalArgumentException("Illegal literal expression value: $value (${value.javaClass.canonicalName})")
}
@@ -321,46 +328,6 @@ class JCTreeConverter(
Type.DOUBLE_TYPE -> 0.0
else -> null
}
private fun convertType(type: Type): JCExpression {
convertBuiltinType(type)?.let { return it }
if (type.sort == Type.ARRAY) {
return treeMaker.TypeArray(convertType(type.elementType))
}
return convertFqName(type.className)
}
private fun convertFqName(internalOrFqName: String): JCExpression {
val path = internalOrFqName.replace('/', '.').split('.')
assert(path.isNotEmpty())
if (path.size == 1) return convertSimpleName(path.single())
var expr = treeMaker.Select(convertSimpleName(path[0]), name(path[1]))
for (index in 2..path.lastIndex) {
expr = treeMaker.Select(expr, name(path[index]))
}
return expr
}
private fun convertBuiltinType(type: Type): JCExpression? {
val typeTag = when (type) {
Type.BYTE_TYPE -> TypeTag.BYTE
Type.BOOLEAN_TYPE -> TypeTag.BOOLEAN
Type.CHAR_TYPE -> TypeTag.CHAR
Type.SHORT_TYPE -> TypeTag.SHORT
Type.INT_TYPE -> TypeTag.INT
Type.LONG_TYPE -> TypeTag.LONG
Type.FLOAT_TYPE -> TypeTag.FLOAT
Type.DOUBLE_TYPE -> TypeTag.DOUBLE
Type.VOID_TYPE -> TypeTag.VOID
else -> null
} ?: return null
return treeMaker.TypeIdent(typeTag)
}
private fun convertSimpleName(name: String): JCExpression = treeMaker.Ident(name(name))
private fun name(name: String) = nameTable.fromString(name)
}
private class ParameterInfo(
@@ -400,36 +367,6 @@ private fun MethodNode.getParametersInfo(containingClass: ClassNode): List<Param
return parameterInfos
}
private inline fun <T, R> mapValues(values: Iterable<T>?, f: (T) -> R?): JavacList<R> {
if (values == null) return JavacList.nil()
var result = JavacList.nil<R>()
for (item in values) {
f(item)?.let { result = result.prepend(it) }
}
return result.reverse()
}
private inline fun <T> mapPairedValues(valuePairs: List<Any>?, f: (String, Any) -> T?): JavacList<T> {
if (valuePairs == null || valuePairs.isEmpty()) return JavacList.nil()
val size = valuePairs.size
var result = JavacList.nil<T>()
assert(size % 2 == 0)
var index = 0
while (index < size) {
val key = valuePairs[index] as String
val value = valuePairs[index + 1]
f(key, value)?.let { result = result.prepend(it) }
index += 2
}
return result.reverse()
}
private operator fun <T : Any> JavacList<T>.plus(other: JavacList<T>): JavacList<T> {
return this.appendList(other)
}
private val ClassDescriptor.isNested: Boolean
get() = containingDeclaration is ClassDescriptor
@@ -442,4 +379,4 @@ private fun isAbstract(access: Int) = (access and Opcodes.ACC_ABSTRACT) > 0
private fun ClassNode.isEnum() = (access and Opcodes.ACC_ENUM) > 0
private fun ClassNode.isAnnotation() = (access and Opcodes.ACC_ANNOTATION) > 0
private fun <T> List<T>.isNullOrEmpty() = this == null || this.isEmpty()
private fun <T> List<T>?.isNullOrEmpty() = this == null || this.isEmpty()
@@ -55,6 +55,7 @@ class KaptRunner {
init {
JavacFileManager.preRegister(context)
KaptTreeMaker.preRegister(context)
KaptJavaCompiler.preRegister(context)
compiler = JavaCompiler.instance(context) as KaptJavaCompiler
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.kapt3
import com.sun.tools.javac.code.TypeTag
import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.tree.TreeMaker
import com.sun.tools.javac.util.Context
import com.sun.tools.javac.util.Names
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.Type.*
class KaptTreeMaker(context: Context?) : TreeMaker(context) {
private val nameTable = Names.instance(context).table
fun convertType(type: Type): JCTree.JCExpression {
convertBuiltinType(type)?.let { return it }
if (type.sort == ARRAY) {
return TypeArray(convertType(type.elementType))
}
return convertFqName(type.className)
}
fun convertFqName(internalOrFqName: String): JCTree.JCExpression {
val path = internalOrFqName.replace('/', '.').split('.')
assert(path.isNotEmpty())
if (path.size == 1) return convertSimpleName(path.single())
var expr = Select(convertSimpleName(path[0]), name(path[1]))
for (index in 2..path.lastIndex) {
expr = Select(expr, name(path[index]))
}
return expr
}
fun convertBuiltinType(type: Type): JCTree.JCExpression? {
val typeTag = when (type) {
BYTE_TYPE -> TypeTag.BYTE
BOOLEAN_TYPE -> TypeTag.BOOLEAN
CHAR_TYPE -> TypeTag.CHAR
SHORT_TYPE -> TypeTag.SHORT
INT_TYPE -> TypeTag.INT
LONG_TYPE -> TypeTag.LONG
FLOAT_TYPE -> TypeTag.FLOAT
DOUBLE_TYPE -> TypeTag.DOUBLE
VOID_TYPE -> TypeTag.VOID
else -> null
} ?: return null
return TypeIdent(typeTag)
}
fun convertSimpleName(name: String): JCTree.JCExpression = Ident(name(name))
fun name(name: String) = nameTable.fromString(name)
companion object {
fun preRegister(context: Context) {
context.put(treeMakerKey, Context.Factory<TreeMaker>(::KaptTreeMaker))
}
}
}
@@ -0,0 +1,243 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.kapt3
import com.sun.tools.javac.code.BoundKind
import com.sun.tools.javac.tree.JCTree.*
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor
import java.util.*
import org.jetbrains.kotlin.kapt3.ElementKind.*
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.org.objectweb.asm.signature.SignatureReader
import com.sun.tools.javac.util.List as JavacList
/*
TopLevel
* FormalTypeParameter
+ SuperClass
* Interface
FormalTypeParameter < TopLevel
+ ClassBound
* InterfaceBound
ClassBound < FormalTypeParameter
+ ClassType
InterfaceBound < FormalTypeParameter
? ClassType
? TypeVariable
TypeVariable < InterfaceBound
SuperClass < TopLevel
+ ClassType
Interface < TopLevel
+ ClassType
ClassType < *
* TypeArgument
TypeArgument < ClassType
+ ClassType
*/
enum class ElementKind {
Root, TypeParameter, ClassBound, InterfaceBound, SuperClass, Interface, ClassType, TypeArgument, TypeVariable
}
class SignatureNode(val kind: ElementKind, val name: String? = null) {
val children: MutableList<SignatureNode> = SmartList<SignatureNode>()
}
class SignatureParser(val treeMaker: KaptTreeMaker) {
class ClassGenericSignature(
val typeParameters: JavacList<JCTypeParameter>,
val superClass: JCExpression,
val interfaces: JavacList<JCExpression>)
fun parseClassSignature(
signature: String?,
rawSuperClass: JCExpression,
rawInterfaces: JavacList<JCExpression>
): ClassGenericSignature {
if (signature == null) {
return ClassGenericSignature(JavacList.nil(), rawSuperClass, rawInterfaces)
}
val root = parse(signature)
val typeParameters = smartList()
val superClasses = smartList()
val interfaces = smartList()
root.split(typeParameters, TypeParameter, superClasses, SuperClass, interfaces, Interface)
val jcTypeParameters = mapValues(typeParameters) { parseTypeParameter(it) }
val superClassType = parseType(superClasses.single().children.single())
val interfaceTypes = mapValues(interfaces) { parseType(it.children.single()) }
return ClassGenericSignature(jcTypeParameters, superClassType, interfaceTypes)
}
private fun parseTypeParameter(node: SignatureNode): JCTypeParameter {
assert(node.kind == TypeParameter)
val classBounds = smartList()
val interfaceBounds = smartList()
node.split(classBounds, ClassBound, interfaceBounds, InterfaceBound)
assert(classBounds.size <= 1)
val jcClassBound = classBounds.firstOrNull()?.let { parseBound(it) }
val jcInterfaceBounds = mapValues(interfaceBounds) { parseBound(it) }
val allBounds = if (jcClassBound != null) jcInterfaceBounds.prepend(jcClassBound) else jcInterfaceBounds
return treeMaker.TypeParameter(treeMaker.name(node.name!!), allBounds)
}
private fun parseBound(node: SignatureNode): JCExpression {
assert(node.kind == ClassBound || node.kind == InterfaceBound)
return parseType(node.children.single())
}
private fun parseType(node: SignatureNode): JCExpression {
val kind = node.kind
return when (kind) {
ClassType -> {
val classFqName = node.name!!.replace('/', '.')
val args = node.children
val fqNameExpression = treeMaker.convertFqName(classFqName)
if (args.isEmpty()) return fqNameExpression
treeMaker.TypeApply(fqNameExpression, mapValues(args) { arg ->
assert(arg.kind == TypeArgument) { "Unexpected kind ${arg.kind}, $TypeArgument expected" }
val variance = arg.name ?: return@mapValues treeMaker.Wildcard(treeMaker.TypeBoundKind(BoundKind.UNBOUND), null)
val argType = parseType(arg.children.single())
when (variance.single()) {
'=' -> argType
'+' -> treeMaker.Wildcard(treeMaker.TypeBoundKind(BoundKind.EXTENDS), argType)
'-' -> treeMaker.Wildcard(treeMaker.TypeBoundKind(BoundKind.SUPER), argType)
else -> error("Unknown variance, '=', '+' or '-' expected")
}
})
}
TypeVariable -> treeMaker.convertSimpleName(node.name!!)
else -> error("Unsupported type: $node")
}
}
private fun parse(signature: String): SignatureNode {
val parser = SignatureParserVisitor()
SignatureReader(signature).accept(parser)
return parser.root
}
}
private fun smartList() = SmartList<SignatureNode>()
private fun SignatureNode.split(l1: MutableList<SignatureNode>, e1: ElementKind, l2: MutableList<SignatureNode>, e2: ElementKind) {
for (child in children) {
val kind = child.kind
when (kind) {
e1 -> l1 += child
e2 -> l2 += child
else -> error("Unknown kind: $kind")
}
}
}
private fun SignatureNode.split(
l1: MutableList<SignatureNode>,
e1: ElementKind,
l2: MutableList<SignatureNode>,
e2: ElementKind,
l3: MutableList<SignatureNode>,
e3: ElementKind) {
for (child in children) {
val kind = child.kind
when (kind) {
e1 -> l1 += child
e2 -> l2 += child
e3 -> l3 += child
else -> error("Unknown kind: $kind")
}
}
}
private class SignatureParserVisitor : SignatureVisitor(Opcodes.ASM5) {
val root = SignatureNode(Root)
private val stack = ArrayDeque<SignatureNode>(5).apply { add(root) }
private fun push(kind: ElementKind, parent: ElementKind? = null, name: String? = null) {
if (parent != null) {
while (stack.peek().kind != parent) {
stack.pop()
}
}
val newNode = SignatureNode(kind, name)
stack.peek().children += newNode
stack.push(newNode)
}
override fun visitSuperclass(): SignatureVisitor {
push(SuperClass, parent = Root)
return super.visitSuperclass()
}
override fun visitInterface(): SignatureVisitor {
push(Interface, parent = Root)
return super.visitInterface()
}
override fun visitFormalTypeParameter(name: String) {
push(TypeParameter, parent = Root, name = name)
}
override fun visitClassBound(): SignatureVisitor {
push(ClassBound, parent = TypeParameter)
return super.visitClassBound()
}
override fun visitInterfaceBound(): SignatureVisitor {
push(InterfaceBound, parent = TypeParameter)
return super.visitInterfaceBound()
}
override fun visitTypeArgument() {
push(TypeArgument, parent = ClassType)
}
override fun visitTypeArgument(wildcard: Char): SignatureVisitor {
push(TypeArgument, parent = ClassType, name = wildcard.toString())
return super.visitTypeArgument(wildcard)
}
override fun visitClassType(name: String) {
push(ClassType, name = name)
}
override fun visitTypeVariable(name: String) {
push(TypeVariable, parent = InterfaceBound, name = name)
}
override fun visitEnd() {
while (stack.peek().kind != ClassType) {
stack.pop()
}
stack.pop()
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.kapt3
import com.sun.tools.javac.util.List as JavacList
internal inline fun <T, R> mapValues(values: Iterable<T>?, f: (T) -> R?): JavacList<R> {
if (values == null) return JavacList.nil()
var result = JavacList.nil<R>()
for (item in values) {
f(item)?.let { result = result.prepend(it) }
}
return result.reverse()
}
internal inline fun <T> mapPairedValues(valuePairs: List<Any>?, f: (String, Any) -> T?): JavacList<T> {
if (valuePairs == null || valuePairs.isEmpty()) return JavacList.nil()
val size = valuePairs.size
var result = JavacList.nil<T>()
assert(size % 2 == 0)
var index = 0
while (index < size) {
val key = valuePairs[index] as String
val value = valuePairs[index + 1]
f(key, value)?.let { result = result.prepend(it) }
index += 2
}
return result.reverse()
}
internal operator fun <T : Any> JavacList<T>.plus(other: JavacList<T>): JavacList<T> {
return this.appendList(other)
}
@@ -65,6 +65,12 @@ public class JCTreeConverterTestGenerated extends AbstractJCTreeConverterTest {
doTest(fileName);
}
@TestMetadata("genericSimple.kt")
public void testGenericSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/genericSimple.kt");
doTest(fileName);
}
@TestMetadata("inheritanceSimple.kt")
public void testInheritanceSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/inheritanceSimple.kt");
+8
View File
@@ -0,0 +1,8 @@
import java.io.Serializable
import java.util.*
interface Intf<I1, I2 : Serializable>
interface Intf2<out T : List<String>, M : T>
interface OtherIntf<O : CharSequence>
open class BaseClass<B : Any>
class MyClass<M1, M2> : Intf<Any, java.util.Date>, OtherIntf<String>, BaseClass<RuntimeException>()
+34
View File
@@ -0,0 +1,34 @@
public abstract interface Intf<I1 extends java.lang.Object, I2 extends java.io.Serializable> {
}
////////////////////
public abstract interface Intf2<T extends java.util.List<? extends java.lang.String>, M extends T> {
}
////////////////////
public abstract interface OtherIntf<O extends java.lang.CharSequence> {
}
////////////////////
public class BaseClass<B extends java.lang.Object> {
public BaseClass() {
super();
}
}
////////////////////
public final class MyClass<M1 extends java.lang.Object, M2 extends java.lang.Object> extends BaseClass<java.lang.RuntimeException> implements Intf<java.lang.Object, java.util.Date>, OtherIntf<java.lang.String> {
public MyClass() {
super();
}
}