From e4158a5571fc458815d938280f83868fe52cedf5 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Wed, 26 Oct 2016 22:07:31 +0300 Subject: [PATCH] Kapt3: Number of bugfixes in JCTreeConverter: Convert Java primitive and array types properly. Enums: ignore first two synthetic constructor parameters, do not generate super class constructor call, add ACC_ENUM flag to enum values, do not generate "values" and "valueOf" methods. Provide a JavaFileObject for JCCompilationUnit. Handle DefaultImpls, ignore empty DefaultImpls classes. Use a name table from Javac Names. --- .../jetbrains/kotlin/kapt3/JCTreeConverter.kt | 169 ++++++++++++++---- .../org/jetbrains/kotlin/kapt3/KaptRunner.kt | 8 +- .../kotlin/kapt3/SyntheticJavaFileObject.kt | 91 ++++++++++ .../kapt3/test/AbstractJCTreeConverterTest.kt | 22 ++- .../test/JCTreeConverterTestGenerated.java | 30 ++++ .../kapt3/testData/converter/annotations.kt | 31 ++++ .../kapt3/testData/converter/annotations.txt | 87 +++++++++ .../kapt3/testData/converter/dataClass.txt | 2 +- .../kapt3/testData/converter/defaultImpls.kt | 15 ++ .../kapt3/testData/converter/defaultImpls.txt | 54 ++++++ plugins/kapt3/testData/converter/enums.kt | 10 ++ plugins/kapt3/testData/converter/enums.txt | 42 +++++ plugins/kapt3/testData/converter/functions.kt | 6 + .../kapt3/testData/converter/functions.txt | 22 +++ .../testData/converter/inheritanceSimple.txt | 39 ++-- plugins/kapt3/testData/converter/jvmStatic.kt | 13 ++ .../kapt3/testData/converter/jvmStatic.txt | 40 +++++ .../testData/converter/nestedClasses.txt | 29 +-- 18 files changed, 618 insertions(+), 92 deletions(-) create mode 100644 plugins/kapt3/src/org/jetbrains/kotlin/kapt3/SyntheticJavaFileObject.kt create mode 100644 plugins/kapt3/testData/converter/annotations.kt create mode 100644 plugins/kapt3/testData/converter/annotations.txt create mode 100644 plugins/kapt3/testData/converter/defaultImpls.kt create mode 100644 plugins/kapt3/testData/converter/defaultImpls.txt create mode 100644 plugins/kapt3/testData/converter/enums.kt create mode 100644 plugins/kapt3/testData/converter/enums.txt create mode 100644 plugins/kapt3/testData/converter/functions.kt create mode 100644 plugins/kapt3/testData/converter/functions.txt create mode 100644 plugins/kapt3/testData/converter/jvmStatic.kt create mode 100644 plugins/kapt3/testData/converter/jvmStatic.txt diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/JCTreeConverter.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/JCTreeConverter.kt index 8a720a05221..333b757b80b 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/JCTreeConverter.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/JCTreeConverter.kt @@ -16,23 +16,26 @@ package org.jetbrains.kotlin.kapt3 +import com.sun.tools.javac.code.TypeTag +import com.sun.tools.javac.file.JavacFileManager 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.Name import com.sun.tools.javac.util.Names -import com.sun.tools.javac.util.SharedNameTable import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.tree.* +import java.util.* import javax.lang.model.element.ElementKind +import javax.tools.JavaFileManager import com.sun.tools.javac.util.List as JavacList class JCTreeConverter( @@ -52,7 +55,7 @@ class JCTreeConverter( Opcodes.ACC_SYNCHRONIZED or Opcodes.ACC_STATIC or Opcodes.ACC_NATIVE or Opcodes.ACC_DEPRECATED private val FIELD_MODIFIERS = VISIBILITY_MODIFIERS or MODALITY_MODIFIERS or - Opcodes.ACC_STATIC or Opcodes.ACC_VOLATILE or Opcodes.ACC_TRANSIENT + Opcodes.ACC_STATIC or Opcodes.ACC_VOLATILE or Opcodes.ACC_TRANSIENT or Opcodes.ACC_ENUM private val BLACKLISTED_ANNOTATATIONS = listOf( "java.lang.Deprecated", "kotlin.Deprecated", // Deprecated annotations @@ -63,8 +66,9 @@ class JCTreeConverter( ) } + private val fileManager = context.get(JavaFileManager::class.java) as JavacFileManager private val treeMaker = TreeMaker.instance(context) - private val nameTable = SharedNameTable(Names.instance(context)) + private val nameTable = Names.instance(context).table private var done = false @@ -82,7 +86,7 @@ class JCTreeConverter( // Nested classes will be processed during the outer classes conversion if (descriptor.isNested) return null - val classDeclaration = convertClass(clazz) + val classDeclaration = convertClass(clazz, true) ?: return null val packageAnnotations = JavacList.nil() val packageName = ktFile.packageFqName.asString() @@ -91,13 +95,16 @@ class JCTreeConverter( val imports = JavacList.nil() val classes = JavacList.of(classDeclaration) - return treeMaker.TopLevel(packageAnnotations, packageClause, imports + classes) + val topLevel = treeMaker.TopLevel(packageAnnotations, packageClause, imports + classes) + val javaFileObject = SyntheticJavaFileObject(topLevel, classDeclaration, System.currentTimeMillis(), fileManager) + topLevel.sourcefile = javaFileObject + return topLevel } /** * Returns false for the inner classe or if the origin for the class was not found. */ - private fun convertClass(clazz: ClassNode): JCClassDecl? { + private fun convertClass(clazz: ClassNode, isTopLevel: Boolean): JCClassDecl? { if (isSynthetic(clazz.access)) return null val descriptor = origins[clazz]?.descriptor as? ClassDescriptor ?: return null @@ -105,17 +112,41 @@ class JCTreeConverter( if (!descriptor.isInner && descriptor.isNested) (clazz.access or Opcodes.ACC_STATIC) else clazz.access, ElementKind.CLASS, clazz.visibleAnnotations, clazz.invisibleAnnotations) - val simpleName = name(descriptor.name.asString()) + val isEnum = clazz.isEnum() + val isAnnotation = clazz.isAnnotation() + + val isDefaultImpls = clazz.name.endsWith("${descriptor.name.asString()}/DefaultImpls") + && isPublic(clazz.access) && isFinal(clazz.access) + && descriptor.kind == ClassKind.INTERFACE + + // DefaultImpls without any contents don't have INNERCLASS'es inside it (and inside the parent interface) + if (isDefaultImpls && (isTopLevel || (clazz.fields.isNullOrEmpty() && clazz.methods.isNullOrEmpty()))) { + return null + } + + val simpleName = name(if (isDefaultImpls) "DefaultImpls" else descriptor.name.asString()) + val typeParams = JavacList.nil() - val extending = if (clazz.superName == "java/lang/Object" || clazz.isEnum()) null else convertFqName(clazz.superName) - val implementing = mapValues(clazz.interfaces) { convertFqName(it) } + val extending = if (clazz.superName == "java/lang/Object" || isEnum) null else convertFqName(clazz.superName) + + val implementing = mapValues(clazz.interfaces) { + if (isAnnotation && it == "java/lang/annotation/Annotation") return@mapValues null + convertFqName(it) + } val fields = mapValues(clazz.fields) { convertField(it) } - val methods = mapValues(clazz.methods) { convertMethod(it, clazz, simpleName) } + val methods = mapValues(clazz.methods) { + if (isEnum) { + if (it.name == "values" && it.desc == "()[L${clazz.name};") return@mapValues null + if (it.name == "valueOf" && it.desc == "(Ljava/lang/String;)L${clazz.name};") return@mapValues null + } + + convertMethod(it, clazz) + } val nestedClasses = mapValues(clazz.innerClasses) { innerClass -> if (innerClass.outerName != clazz.name) return@mapValues null val innerClassNode = classes.firstOrNull { it.name == innerClass.name } ?: return@mapValues null - convertClass(innerClassNode) + convertClass(innerClassNode, false) } return treeMaker.ClassDef(modifiers, simpleName, typeParams, extending, implementing, fields + methods + nestedClasses) @@ -127,16 +158,26 @@ class JCTreeConverter( val modifiers = convertModifiers(field.access, ElementKind.FIELD, field.visibleAnnotations, field.invisibleAnnotations) val name = name(field.name) val type = Type.getType(field.desc) - val typeExpression = convertFqName(type.className) + + // Enum type must be an identifier (Javac requirement) + val typeExpression = if (isEnum(field.access)) { + convertSimpleName(type.className.substringAfterLast('.')) + } else { + convertType(type) + } + val value = field.value + val initializer = when (value) { - is Byte, is Boolean, is Char, is Short, is Int, is Long, is Float, is Double, is String -> treeMaker.Literal(value) + is Char -> treeMaker.Literal(TypeTag.CHAR, value.toInt()) + is Byte, is Boolean, is Short, is Int, is Long, is Float, is Double, is String -> treeMaker.Literal(value) else -> if (isFinal(field.access)) convertLiteralExpression(getDefaultValue(type)) else null } + return treeMaker.VarDef(modifiers, name, typeExpression, initializer) } - private fun convertMethod(method: MethodNode, containingClass: ClassNode, containingClassSimpleName: Name): JCMethodDecl? { + private fun convertMethod(method: MethodNode, containingClass: ClassNode): JCMethodDecl? { if (isSynthetic(method.access)) return null val descriptor = origins[method]?.descriptor as? CallableDescriptor ?: return null @@ -147,22 +188,24 @@ class JCTreeConverter( method.visibleAnnotations } - val modifiers = convertModifiers(method.access, ElementKind.METHOD, visibleAnnotations, method.invisibleAnnotations) + val modifiers = convertModifiers( + if (containingClass.isEnum()) (method.access and VISIBILITY_MODIFIERS.inv()) else method.access, + ElementKind.METHOD, visibleAnnotations, method.invisibleAnnotations) val isConstructor = method.name == "" - val name = if (isConstructor) containingClassSimpleName else name(method.name) + val name = name(method.name) val typeParameters = JavacList.nil() val receiverParameter = null val returnType = Type.getReturnType(method.desc) - val returnTypeExpr = if (isConstructor) null else convertFqName(returnType.className) + val returnTypeExpr = if (isConstructor) null else convertType(returnType) - val parametersInfo = method.getParametersInfo() + 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 = convertFqName(info.type) + val type = convertType(info.type) treeMaker.VarDef(modifiers, name, type, null) } @@ -172,8 +215,10 @@ class JCTreeConverter( val body = if (defaultValue != null) { null - } else if ((method.access and Opcodes.ACC_ABSTRACT) > 0) { + } else if (isAbstract(method.access)) { null + } else if (isConstructor && containingClass.isEnum()) { + treeMaker.Block(0, JavacList.nil()) } else if (isConstructor) { // We already checked it in convertClass() val declaration = origins[containingClass]?.descriptor as ClassDescriptor @@ -226,10 +271,11 @@ class JCTreeConverter( } private fun convertAnnotation(annotation: AnnotationNode): JCAnnotation? { - val fqName = Type.getType(annotation.desc).className + val annotationType = Type.getType(annotation.desc) + val fqName = annotationType.className if (BLACKLISTED_ANNOTATATIONS.any { fqName.startsWith(it) }) return null - val name = convertFqName(fqName) + val name = convertType(annotationType) val values = mapPairedValues(annotation.values) { key, value -> treeMaker.Assign(convertSimpleName(key), convertLiteralExpression(value)) } @@ -238,8 +284,9 @@ class JCTreeConverter( private fun convertLiteralExpression(value: Any?): JCExpression { return when (value) { - null -> convertSimpleName("null") - is Byte, is Boolean, is Char, is Short, is Int, is Long, is Float, is Double, is String -> treeMaker.Literal(value) + null -> treeMaker.Literal(TypeTag.BOT, null) + is Char -> treeMaker.Literal(TypeTag.CHAR, value.toInt()) + is Byte, is Boolean, is Short, is Int, is Long, is Float, is Double, is String -> treeMaker.Literal(value) is ByteArray -> treeMaker.NewArray(null, JavacList.nil(), mapValues(value.asIterable()) { convertLiteralExpression(it) }) is BooleanArray -> treeMaker.NewArray(null, JavacList.nil(), mapValues(value.asIterable()) { convertLiteralExpression(it) }) @@ -249,10 +296,15 @@ class JCTreeConverter( is LongArray -> treeMaker.NewArray(null, JavacList.nil(), mapValues(value.asIterable()) { convertLiteralExpression(it) }) is FloatArray -> treeMaker.NewArray(null, JavacList.nil(), mapValues(value.asIterable()) { convertLiteralExpression(it) }) is DoubleArray -> treeMaker.NewArray(null, JavacList.nil(), mapValues(value.asIterable()) { convertLiteralExpression(it) }) - is Array<*> -> treeMaker.NewArray(null, JavacList.nil(), mapValues(value.asIterable()) { convertLiteralExpression(it) }) + is Array<*> -> { // Two-element String array for enumerations ([desc, fieldName]) + assert(value.size == 2) + val enumType = Type.getType(value[0] as String) + val valueName = value[1] as String + treeMaker.Select(convertType(enumType), name(valueName)) + } is List<*> -> treeMaker.NewArray(null, JavacList.nil(), mapValues(value) { convertLiteralExpression(it) }) - is Type -> treeMaker.Select(convertFqName(value.className), name("class")) + is Type -> treeMaker.Select(convertType(value), name("class")) is AnnotationNode -> convertAnnotation(value) ?: error("Annotation is filtered out") else -> throw IllegalArgumentException("Illegal literal expression value: $value (${value.javaClass.canonicalName})") } @@ -270,6 +322,14 @@ class JCTreeConverter( 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()) @@ -282,7 +342,23 @@ class JCTreeConverter( return expr } - private fun convertSimpleName(name: String): JCIdent = treeMaker.Ident(name(name)) + 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) } @@ -290,21 +366,38 @@ class JCTreeConverter( private class ParameterInfo( val access: Int, val name: String, - val type: String, + val type: Type, val visibleAnnotations: List?, val invisibleAnnotations: List?) -private fun MethodNode.getParametersInfo(): List { +private fun MethodNode.getParametersInfo(containingClass: ClassNode): List { val localVariables = this.localVariables ?: emptyList() val parameters = this.parameters ?: emptyList() - val isStatic = (access and Opcodes.ACC_STATIC) > 0 + val isStatic = isStatic(access) + + // First and second parameters in enum constructors are synthetic, we should ignore them + val isEnumConstructor = (name == "") && containingClass.isEnum() + val startParameterIndex = if (isEnumConstructor) 2 else 0 + val parameterTypes = Type.getArgumentTypes(desc) - return parameterTypes.mapIndexed { index, type -> - val name = parameters.getOrNull(index)?.name ?: localVariables.getOrNull(index + (if (isStatic) 0 else 1))?.name ?: "p$index" + + val parameterInfos = ArrayList(parameterTypes.size - startParameterIndex) + for (index in startParameterIndex..parameterTypes.lastIndex) { + val type = parameterTypes[index] + var name = parameters.getOrNull(index - startParameterIndex)?.name + ?: localVariables.getOrNull(index + (if (isStatic) 0 else 1))?.name + ?: "p${index - startParameterIndex}" + + // Property setters has bad parameter names + if (name.startsWith("<") && name.endsWith(">")) { + name = "p${index - startParameterIndex}" + } + val visibleAnnotations = visibleParameterAnnotations?.get(index) val invisibleAnnotations = invisibleParameterAnnotations?.get(index) - ParameterInfo(0, name, type.className, visibleAnnotations, invisibleAnnotations) + parameterInfos += ParameterInfo(0, name, type, visibleAnnotations, invisibleAnnotations) } + return parameterInfos } private inline fun mapValues(values: Iterable?, f: (T) -> R?): JavacList { @@ -340,7 +433,13 @@ private operator fun JavacList.plus(other: JavacList): JavacList private val ClassDescriptor.isNested: Boolean get() = containingDeclaration is ClassDescriptor +private fun isEnum(access: Int) = (access and Opcodes.ACC_ENUM) > 0 +private fun isPublic(access: Int) = (access and Opcodes.ACC_PUBLIC) > 0 private fun isSynthetic(access: Int) = (access and Opcodes.ACC_SYNTHETIC) > 0 private fun isFinal(access: Int) = (access and Opcodes.ACC_FINAL) > 0 private fun isStatic(access: Int) = (access and Opcodes.ACC_STATIC) > 0 -private fun ClassNode.isEnum() = (access and Opcodes.ACC_ENUM) > 0 \ No newline at end of file +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 List.isNullOrEmpty() = this == null || this.isEmpty() \ No newline at end of file diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt index 3169ae82f19..1f440fd824c 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptRunner.kt @@ -24,10 +24,8 @@ import com.sun.tools.javac.processing.AnnotationProcessingError import com.sun.tools.javac.processing.JavacProcessingEnvironment import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.util.Context -import com.sun.tools.javac.util.Log import com.sun.tools.javac.util.Options import java.io.File -import java.io.PrintWriter import javax.annotation.processing.Processor import javax.tools.JavaFileManager import com.sun.tools.javac.util.List as JavacList @@ -52,14 +50,16 @@ class KaptError : RuntimeException { class KaptRunner { val context = Context() + val compiler: KaptJavaCompiler private val options: Options init { JavacFileManager.preRegister(context) KaptJavaCompiler.preRegister(context) + compiler = JavaCompiler.instance(context) as KaptJavaCompiler + options = Options.instance(context) - context.put(Log.outKey, PrintWriter(System.err, true)) } fun parseJavaFiles( @@ -69,7 +69,6 @@ class KaptRunner { classpath.forEach { options.put(Option.CLASSPATH, it.canonicalPath) } val fileManager = context.get(JavaFileManager::class.java) as JavacFileManager - val compiler = JavaCompiler.instance(context) as KaptJavaCompiler try { val javaFileObjects = fileManager.getJavaFileObjectsFromFiles(javaSourceFiles) @@ -93,7 +92,6 @@ class KaptRunner { options.put(Option.D, classOutputDir.canonicalPath) val fileManager = context.get(JavaFileManager::class.java) as JavacFileManager - val compiler = JavaCompiler.instance(context) as KaptJavaCompiler val processingEnvironment = JavacProcessingEnvironment.instance(context) try { diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/SyntheticJavaFileObject.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/SyntheticJavaFileObject.kt new file mode 100644 index 00000000000..167677fae34 --- /dev/null +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/SyntheticJavaFileObject.kt @@ -0,0 +1,91 @@ +/* + * 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.file.BaseFileObject +import com.sun.tools.javac.file.JavacFileManager +import com.sun.tools.javac.tree.JCTree +import java.io.* +import java.net.URI +import java.net.URL +import javax.lang.model.element.Modifier +import javax.lang.model.element.NestingKind +import javax.tools.JavaFileObject + +class SyntheticJavaFileObject( + val compilationUnit: JCTree.JCCompilationUnit, + val clazz: JCTree.JCClassDecl, + val timestamp: Long, + fileManager: JavacFileManager +) : BaseFileObject(fileManager) { + override fun getShortName() = clazz.simpleName.toString() + + override fun inferBinaryName(path: MutableIterable?) = throw UnsupportedOperationException() + + override fun isNameCompatible(simpleName: String?, kind: JavaFileObject.Kind?) = true + + override fun getKind() = JavaFileObject.Kind.SOURCE + + override fun getName() = compilationUnit.packageName.toString().replace('.', '/') + clazz.name + ".java" + + override fun getAccessLevel(): Modifier? { + val flags = clazz.modifiers.getFlags() + if (Modifier.PUBLIC in flags) return Modifier.PUBLIC + if (Modifier.PROTECTED in flags) return Modifier.PROTECTED + if (Modifier.PRIVATE in flags) return Modifier.PRIVATE + return null + } + + override fun openInputStream() = getCharContent(false).byteInputStream() + + override fun getCharContent(ignoreEncodingErrors: Boolean) = compilationUnit.toString() + + override fun getNestingKind() = NestingKind.TOP_LEVEL + + override fun toUri(): URI? = URL(name).toURI() + + override fun openReader(ignoreEncodingErrors: Boolean) = getCharContent(ignoreEncodingErrors).reader() + + override fun openWriter() = throw UnsupportedOperationException() + + override fun getLastModified() = timestamp + + override fun openOutputStream() = throw UnsupportedOperationException() + + override fun delete() = throw UnsupportedOperationException() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other?.javaClass != javaClass) return false + + other as SyntheticJavaFileObject + + if (compilationUnit != other.compilationUnit) return false + if (clazz != other.clazz) return false + if (timestamp != other.timestamp) return false + + return true + } + + override fun hashCode(): Int { + var result = 0 + result = 31 * result + compilationUnit.hashCode() + result = 31 * result + clazz.hashCode() + result = 31 * result + timestamp.hashCode() + return result + } +} \ No newline at end of file diff --git a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractJCTreeConverterTest.kt b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractJCTreeConverterTest.kt index a4c6d5307f6..a095093ef12 100644 --- a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractJCTreeConverterTest.kt +++ b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractJCTreeConverterTest.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.kapt3.test +import com.intellij.openapi.util.text.StringUtil +import com.sun.tools.javac.comp.CompileStates import org.jetbrains.kotlin.codegen.CodegenTestCase import org.jetbrains.kotlin.codegen.CodegenTestUtil import org.jetbrains.kotlin.kapt3.JCTreeConverter @@ -23,6 +25,7 @@ import org.jetbrains.kotlin.kapt3.Kapt3BuilderFactory import org.jetbrains.kotlin.kapt3.KaptRunner import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.util.trimTrailingWhitespacesAndAddNewlineAtEOF import java.io.File abstract class AbstractJCTreeConverterTest : CodegenTestCase() { @@ -38,10 +41,23 @@ abstract class AbstractJCTreeConverterTest : CodegenTestCase() { val typeMapper = factory.generationState.typeMapper val kaptRunner = KaptRunner() - val converter = JCTreeConverter(kaptRunner.context, typeMapper, classBuilderFactory.compiledClasses, classBuilderFactory.origins) - val javaFiles = converter.convert() + try { + val converter = JCTreeConverter(kaptRunner.context, typeMapper, classBuilderFactory.compiledClasses, classBuilderFactory.origins) + val javaFiles = converter.convert() - KotlinTestUtils.assertEqualsToFile(txtFile, javaFiles.joinToString("\n\n////////////////////\n\n")) + kaptRunner.compiler.enterTrees(javaFiles) + + val actualRaw = javaFiles.joinToString ("\n\n////////////////////\n\n") + val actual = StringUtil.convertLineSeparators(actualRaw.trim({ it <= ' ' })).trimTrailingWhitespacesAndAddNewlineAtEOF() + + if (kaptRunner.compiler.shouldStop(CompileStates.CompileState.ENTER)) { + error("There were errors during analysis. See errors above. Stubs:\n\n$actual") + } + + KotlinTestUtils.assertEqualsToFile(txtFile, actual) + } finally { + kaptRunner.compiler.close() + } } } diff --git a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/JCTreeConverterTestGenerated.java b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/JCTreeConverterTestGenerated.java index e4031fe64c6..02761abe2f4 100644 --- a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/JCTreeConverterTestGenerated.java +++ b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/JCTreeConverterTestGenerated.java @@ -35,18 +35,48 @@ public class JCTreeConverterTestGenerated extends AbstractJCTreeConverterTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("plugins/kapt3/testData/converter"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("annotations.kt") + public void testAnnotations() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/annotations.kt"); + doTest(fileName); + } + @TestMetadata("dataClass.kt") public void testDataClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/dataClass.kt"); doTest(fileName); } + @TestMetadata("defaultImpls.kt") + public void testDefaultImpls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/defaultImpls.kt"); + doTest(fileName); + } + + @TestMetadata("enums.kt") + public void testEnums() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/enums.kt"); + doTest(fileName); + } + + @TestMetadata("functions.kt") + public void testFunctions() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/functions.kt"); + doTest(fileName); + } + @TestMetadata("inheritanceSimple.kt") public void testInheritanceSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/inheritanceSimple.kt"); doTest(fileName); } + @TestMetadata("jvmStatic.kt") + public void testJvmStatic() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/jvmStatic.kt"); + doTest(fileName); + } + @TestMetadata("nestedClasses.kt") public void testNestedClasses() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/nestedClasses.kt"); diff --git a/plugins/kapt3/testData/converter/annotations.kt b/plugins/kapt3/testData/converter/annotations.kt new file mode 100644 index 00000000000..65589cc2f07 --- /dev/null +++ b/plugins/kapt3/testData/converter/annotations.kt @@ -0,0 +1,31 @@ +annotation class Anno1 +enum class Colors { WHITE, BLACK } +annotation class Anno2( + val i: Int = 5, + val s: String = "ABC", + val ii: IntArray = intArrayOf(1, 2, 3), + val ss: Array = arrayOf("A", "B"), + val a: Anno1, + val color: Colors = Colors.BLACK, + val colors: Array = arrayOf(Colors.BLACK, Colors.WHITE), + val clazz: kotlin.reflect.KClass<*>, + val classes: Array> +) +annotation class Anno3(val value: String) + +@Anno1 +@Anno2(a = Anno1(), clazz = TestAnno::class, classes = arrayOf(TestAnno::class, Anno1::class)) +@Anno3(value = "value") +class TestAnno + +@Anno3("value") +@Anno2(i = 6, s = "BCD", ii = intArrayOf(4, 5, 6), ss = arrayOf("Z", "X"), + a = Anno1(), color = Colors.WHITE, colors = arrayOf(Colors.WHITE), + clazz = TestAnno::class, classes = arrayOf(TestAnno::class, Anno1::class)) +class TestAnno2 { + @Anno1 + fun a(@Anno3("param-pam-pam") param: String) {} + + @get:Anno3("getter") @set:Anno3("setter") @property:Anno3("property") @field:Anno3("field") @setparam:Anno3("setparam") + var b: String = "property initializer" +} \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/annotations.txt b/plugins/kapt3/testData/converter/annotations.txt new file mode 100644 index 00000000000..f82e16f0882 --- /dev/null +++ b/plugins/kapt3/testData/converter/annotations.txt @@ -0,0 +1,87 @@ +public abstract @interface Anno1 { +} + +//////////////////// + + +public enum Colors { + /*public static final*/ WHITE /* = null */, + /*public static final*/ BLACK /* = null */; + + Colors() { + } +} + +//////////////////// + + +public abstract @interface Anno2 { + + public abstract int i() default 5; + + public abstract java.lang.String s() default "ABC"; + + public abstract int[] ii() default {1, 2, 3}; + + public abstract java.lang.String[] ss() default {"A", "B"}; + + public abstract Anno1 a(); + + public abstract Colors color() default Colors.BLACK; + + public abstract Colors[] colors() default {Colors.BLACK, Colors.WHITE}; + + public abstract java.lang.Class clazz(); + + public abstract java.lang.Class[] classes(); +} + +//////////////////// + + +public abstract @interface Anno3 { + + public abstract java.lang.String value(); +} + +//////////////////// + + +@Anno3(value = "value") +@Anno2(a = @Anno1(), clazz = TestAnno.class, classes = {TestAnno.class, Anno1.class}) +@Anno1() +public final class TestAnno { + + public TestAnno() { + super(); + } +} + +//////////////////// + + +@Anno2(i = 6, s = "BCD", ii = {4, 5, 6}, ss = {"Z", "X"}, a = @Anno1(), color = Colors.WHITE, colors = {Colors.WHITE}, clazz = TestAnno.class, classes = {TestAnno.class, Anno1.class}) +@Anno3(value = "value") +public final class TestAnno2 { + @Anno3(value = "field") + private java.lang.String b; + + @Anno1() + public final void a(@Anno3(value = "param-pam-pam") + java.lang.String param) { + } + + @Anno3(value = "getter") + public final java.lang.String getB() { + return null; + } + + @Anno3(value = "setter") + public final void setB(@Anno3(value = "setparam") + java.lang.String p0) { + } + + public TestAnno2() { + super(); + } +} diff --git a/plugins/kapt3/testData/converter/dataClass.txt b/plugins/kapt3/testData/converter/dataClass.txt index 36fb23ce36a..a6c27d603a4 100644 --- a/plugins/kapt3/testData/converter/dataClass.txt +++ b/plugins/kapt3/testData/converter/dataClass.txt @@ -19,7 +19,7 @@ public final class User { return 0; } - public /*missing*/ User(java.lang.String firstName, java.lang.String secondName, int age) { + public User(java.lang.String firstName, java.lang.String secondName, int age) { super(); } diff --git a/plugins/kapt3/testData/converter/defaultImpls.kt b/plugins/kapt3/testData/converter/defaultImpls.kt new file mode 100644 index 00000000000..9ce992049c6 --- /dev/null +++ b/plugins/kapt3/testData/converter/defaultImpls.kt @@ -0,0 +1,15 @@ +interface IntfWithoutDefaultImpls + +interface IntfWithDefaultImpls { + fun a() {} +} + +interface Intf { + companion object { + val BLACK = 1 + const val WHITE = 2 + } + + val color: Int + get() = BLACK +} \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/defaultImpls.txt b/plugins/kapt3/testData/converter/defaultImpls.txt new file mode 100644 index 00000000000..0d58ca3818c --- /dev/null +++ b/plugins/kapt3/testData/converter/defaultImpls.txt @@ -0,0 +1,54 @@ +public abstract interface IntfWithoutDefaultImpls { +} + +//////////////////// + + +public abstract interface IntfWithDefaultImpls { + + public abstract void a(); + + public static final class DefaultImpls { + + public DefaultImpls() { + super(); + } + + public static void a(IntfWithDefaultImpls $this) { + } + } +} + +//////////////////// + + +public abstract interface Intf { + public static final Intf.Companion Companion = null; + public static final int WHITE = 2; + + public abstract int getColor(); + + public static final class DefaultImpls { + + public DefaultImpls() { + super(); + } + + public static int getColor(Intf $this) { + return 0; + } + } + + public static final class Companion { + private static final int BLACK = 1; + public static final int WHITE = 2; + + public final int getBLACK() { + return 0; + } + + private Companion() { + super(); + } + } +} diff --git a/plugins/kapt3/testData/converter/enums.kt b/plugins/kapt3/testData/converter/enums.kt new file mode 100644 index 00000000000..eeb62a8638a --- /dev/null +++ b/plugins/kapt3/testData/converter/enums.kt @@ -0,0 +1,10 @@ +enum class Enum1 { + BLACK, WHITE +} + +annotation class Anno1(val value: String) + +enum class Enum2(@Anno1("first") val col: String, @Anno1("second") val col2: Int) { + RED("red", 1), WHITE("white", 2); + fun color() = col +} \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/enums.txt b/plugins/kapt3/testData/converter/enums.txt new file mode 100644 index 00000000000..8b229e323a6 --- /dev/null +++ b/plugins/kapt3/testData/converter/enums.txt @@ -0,0 +1,42 @@ +public enum Enum1 { + /*public static final*/ BLACK /* = null */, + /*public static final*/ WHITE /* = null */; + + Enum1() { + } +} + +//////////////////// + + +public abstract @interface Anno1 { + + public abstract java.lang.String value(); +} + +//////////////////// + + +public enum Enum2 { + /*public static final*/ RED /* = null */, + /*public static final*/ WHITE /* = null */; + private final java.lang.String col = null; + private final int col2 = 0; + + final java.lang.String color() { + return null; + } + + final java.lang.String getCol() { + return null; + } + + final int getCol2() { + return 0; + } + + Enum2(@Anno1(value = "first") + java.lang.String col, @Anno1(value = "second") + int col2) { + } +} diff --git a/plugins/kapt3/testData/converter/functions.kt b/plugins/kapt3/testData/converter/functions.kt new file mode 100644 index 00000000000..9fff54a8d72 --- /dev/null +++ b/plugins/kapt3/testData/converter/functions.kt @@ -0,0 +1,6 @@ +class FunctionsTest { + fun f() = String::length + fun f2() = fun (a: Int, b: Int) = a > b + fun f3() = run {} + fun f4() = run { 3 } +} \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/functions.txt b/plugins/kapt3/testData/converter/functions.txt new file mode 100644 index 00000000000..63dd17835b3 --- /dev/null +++ b/plugins/kapt3/testData/converter/functions.txt @@ -0,0 +1,22 @@ + +public final class FunctionsTest { + + public final kotlin.reflect.KProperty1 f() { + return null; + } + + public final kotlin.jvm.functions.Function2 f2() { + return null; + } + + public final void f3() { + } + + public final int f4() { + return 0; + } + + public FunctionsTest() { + super(); + } +} \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/inheritanceSimple.txt b/plugins/kapt3/testData/converter/inheritanceSimple.txt index 7da051873fc..9139c8026c7 100644 --- a/plugins/kapt3/testData/converter/inheritanceSimple.txt +++ b/plugins/kapt3/testData/converter/inheritanceSimple.txt @@ -1,31 +1,14 @@ - public abstract interface Context { } //////////////////// -public final class Context { -} - -//////////////////// - - public enum Result { -; - public static final Result SUCCESS = null; - public static final Result ERROR = null; - - protected /*missing*/ Result(java.lang.String $enum_name_or_ordinal$0, int $enum_name_or_ordinal$1) { - super(null, 0); - } - - public static Result[] values() { - return null; - } - - public static Result valueOf(java.lang.String p0) { - return null; + /*public static final*/ SUCCESS /* = null */, + /*public static final*/ ERROR /* = null */; + + Result() { } } @@ -33,10 +16,10 @@ public enum Result { public abstract class BaseClass { - + public abstract Result doJob(); - - public /*missing*/ BaseClass(Context context, int num, boolean bool) { + + public BaseClass(Context context, int num, boolean bool) { super(); } } @@ -45,13 +28,13 @@ public abstract class BaseClass { public final class Inheritor extends BaseClass { - + @java.lang.Override() public Result doJob() { return null; } - - public /*missing*/ Inheritor(Context context) { + + public Inheritor(Context context) { super(null, 0, false); } -} \ No newline at end of file +} diff --git a/plugins/kapt3/testData/converter/jvmStatic.kt b/plugins/kapt3/testData/converter/jvmStatic.kt new file mode 100644 index 00000000000..001c0ed76a1 --- /dev/null +++ b/plugins/kapt3/testData/converter/jvmStatic.kt @@ -0,0 +1,13 @@ +class JvmStaticTest { + companion object { + @JvmStatic + val one = 1 + + val two = 2 + + const val c: Char = 'C' + } + + val three: Byte = 3.toByte() + val d: Char = 'D' +} diff --git a/plugins/kapt3/testData/converter/jvmStatic.txt b/plugins/kapt3/testData/converter/jvmStatic.txt new file mode 100644 index 00000000000..d007f8123b7 --- /dev/null +++ b/plugins/kapt3/testData/converter/jvmStatic.txt @@ -0,0 +1,40 @@ + +public final class JvmStaticTest { + private final byte three = "3"; + private final char d = 'D'; + private static final int one = 1; + private static final int two = 2; + public static final char c = 'C'; + public static final JvmStaticTest.Companion Companion = null; + + public final byte getThree() { + return "0"; + } + + public final char getD() { + return '\u0000'; + } + + public JvmStaticTest() { + super(); + } + + public static final int getOne() { + return 0; + } + + public static final class Companion { + + public final int getOne() { + return 0; + } + + public final int getTwo() { + return 0; + } + + private Companion() { + super(); + } + } +} \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/nestedClasses.txt b/plugins/kapt3/testData/converter/nestedClasses.txt index 1e4beaf4dc9..dd4d3a5f750 100644 --- a/plugins/kapt3/testData/converter/nestedClasses.txt +++ b/plugins/kapt3/testData/converter/nestedClasses.txt @@ -1,19 +1,18 @@ - public final class Test { - public /*missing*/ Test() { + public Test() { super(); } public static final class Nested { - public /*missing*/ Nested() { + public Nested() { super(); } public static final class NestedNested { - public /*missing*/ NestedNested() { + public NestedNested() { super(); } } @@ -21,7 +20,7 @@ public final class Test { public final class Inner { - public /*missing*/ Inner(Test $outer) { + public Inner(Test $outer) { super(); } } @@ -29,7 +28,7 @@ public final class Test { public static final class NestedObject { public static final Test.NestedObject INSTANCE = null; - private /*missing*/ NestedObject() { + private NestedObject() { super(); } } @@ -38,20 +37,10 @@ public final class Test { } public static enum NestedEnum { -; - public static final Test.NestedEnum BLACK = null; - public static final Test.NestedEnum WHITE = null; + /*public static final*/ BLACK /* = null */, + /*public static final*/ WHITE /* = null */; - protected /*missing*/ NestedEnum(java.lang.String $enum_name_or_ordinal$0, int $enum_name_or_ordinal$1) { - super(null, 0); - } - - public static Test.NestedEnum[] values() { - return null; - } - - public static Test.NestedEnum valueOf(java.lang.String p0) { - return null; + NestedEnum() { } } -} \ No newline at end of file +}