From 9c2ce475e205a1e6b1058ed1b44f094ac31e42c5 Mon Sep 17 00:00:00 2001 From: strangepleasures Date: Thu, 16 Nov 2023 21:14:36 +0000 Subject: [PATCH] KT-60821 [KAPT] Generate stubs without KaptTreeMaker Merge-request: KT-MR-12979 Merged-by: Pavel Mikhailovskii --- build.gradle.kts | 1 - .../kotlin/kapt3/base/annotationProcessing.kt | 6 + .../kapt3/stubs/AbstractKDocCommentKeeper.kt | 152 --- .../kapt3/stubs/Kapt3DocCommentKeeper.kt | 137 ++- .../kapt3/stubs/MembersPositionComparator.kt | 9 +- .../testData/converter/comments.kt | 1 + .../converter/defaultParameterValueOn.fir.txt | 4 +- .../testData/converter/jvmStatic.fir.txt | 2 +- .../testData/converter/primitiveTypes.fir.txt | 6 +- plugins/kapt4/build.gradle.kts | 12 - .../kapt4/Kapt4AnalysisHandlerExtension.kt | 85 +- .../kapt4/Kapt4ContextForStubGeneration.kt | 43 - .../kotlin/kapt4/Kapt4KDocCommentKeeper.kt | 61 -- .../kotlin/kapt4/Kapt4LineMappingCollector.kt | 18 +- .../kotlin/kapt4/Kapt4StubGenerator.kt | 978 ------------------ .../jetbrains/kotlin/kapt4/Kapt4TreeMaker.kt | 141 --- .../jetbrains/kotlin/kapt4/StubGenerator.kt | 782 ++++++++++++++ .../jetbrains/kotlin/kapt4/parseParameters.kt | 26 - .../src/org/jetbrains/kotlin/kapt4/utils.kt | 163 +-- .../org/jetbrains/kotlin/kapt4/Kapt4Facade.kt | 44 +- .../jetbrains/kotlin/kapt4/Kapt4Handler.kt | 8 +- 21 files changed, 1035 insertions(+), 1644 deletions(-) delete mode 100644 plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/AbstractKDocCommentKeeper.kt delete mode 100644 plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4ContextForStubGeneration.kt delete mode 100644 plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4KDocCommentKeeper.kt delete mode 100644 plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4StubGenerator.kt delete mode 100644 plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4TreeMaker.kt create mode 100644 plugins/kapt4/src/org/jetbrains/kotlin/kapt4/StubGenerator.kt delete mode 100644 plugins/kapt4/src/org/jetbrains/kotlin/kapt4/parseParameters.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4e3be0bd968..258262fc047 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -488,7 +488,6 @@ val projectsWithEnabledContextReceivers by extra { ":kotlinx-serialization-compiler-plugin.k2", ":plugins:parcelize:parcelize-compiler:parcelize.k2", ":plugins:fir-plugin-prototype", - ":plugins:kapt4", ) } diff --git a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt index ab77c135769..3d2d687ce87 100644 --- a/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt +++ b/plugins/kapt3/kapt3-base/src/org/jetbrains/kotlin/kapt3/base/annotationProcessing.kt @@ -29,6 +29,12 @@ import kotlin.collections.List import kotlin.system.measureTimeMillis import com.sun.tools.javac.util.List as JavacList +fun KaptContext.doAnnotationProcessing( + javaSourceFiles: List, + processors: List, + binaryTypesToReprocess: List = emptyList() +) = doAnnotationProcessing(javaSourceFiles, processors, JavacList.nil(), binaryTypesToReprocess) + fun KaptContext.doAnnotationProcessing( javaSourceFiles: List, processors: List, diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/AbstractKDocCommentKeeper.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/AbstractKDocCommentKeeper.kt deleted file mode 100644 index 4918af41baf..00000000000 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/AbstractKDocCommentKeeper.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2010-2017 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.stubs - -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiRecursiveElementVisitor -import com.intellij.psi.impl.source.tree.LeafPsiElement -import com.sun.tools.javac.parser.Tokens -import com.sun.tools.javac.tree.DCTree -import com.sun.tools.javac.tree.DocCommentTable -import com.sun.tools.javac.tree.JCTree -import com.sun.tools.javac.tree.TreeScanner -import org.jetbrains.kotlin.kapt3.base.KaptContext -import org.jetbrains.kotlin.kdoc.lexer.KDocTokens -import org.jetbrains.kotlin.kdoc.psi.api.KDoc -import org.jetbrains.org.objectweb.asm.Opcodes - -abstract class AbstractKDocCommentKeeper(protected val kaptContext: T) { - private val docCommentTable = KaptDocCommentTable() - - fun getDocTable(file: JCTree.JCCompilationUnit): DocCommentTable { - val map = docCommentTable.takeIf { it.map.isNotEmpty() } ?: return docCommentTable - - // Enum values with doc comments are rendered incorrectly in javac pretty print, - // so we delete the comments. - file.accept(object : TreeScanner() { - var removeComments = false - - override fun visitVarDef(def: JCTree.JCVariableDecl) { - if (!removeComments && (def.modifiers.flags and Opcodes.ACC_ENUM.toLong()) != 0L) { - map.removeComment(def) - - removeComments = true - super.visitVarDef(def) - removeComments = false - return - } - - super.visitVarDef(def) - } - - override fun scan(tree: JCTree?) { - if (removeComments && tree != null) { - map.removeComment(tree) - } - - super.scan(tree) - } - }) - - return docCommentTable - } - - protected fun saveKDocComment(tree: JCTree, comment: KDoc) { - docCommentTable.putComment(tree, KDocComment(escapeNestedComments(extractCommentText(comment)))) - } - - private fun escapeNestedComments(text: String): String { - val result = StringBuilder() - - var index = 0 - var commentLevel = 0 - - while (index < text.length) { - val currentChar = text[index] - fun nextChar() = text.getOrNull(index + 1) - - if (currentChar == '/' && nextChar() == '*') { - commentLevel++ - index++ - result.append("/ *") - } else if (currentChar == '*' && nextChar() == '/') { - commentLevel = maxOf(0, commentLevel - 1) - index++ - result.append("* /") - } else { - result.append(currentChar) - } - - index++ - } - - return result.toString() - } - - private fun extractCommentText(docComment: KDoc): String { - return buildString { - docComment.accept(object : PsiRecursiveElementVisitor() { - override fun visitElement(element: PsiElement) { - if (element is LeafPsiElement) { - if (element.isKDocLeadingAsterisk()) { - val indent = takeLastWhile { it == ' ' || it == '\t' }.length - if (indent > 0) { - delete(length - indent, length) - } - } else if (!element.isKDocStart() && !element.isKDocEnd()) { - append(element.text) - } - } - - super.visitElement(element) - } - }) - }.trimIndent().trim() - } - - private fun LeafPsiElement.isKDocStart() = elementType == KDocTokens.START - private fun LeafPsiElement.isKDocEnd() = elementType == KDocTokens.END - private fun LeafPsiElement.isKDocLeadingAsterisk() = elementType == KDocTokens.LEADING_ASTERISK -} - -private class KDocComment(val body: String) : Tokens.Comment { - override fun getSourcePos(index: Int) = -1 - override fun getStyle() = Tokens.Comment.CommentStyle.JAVADOC - override fun getText() = body - override fun isDeprecated() = false -} - -private class KaptDocCommentTable(map: Map = emptyMap()) : DocCommentTable { - private val table = map.toMutableMap() - - val map: Map - get() = table - - override fun hasComment(tree: JCTree) = tree in table - override fun getComment(tree: JCTree) = table[tree] - override fun getCommentText(tree: JCTree) = getComment(tree)?.text - - override fun getCommentTree(tree: JCTree): DCTree.DCDocComment? = null - - override fun putComment(tree: JCTree, c: Tokens.Comment) { - table[tree] = c - } - - fun removeComment(tree: JCTree) { - table.remove(tree) - } -} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/Kapt3DocCommentKeeper.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/Kapt3DocCommentKeeper.kt index ec161bade2a..4e9ffdbe4fb 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/Kapt3DocCommentKeeper.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/Kapt3DocCommentKeeper.kt @@ -5,19 +5,31 @@ package org.jetbrains.kotlin.kapt3.stubs +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiRecursiveElementVisitor +import com.intellij.psi.impl.source.tree.LeafPsiElement +import com.sun.tools.javac.parser.Tokens +import com.sun.tools.javac.tree.DCTree +import com.sun.tools.javac.tree.DocCommentTable import com.sun.tools.javac.tree.JCTree +import com.sun.tools.javac.tree.TreeScanner import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor import org.jetbrains.kotlin.kapt3.KaptContextForStubGeneration +import org.jetbrains.kotlin.kdoc.lexer.KDocTokens +import org.jetbrains.kotlin.kdoc.psi.api.KDoc import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.tree.FieldNode import org.jetbrains.org.objectweb.asm.tree.MethodNode -class Kapt3DocCommentKeeper(kaptContext: KaptContextForStubGeneration) : AbstractKDocCommentKeeper(kaptContext) { +internal class Kapt3DocCommentKeeper(private val kaptContext: KaptContextForStubGeneration) { + private val docCommentTable = KaptDocCommentTable() + fun saveKDocComment(tree: JCTree, node: Any) { val origin = kaptContext.origins[node] ?: return val psiElement = origin.element as? KtDeclaration ?: return @@ -45,4 +57,125 @@ class Kapt3DocCommentKeeper(kaptContext: KaptContextForStubGeneration) : Abstrac saveKDocComment(tree, docComment) } -} \ No newline at end of file + + fun getDocTable(file: JCTree.JCCompilationUnit): DocCommentTable { + val map = docCommentTable.takeIf { it.map.isNotEmpty() } ?: return docCommentTable + + // Enum values with doc comments are rendered incorrectly in javac pretty print, + // so we delete the comments. + file.accept(object : TreeScanner() { + var removeComments = false + + override fun visitVarDef(def: JCTree.JCVariableDecl) { + if (!removeComments && (def.modifiers.flags and Opcodes.ACC_ENUM.toLong()) != 0L) { + map.removeComment(def) + + removeComments = true + super.visitVarDef(def) + removeComments = false + return + } + + super.visitVarDef(def) + } + + override fun scan(tree: JCTree?) { + if (removeComments && tree != null) { + map.removeComment(tree) + } + + super.scan(tree) + } + }) + + return docCommentTable + } + + protected fun saveKDocComment(tree: JCTree, comment: KDoc) { + docCommentTable.putComment(tree, KDocComment(extractComment(comment))) + } +} + +private class KDocComment(val body: String) : Tokens.Comment { + override fun getSourcePos(index: Int) = -1 + override fun getStyle() = Tokens.Comment.CommentStyle.JAVADOC + override fun getText() = body + override fun isDeprecated() = false +} + +private class KaptDocCommentTable(map: Map = emptyMap()) : DocCommentTable { + private val table = map.toMutableMap() + + val map: Map + get() = table + + override fun hasComment(tree: JCTree) = tree in table + override fun getComment(tree: JCTree) = table[tree] + override fun getCommentText(tree: JCTree) = getComment(tree)?.text + + override fun getCommentTree(tree: JCTree): DCTree.DCDocComment? = null + + override fun putComment(tree: JCTree, c: Tokens.Comment) { + table[tree] = c + } + + fun removeComment(tree: JCTree) { + table.remove(tree) + } +} + +fun extractComment(comment: KDoc) = escapeNestedComments(extractCommentText(comment)) + + +private fun escapeNestedComments(text: String): String { + val result = StringBuilder() + + var index = 0 + var commentLevel = 0 + + while (index < text.length) { + val currentChar = text[index] + fun nextChar() = text.getOrNull(index + 1) + + if (currentChar == '/' && nextChar() == '*') { + commentLevel++ + index++ + result.append("/ *") + } else if (currentChar == '*' && nextChar() == '/') { + commentLevel = maxOf(0, commentLevel - 1) + index++ + result.append("* /") + } else { + result.append(currentChar) + } + + index++ + } + + return result.toString() +} + +private fun extractCommentText(docComment: KDoc): String { + return buildString { + docComment.accept(object : PsiRecursiveElementVisitor() { + override fun visitElement(element: PsiElement) { + if (element is LeafPsiElement) { + if (element.isKDocLeadingAsterisk()) { + val indent = takeLastWhile { it == ' ' || it == '\t' }.length + if (indent > 0) { + delete(length - indent, length) + } + } else if (!element.isKDocStart() && !element.isKDocEnd()) { + append(element.text) + } + } + + super.visitElement(element) + } + }) + }.trimIndent().trim() +} + +private fun LeafPsiElement.isKDocStart() = elementType == KDocTokens.START +private fun LeafPsiElement.isKDocEnd() = elementType == KDocTokens.END +private fun LeafPsiElement.isKDocLeadingAsterisk() = elementType == KDocTokens.LEADING_ASTERISK \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/MembersPositionComparator.kt b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/MembersPositionComparator.kt index b385d922da3..c39fca5a1c1 100644 --- a/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/MembersPositionComparator.kt +++ b/plugins/kapt3/kapt3-compiler/src/org/jetbrains/kotlin/kapt3/stubs/MembersPositionComparator.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.kapt3.stubs -import com.sun.tools.javac.tree.JCTree import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition /** @@ -20,9 +19,9 @@ import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition * The consequence is that the contents of the generated stub files may not be consistent across a clean build and an incremental * build, making the build non-deterministic and dependent tasks run unnecessarily (see KT-40882). */ -class MembersPositionComparator(val classSource: KotlinPosition?, val memberData: Map) : - Comparator { - override fun compare(o1: JCTree, o2: JCTree): Int { +class MembersPositionComparator(val classSource: KotlinPosition?, val memberData: Map) : + Comparator { + override fun compare(o1: T, o2: T): Int { val data1 = memberData.getValue(o1) val data2 = memberData.getValue(o2) classSource ?: return compareDescriptors(data1, data2) @@ -51,4 +50,4 @@ class MembersPositionComparator(val classSource: KotlinPosition?, val memberData return m1.descriptor.compareTo(m2.descriptor) } } -class MemberData(val name: String, val descriptor: String, val position: KotlinPosition?) \ No newline at end of file +class MemberData(val name: String, val descriptor: String, val position: KotlinPosition?) diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/comments.kt b/plugins/kapt3/kapt3-compiler/testData/converter/comments.kt index 390a0feda5c..c626e3f90d6 100644 --- a/plugins/kapt3/kapt3-compiler/testData/converter/comments.kt +++ b/plugins/kapt3/kapt3-compiler/testData/converter/comments.kt @@ -44,6 +44,7 @@ class Test4 { } enum class EnumError { + /** This is the one */ One { override fun doIt() = "" diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/defaultParameterValueOn.fir.txt b/plugins/kapt3/kapt3-compiler/testData/converter/defaultParameterValueOn.fir.txt index 1361f32f47d..78d9d983851 100644 --- a/plugins/kapt3/kapt3-compiler/testData/converter/defaultParameterValueOn.fir.txt +++ b/plugins/kapt3/kapt3-compiler/testData/converter/defaultParameterValueOn.fir.txt @@ -135,10 +135,10 @@ public enum Em { @kotlin.Metadata() public final class Foo { private final boolean z = true; - private final byte b = (byte)0; + private final byte b = 0; private final char c = 'c'; private final char c2 = '\n'; - private final short sh = (short)10; + private final short sh = 10; private final int i = 10; private final long l = -10L; private final float f = 1.0F; diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/jvmStatic.fir.txt b/plugins/kapt3/kapt3-compiler/testData/converter/jvmStatic.fir.txt index e6079c3550b..5bf5ed3a561 100644 --- a/plugins/kapt3/kapt3-compiler/testData/converter/jvmStatic.fir.txt +++ b/plugins/kapt3/kapt3-compiler/testData/converter/jvmStatic.fir.txt @@ -79,7 +79,7 @@ public final class JvmStaticTest { private static final int one = 1; public static final int two = 2; public static final char c = 'C'; - public final byte three = (byte)3; + public final byte three = 3; public final char d = 'D'; public JvmStaticTest() { diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/primitiveTypes.fir.txt b/plugins/kapt3/kapt3-compiler/testData/converter/primitiveTypes.fir.txt index 9c9d57ee9fd..c88492b2f65 100644 --- a/plugins/kapt3/kapt3-compiler/testData/converter/primitiveTypes.fir.txt +++ b/plugins/kapt3/kapt3-compiler/testData/converter/primitiveTypes.fir.txt @@ -150,9 +150,9 @@ public final class PrimitiveTypes { public static final int intMinValue = -2147483648; public static final int intMaxValue = 2147483647; public static final int intHex = -1; - public static final byte byte0 = (byte)0; - public static final byte byte50 = (byte)50; - public static final short short5 = (short)5; + public static final byte byte0 = 0; + public static final byte byte50 = 50; + public static final short short5 = 5; public static final char charC = 'C'; public static final char char0 = '\u0000'; public static final char char10 = '\n'; diff --git a/plugins/kapt4/build.gradle.kts b/plugins/kapt4/build.gradle.kts index 6e2d76f1e21..07a951535dc 100644 --- a/plugins/kapt4/build.gradle.kts +++ b/plugins/kapt4/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - description = "Annotation Processor for Kotlin" plugins { @@ -13,7 +11,6 @@ dependencies { implementation(project(":kotlin-annotation-processing-compiler")) compileOnly(project(":kotlin-annotation-processing-base")) compileOnly(project(":analysis:analysis-api-standalone")) - compileOnly(toolsJarApi()) embedded(project(":kotlin-annotation-processing-compiler")) { isTransitive = false } @@ -77,12 +74,3 @@ publish() runtimeJar() sourcesJar() javadocJar() - - -allprojects { - tasks.withType(KotlinCompile::class).all { - kotlinOptions { - freeCompilerArgs += "-Xcontext-receivers" - } - } -} diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4AnalysisHandlerExtension.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4AnalysisHandlerExtension.kt index 7a1495ab0df..7f970458d77 100644 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4AnalysisHandlerExtension.kt +++ b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4AnalysisHandlerExtension.kt @@ -5,8 +5,9 @@ package org.jetbrains.kotlin.kapt4 -import com.sun.tools.javac.tree.JCTree +import com.intellij.psi.PsiJavaFile import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider import org.jetbrains.kotlin.analysis.api.standalone.KtAlwaysAccessibleLifetimeTokenProvider @@ -14,7 +15,6 @@ import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISe import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil.formatOutputMessage import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot @@ -26,11 +26,10 @@ import org.jetbrains.kotlin.kapt3.EfficientProcessorLoader import org.jetbrains.kotlin.kapt3.KAPT_OPTIONS import org.jetbrains.kotlin.kapt3.base.* import org.jetbrains.kotlin.kapt3.base.util.KaptLogger -import org.jetbrains.kotlin.kapt3.base.util.getPackageNameJava9Aware import org.jetbrains.kotlin.kapt3.base.util.info import org.jetbrains.kotlin.kapt3.measureTimeMillis import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger -import org.jetbrains.kotlin.kapt3.util.prettyPrint +import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion import org.jetbrains.kotlin.psi.KtFile import java.io.File @@ -91,52 +90,70 @@ private class Kapt4AnalysisHandlerExtension : FirAnalysisHandlerExtension() { } return try { - KtAnalysisSessionProvider.getInstance(module.project).analyze(module) { - Kapt4ContextForStubGeneration( + if (options.mode.generateStubs) { + KtAnalysisSessionProvider.getInstance(module.project).analyze(module) { + generateAndSaveStubs( + psiFiles.filterIsInstance(), + options, + logger, + configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES), + this, + configuration[CommonConfigurationKeys.METADATA_VERSION] + ) + } + } + if (options.mode.runAnnotationProcessing) { + KaptContext( options, false, - logger, - this, - psiFiles.filterIsInstance(), - configuration[CommonConfigurationKeys.METADATA_VERSION] + logger ).use { context -> - if (options.mode.generateStubs) - generateStubs(context, messageCollector, configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES)) - - if (options.mode.runAnnotationProcessing) - runProcessors(context, options) + runProcessors(context, options) } - true } + true + } catch (e: Exception) { logger.exception(e) false } } - private fun generateStubs(context: Kapt4ContextForStubGeneration, messageCollector: MessageCollector, reportOutputFiles: Boolean) { - val generator = with(context) { Kapt4StubGenerator() } - val (stubGenerationTime, classesToStubs) = measureTimeMillis { generator.generateStubs() } + private fun generateAndSaveStubs( + files: List, + options: KaptOptions, + logger: MessageCollectorBackedKaptLogger, + reportOutputFiles: Boolean, + analysisSession: KtAnalysisSession, + overriddenMetadataVersion: BinaryVersion? + ) { + fun onError(message: String) { + if(options[KaptFlag.STRICT]) { + logger.error(message) + } else { + logger.warn(message) + } + } + val (stubGenerationTime, classesToStubs) = measureTimeMillis { + generateStubs(files, options, ::onError, analysisSession, overriddenMetadataVersion) + } - context.logger.info { "Java stub generation took $stubGenerationTime ms" } - val infoBuilder = if (context.logger.isVerbose) StringBuilder("Stubs for Kotlin classes: ") else null + logger.info { "Java stub generation took $stubGenerationTime ms" } + val infoBuilder = if (logger.isVerbose) StringBuilder("Stubs for Kotlin classes: ") else null for ((lightClass, kaptStub) in classesToStubs) { if (kaptStub == null) continue - val stub = kaptStub.file - val className = (stub.defs.first { it is JCTree.JCClassDecl } as JCTree.JCClassDecl).simpleName.toString() - - val packageName = stub.getPackageNameJava9Aware()?.toString() ?: "" - val stubsOutputDir = context.options.stubsOutputDir + val stub = kaptStub.source + val className = lightClass.name + val packageName = (lightClass.parent as PsiJavaFile).packageName + val stubsOutputDir = options.stubsOutputDir val packageDir = if (packageName.isEmpty()) stubsOutputDir else File(stubsOutputDir, packageName.replace('.', '/')) packageDir.mkdirs() val generatedFile = File(packageDir, "$className.java") - generatedFile.writeText(stub.prettyPrint(context.context)) - + generatedFile.writeText(stub) infoBuilder?.append(generatedFile.path) - - kaptStub.writeMetadataIfNeeded(forSource = generatedFile) + kaptStub.writeMetadata(forSource = generatedFile) if (reportOutputFiles) { val ktFiles = when(lightClass) { @@ -144,17 +161,17 @@ private class Kapt4AnalysisHandlerExtension : FirAnalysisHandlerExtension() { else -> listOfNotNull(lightClass.kotlinOrigin?.containingKtFile) } val report = formatOutputMessage(ktFiles.map { it.virtualFilePath }, generatedFile.path) - messageCollector.report(CompilerMessageSeverity.OUTPUT, report) + logger.messageCollector.report(CompilerMessageSeverity.OUTPUT, report) } } - context.logger.info { infoBuilder.toString() } + logger.info { infoBuilder.toString() } - File(context.options.stubsOutputDir, "error").apply { mkdirs() }.resolve("NonExistentClass.java") + File(options.stubsOutputDir, "error").apply { mkdirs() }.resolve("NonExistentClass.java") .writeText("package error;\npublic class NonExistentClass {}\n") } - private fun runProcessors(context: Kapt4ContextForStubGeneration, options: KaptOptions) { + private fun runProcessors(context: KaptContext, options: KaptOptions) { val sources = options.collectJavaSourceFiles(context.sourcesToReprocess) if (sources.isEmpty()) return EfficientProcessorLoader(options, context.logger).use { diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4ContextForStubGeneration.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4ContextForStubGeneration.kt deleted file mode 100644 index 820aef2d9d9..00000000000 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4ContextForStubGeneration.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.kapt4 - -import com.sun.tools.javac.tree.TreeMaker -import com.sun.tools.javac.util.Context -import org.jetbrains.kotlin.analysis.api.KtAnalysisSession -import org.jetbrains.kotlin.asJava.classes.KtLightClass -import org.jetbrains.kotlin.asJava.findFacadeClass -import org.jetbrains.kotlin.asJava.toLightClass -import org.jetbrains.kotlin.kapt3.base.KaptContext -import org.jetbrains.kotlin.kapt3.base.KaptOptions -import org.jetbrains.kotlin.kapt3.base.util.KaptLogger -import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtFile - -internal class Kapt4ContextForStubGeneration( - options: KaptOptions, - withJdk: Boolean, - logger: KaptLogger, - val analysisSession: KtAnalysisSession, - val files: List, - val overriddenMetadataVersion: BinaryVersion? = null -) : KaptContext(options, withJdk, logger) { - val classes: Iterable = buildSet { - files.flatMapTo(this) { file -> - file.children.filterIsInstance().mapNotNull { - it.toLightClass() - } - } - files.mapNotNullTo(this) { ktFile -> ktFile.findFacadeClass() }.distinct() - } - - internal val treeMaker = TreeMaker.instance(context) as Kapt4TreeMaker - - override fun preregisterTreeMaker(context: Context) { - Kapt4TreeMaker.preRegister(context) - } -} diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4KDocCommentKeeper.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4KDocCommentKeeper.kt deleted file mode 100644 index 685c0f81f36..00000000000 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4KDocCommentKeeper.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.kapt4 - -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiField -import com.intellij.psi.PsiMethod -import com.sun.tools.javac.tree.JCTree -import org.jetbrains.kotlin.asJava.elements.KtLightElement -import org.jetbrains.kotlin.asJava.elements.KtLightMember -import org.jetbrains.kotlin.asJava.elements.KtLightParameter -import org.jetbrains.kotlin.kapt3.stubs.AbstractKDocCommentKeeper -import org.jetbrains.kotlin.psi.* - - -internal class Kapt4KDocCommentKeeper(context: Kapt4ContextForStubGeneration): AbstractKDocCommentKeeper(context) { - fun saveKDocComment(tree: JCTree, psiElement: PsiElement) { - val ktElement = psiElement.extractOriginalKtDeclaration() ?: return - if (psiElement is PsiField && ktElement is KtObjectDeclaration) { - // Do not write KDoc on object instance field - return - } - - val docComment = - when { - ktElement is KtProperty -> ktElement.docComment - ktElement.docComment == null && ktElement is KtPropertyAccessor -> ktElement.property.docComment - else -> ktElement.docComment - } ?: return - - if (psiElement is PsiMethod && psiElement.isConstructor && ktElement is KtClassOrObject) { - // We don't want the class comment to be duplicated on () - return - } - - saveKDocComment(tree, docComment) - } -} - -inline fun PsiElement.extractOriginalKtDeclaration(): T? { - // This when is needed to avoid recursion - val elementToExtract = when (this) { - is KtLightParameter -> when (kotlinOrigin) { - null -> method - else -> return kotlinOrigin as? T - } - else -> this - } - - return when (elementToExtract) { - is KtLightMember<*> -> { - val origin = elementToExtract.lightMemberOrigin - origin?.auxiliaryOriginalElement ?: origin?.originalElement ?: elementToExtract.kotlinOrigin - } - is KtLightElement<*, *> -> elementToExtract.kotlinOrigin - else -> null - } as? T -} diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4LineMappingCollector.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4LineMappingCollector.kt index f58b1481aab..c1fb4a4e415 100644 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4LineMappingCollector.kt +++ b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4LineMappingCollector.kt @@ -6,13 +6,12 @@ package org.jetbrains.kotlin.kapt4 import com.intellij.psi.* -import com.sun.tools.javac.tree.JCTree import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition -import org.jetbrains.kotlin.kapt3.base.stubs.getJavacSignature import org.jetbrains.kotlin.kapt3.stubs.KaptLineMappingCollectorBase +import org.jetbrains.org.objectweb.asm.tree.ClassNode -internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() { +internal class Kapt4LineMappingCollector : KaptLineMappingCollectorBase() { fun registerClass(lightClass: PsiClass) { register(lightClass, lightClass.qualifiedNameWithSlashes) } @@ -25,13 +24,8 @@ internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() { register(field, lightClass.qualifiedNameWithSlashes + "#" + field.name) } - fun registerSignature(declaration: JCTree.JCMethodDecl, method: PsiMethod) { - signatureInfo[declaration.getJavacSignature()] = method.name + method.signature - } - - fun getPosition(lightClass: PsiClass): KotlinPosition? { - return lineInfo[lightClass.qualifiedNameWithSlashes] - } + fun getPosition(clazz: PsiClass): KotlinPosition? = + lineInfo[clazz.qualifiedNameWithSlashes] fun getPosition(lightClass: PsiClass, method: PsiMethod): KotlinPosition? = lineInfo[lightClass.qualifiedNameWithSlashes + "#" + method.name + method.signature] @@ -47,4 +41,8 @@ internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() { private val PsiClass.qualifiedNameWithSlashes: String get() = qualifiedNameWithDollars?.replace(".", "/") ?: "" + + fun registerSignature(javacSignature: String, method: PsiMethod) { + signatureInfo[javacSignature] = method.name + method.signature + } } diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4StubGenerator.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4StubGenerator.kt deleted file mode 100644 index 713bb92b108..00000000000 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4StubGenerator.kt +++ /dev/null @@ -1,978 +0,0 @@ -/* - * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -@file:Suppress("UnstableApiUsage") - -package org.jetbrains.kotlin.kapt4 - -import com.google.common.collect.HashMultimap -import com.google.common.collect.Multimap -import com.intellij.lang.ASTNode -import com.intellij.psi.* -import com.intellij.psi.impl.source.tree.LeafPsiElement -import com.sun.tools.javac.code.Flags -import com.sun.tools.javac.code.TypeTag -import com.sun.tools.javac.parser.Tokens -import com.sun.tools.javac.tree.JCTree -import com.sun.tools.javac.tree.JCTree.* -import kotlinx.kapt.KaptIgnored -import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol -import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol -import org.jetbrains.kotlin.analysis.api.symbols.KtEnumEntrySymbol -import org.jetbrains.kotlin.asJava.classes.KtLightClass -import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade -import org.jetbrains.kotlin.asJava.elements.KtLightElement -import org.jetbrains.kotlin.asJava.elements.KtLightElementBase -import org.jetbrains.kotlin.builtins.StandardNames -import org.jetbrains.kotlin.config.LanguageVersion -import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil -import org.jetbrains.kotlin.idea.references.KtReference -import org.jetbrains.kotlin.kapt3.base.KaptFlag -import org.jetbrains.kotlin.kapt3.base.javac.kaptError -import org.jetbrains.kotlin.kapt3.base.javac.reportKaptError -import org.jetbrains.kotlin.kapt3.base.stubs.KaptStubLineInformation -import org.jetbrains.kotlin.kapt3.base.util.TopLevelJava9Aware -import org.jetbrains.kotlin.kapt3.stubs.MemberData -import org.jetbrains.kotlin.kapt3.stubs.MembersPositionComparator -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.light.classes.symbol.classes.SymbolLightClassForNamedClassLike -import org.jetbrains.kotlin.load.java.JvmAnnotationNames.* -import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.SpecialNames -import org.jetbrains.kotlin.name.isOneSegmentFQN -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.stubs.elements.KtAnnotationEntryElementType -import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny -import org.jetbrains.kotlin.utils.addToStdlib.runIf -import org.jetbrains.kotlin.utils.addToStdlib.runUnless -import org.jetbrains.kotlin.utils.toMetadataVersion -import org.jetbrains.org.objectweb.asm.Opcodes -import org.jetbrains.org.objectweb.asm.Type -import java.io.File -import javax.lang.model.element.ElementKind -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract -import kotlin.math.sign - -context(Kapt4ContextForStubGeneration) -internal class Kapt4StubGenerator { - private companion object { - private const val VISIBILITY_MODIFIERS = (Opcodes.ACC_PUBLIC or Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED).toLong() - private const val MODALITY_MODIFIERS = (Opcodes.ACC_FINAL or Opcodes.ACC_ABSTRACT).toLong() - - private const val CLASS_MODIFIERS = VISIBILITY_MODIFIERS or MODALITY_MODIFIERS or - (Opcodes.ACC_DEPRECATED or Opcodes.ACC_INTERFACE or Opcodes.ACC_ANNOTATION or Opcodes.ACC_ENUM or Opcodes.ACC_STATIC).toLong() - - private const val METHOD_MODIFIERS = VISIBILITY_MODIFIERS or MODALITY_MODIFIERS or - (Opcodes.ACC_DEPRECATED or Opcodes.ACC_SYNCHRONIZED or Opcodes.ACC_NATIVE or Opcodes.ACC_STATIC or Opcodes.ACC_STRICT).toLong() - - private const val FIELD_MODIFIERS = VISIBILITY_MODIFIERS or MODALITY_MODIFIERS or - (Opcodes.ACC_VOLATILE or Opcodes.ACC_TRANSIENT or Opcodes.ACC_ENUM or Opcodes.ACC_STATIC).toLong() - - private const val PARAMETER_MODIFIERS = FIELD_MODIFIERS or Flags.PARAMETER or Flags.VARARGS or Opcodes.ACC_FINAL.toLong() - - private val BLACKLISTED_ANNOTATIONS = listOf( - "java.lang.Synthetic", - "synthetic.kotlin.jvm.GeneratedByJvmOverloads" // kapt3-related annotation for marking JvmOverloads-generated methods - ) - - private val KOTLIN_METADATA_ANNOTATION = Metadata::class.java.name - - private val JAVA_KEYWORD_FILTER_REGEX = "[a-z]+".toRegex() - - @Suppress("UselessCallOnNotNull") // nullable toString(), KT-27724 - private val JAVA_KEYWORDS = Tokens.TokenKind.values() - .filter { JAVA_KEYWORD_FILTER_REGEX.matches(it.toString().orEmpty()) } - .mapTo(hashSetOf(), Any::toString) - } - - private val strictMode = options[KaptFlag.STRICT] - private val stripMetadata = options[KaptFlag.STRIP_METADATA] - private val keepKdocComments = options[KaptFlag.KEEP_KDOC_COMMENTS_IN_STUBS] - private val dumpDefaultParameterValues = options[KaptFlag.DUMP_DEFAULT_PARAMETER_VALUES] - - private val kdocCommentKeeper = runIf(keepKdocComments) { Kapt4KDocCommentKeeper(this@Kapt4ContextForStubGeneration) } - - internal fun generateStubs(): Map { - return classes.associateWith { convertTopLevelClass(it) } - } - - private fun convertTopLevelClass(lightClass: KtLightClass): KaptStub? { - val ktFiles = when(lightClass) { - is KtLightClassForFacade -> lightClass.files - else -> listOfNotNull(lightClass.kotlinOrigin?.containingKtFile) - } - val lineMappings = Kapt4LineMappingCollector() - val packageName = (lightClass.parent as? PsiJavaFile)?.packageName ?: return null - val packageClause = runUnless(packageName.isBlank()) { treeMaker.FqName(packageName) } - - val unresolvedQualifiersRecorder = UnresolvedQualifiersRecorder(ktFiles) - val classDeclaration = with(unresolvedQualifiersRecorder) { - convertClass(lightClass, lineMappings, packageName) ?: return null - } - - val classes = JavacList.of(classDeclaration) - - // imports should be collected after class conversion to - val imports = ktFiles.fold(JavacList.nil()) { acc, file -> - acc.appendList(convertImports(file, unresolvedQualifiersRecorder)) - } - - val topLevel = treeMaker.TopLevelJava9Aware(packageClause, imports + classes) - if (kdocCommentKeeper != null) { - topLevel.docComments = kdocCommentKeeper.getDocTable(topLevel) - } - - return KaptStub(topLevel, lineMappings.serialize()) - } - - context(UnresolvedQualifiersRecorder) - private fun convertClass( - lightClass: PsiClass, - lineMappings: Kapt4LineMappingCollector, - packageFqName: String - ): JCClassDecl? { - if (!checkIfValidTypeName(lightClass, lightClass.defaultType)) return null - - val parentClass = lightClass.parent as? PsiClass - - val flags = if ((parentClass?.isInterface == true || parentClass?.isAnnotationType == true) && !lightClass.isPublic) - (lightClass.accessFlags and Flags.PRIVATE.toLong().inv()) else lightClass.accessFlags - - val metadata = calculateMetadata(lightClass) - - val isEnum = lightClass.isEnum - val modifiers = convertModifiers( - lightClass, - flags, - if (isEnum) ElementKind.ENUM else ElementKind.CLASS, - packageFqName, - lightClass.annotations.toList(), - metadata, - ) - - val simpleName = lightClass.name!! - if (!isValidIdentifier(simpleName)) return null - - val classSignature = parseClassSignature(lightClass) - - val enumValues: JavacList = mapJList(lightClass.fields) { field -> - if (field !is PsiEnumConstant) return@mapJList null - val constructorArguments = lightClass.constructors.firstOrNull()?.parameters?.mapNotNull { it.type as? PsiType }.orEmpty() - val args = mapJList(constructorArguments) { convertLiteralExpression(getDefaultValue(it)) } - - convertField( - field, lightClass, lineMappings, packageFqName, treeMaker.NewClass( - /* enclosing = */ null, - /* typeArgs = */ JavacList.nil(), - /* lightClass = */ treeMaker.Ident(treeMaker.name(field.name)), - /* args = */ args, - /* def = */ null - ) - ) - } - - val fieldsPositions = mutableMapOf() - val fields = mapJList(lightClass.fields) { field -> - runUnless(field is PsiEnumConstant) { convertField(field, lightClass, lineMappings, packageFqName)?.also { - fieldsPositions[it] = MemberData(field.name, field.signature, lineMappings.getPosition(lightClass, field)) - } - } - } - - val methodsPositions = mutableMapOf() - val methods = mapJList(lightClass.methods) { method -> - if (isEnum && method.isSyntheticStaticEnumMethod()) { - return@mapJList null - } - - convertMethod(method, lightClass, lineMappings, packageFqName)?.also { - methodsPositions[it] = MemberData(method.name, method.signature, lineMappings.getPosition(lightClass, method)) - } - } - - val nestedClasses = mapJList(lightClass.innerClasses) { innerClass -> - convertClass(innerClass, lineMappings, packageFqName) - } - - lineMappings.registerClass(lightClass) - - val classPosition = lineMappings.getPosition(lightClass) - val sortedFields = JavacList.from(fields.sortedWith(MembersPositionComparator(classPosition, fieldsPositions))) - val sortedMethods = JavacList.from(methods.sortedWith(MembersPositionComparator(classPosition, methodsPositions))) - - return treeMaker.ClassDef( - modifiers, - treeMaker.name(simpleName), - classSignature.typeParameters, - classSignature.superClass.takeUnless { classSignature.superClassIsObject || lightClass.isEnum }, - classSignature.interfaces, - JavacList.from(enumValues + sortedFields + sortedMethods + nestedClasses) - ).keepKdocCommentsIfNecessary(lightClass) - } - - private fun PsiMethod.isSyntheticStaticEnumMethod(): Boolean { - if (!this.isStatic) return false - return when (name) { - StandardNames.ENUM_VALUES.asString() -> parameters.isEmpty() - StandardNames.ENUM_VALUE_OF.asString() -> (parameters.singleOrNull()?.type as? PsiClassType)?.qualifiedName == "java.lang.String" - else -> false - } - } - - private fun convertImports(file: KtFile, unresolvedQualifiers: UnresolvedQualifiersRecorder): JavacList { - if (unresolvedQualifiers.isEmpty()) return JavacList.nil() - - val imports = mutableListOf() - val importedShortNames = mutableSetOf() - - // We prefer ordinary imports over aliased ones. - val sortedImportDirectives = file.importDirectives.partition { it.aliasName == null }.run { first + second } - - loop@ for (importDirective in sortedImportDirectives) { - val acceptableByName = when { - importDirective.isAllUnder -> unresolvedQualifiers.simpleNames.isNotEmpty() - else -> { - val fqName = importDirective.importedFqName ?: continue - fqName.asString() in unresolvedQualifiers.qualifiedNames || fqName.shortName().identifier in unresolvedQualifiers.simpleNames - } - } - - if (!acceptableByName) continue - - val importedSymbols = with(analysisSession) { - val importedReference = importDirective.importedReference - ?.getCalleeExpressionIfAny() - ?.references - ?.firstOrNull() as? KtReference - importedReference?.resolveToSymbols().orEmpty() - } - - val isAllUnderClassifierImport = importDirective.isAllUnder && importedSymbols.any { it is KtClassOrObjectSymbol } - val isCallableImport = !importDirective.isAllUnder && importedSymbols.any { it is KtCallableSymbol } - val isEnumEntryImport = !importDirective.isAllUnder && importedSymbols.any { it is KtEnumEntrySymbol } - - if (isAllUnderClassifierImport || isCallableImport || isEnumEntryImport) continue - - - // Qualified name should be valid Java fq-name - val importedFqName = importDirective.importedFqName?.takeIf { it.pathSegments().size > 1 } ?: continue - if (!isValidQualifiedName(importedFqName)) continue - val importedExpr = treeMaker.FqName(importedFqName.asString()) - imports += if (importDirective.isAllUnder) { - treeMaker.Import(treeMaker.Select(importedExpr, treeMaker.nameTable.names.asterisk), false) - } else { - if (!importedShortNames.add(importedFqName.shortName().asString())) { - continue - } - - treeMaker.Import(importedExpr, false) - } - } - - return JavacList.from(imports) - } - - private fun convertMetadataAnnotation(metadata: Metadata): JCAnnotation { - val argumentsWithNames = mutableMapOf() - if (metadata.kind != 1) argumentsWithNames[KIND_FIELD_NAME] = metadata.kind - argumentsWithNames[METADATA_VERSION_FIELD_NAME] = (overriddenMetadataVersion?.toArray() ?: metadata.metadataVersion).toList() - if (metadata.data1.isNotEmpty()) argumentsWithNames[METADATA_DATA_FIELD_NAME] = metadata.data1.toList() - if (metadata.data2.isNotEmpty()) argumentsWithNames[METADATA_STRINGS_FIELD_NAME] = metadata.data2.toList() - if (metadata.extraString.isNotEmpty()) argumentsWithNames[METADATA_EXTRA_STRING_FIELD_NAME] = metadata.extraString - if (metadata.packageName.isNotEmpty()) argumentsWithNames[METADATA_PACKAGE_NAME_FIELD_NAME] = metadata.packageName - if (metadata.extraInt != 0) argumentsWithNames[METADATA_EXTRA_INT_FIELD_NAME] = metadata.extraInt - - val arguments = argumentsWithNames.map { (name, value) -> - val jValue = convertLiteralExpression(value) - treeMaker.Assign(treeMaker.SimpleName(name), jValue) - } - return treeMaker.Annotation(treeMaker.FqName(Metadata::class.java.canonicalName), JavacList.from(arguments)) - } - - context(UnresolvedQualifiersRecorder) - private fun convertAnnotation( - containingClass: PsiClass, - annotation: PsiAnnotation, - packageFqName: String - ): JCAnnotation? { - fun collectNameParts(node: ASTNode, builder: StringBuilder, takeNext: Boolean) { - when (node) { - is LeafPsiElement -> { - when (node.elementType) { - KtTokens.IDENTIFIER, KtTokens.DOT -> builder.append((node as ASTNode).text) - } - if (takeNext) node.treeNext?.let { collectNameParts(it, builder, true) } - } - else -> - if (node.elementType is KtAnnotationEntryElementType) { - collectNameParts(node.firstChildNode.treeNext, builder, false) - } else { - collectNameParts(node.firstChildNode, builder, true) - if (takeNext) node.treeNext?.let { collectNameParts(it, builder, true) } - } - } - } - - fun qualifiedName(node: ASTNode): String = buildString { - collectNameParts(node, this, false) - } - - - val rawQualifiedName = when (annotation.qualifiedName) { - // A temporary fix for KT-60482 - "" -> (annotation as? KtLightElement<*, *>)?.kotlinOrigin?.node?.let { qualifiedName(it) } - ?.also { recordUnresolvedQualifier(it) } - else -> annotation.qualifiedName - } ?: return null - - val fqName = treeMaker.getQualifiedName(rawQualifiedName) - - if (BLACKLISTED_ANNOTATIONS.any { fqName.startsWith(it) }) return null - if (stripMetadata && fqName == KOTLIN_METADATA_ANNOTATION) return null - - - val annotationFqName = annotation.resolveAnnotationType()?.defaultType.convertAndRecordErrors { - val useSimpleName = '.' in fqName && fqName.substringBeforeLast('.', "") == packageFqName - - when { - useSimpleName -> treeMaker.FqName(fqName.substring(packageFqName.length + 1)) - else -> treeMaker.FqName(fqName) - } - } - - val values = mapJList<_, JCExpression>(annotation.parameterList.attributes) { - val name = it.name?.takeIf { name -> isValidIdentifier(name) } ?: return@mapJList null - val value = it.value - val expr = if (value == null) { - ((it as? KtLightElementBase)?.kotlinOrigin as? KtDotQualifiedExpression)?.let { convertDotQualifiedExpression(it) } - ?: return@mapJList null - } else { - convertPsiAnnotationMemberValue(containingClass, value, packageFqName) - } - treeMaker.Assign(treeMaker.SimpleName(name), expr) - } - - return treeMaker.Annotation(annotationFqName, values) - } - - private fun convertDotQualifiedExpression(dotQualifiedExpression: KtDotQualifiedExpression): JCExpression? { - val qualifier = dotQualifiedExpression.lastChild as? KtNameReferenceExpression ?: return null - val name = qualifier.text.takeIf { isValidIdentifier(it) } ?: "InvalidFieldName" - val lhs = when(val left = dotQualifiedExpression.firstChild) { - is KtNameReferenceExpression -> treeMaker.SimpleName(left.getReferencedName()) - is KtDotQualifiedExpression -> convertDotQualifiedExpression(left) ?: return null - else -> return null - } - return treeMaker.Select(lhs, treeMaker.name(name)) - } - - context(UnresolvedQualifiersRecorder) - private fun convertPsiAnnotationMemberValue( - containingClass: PsiClass, - value: PsiAnnotationMemberValue, - packageFqName: String, - ): JCExpression? { - return when (value) { - is PsiArrayInitializerMemberValue -> { - val arguments = mapJList(value.initializers) { - convertPsiAnnotationMemberValue(containingClass, it, packageFqName) - } - treeMaker.NewArray(null, null, arguments) - } - - is PsiLiteral -> convertLiteralExpression(value.value) - is PsiClassObjectAccessExpression -> { - val type = value.operand.type - checkIfValidTypeName(containingClass, type) - treeMaker.Select(treeMaker.SimpleName(type.qualifiedName), treeMaker.name("class")) - } - is PsiAnnotation -> convertAnnotation(containingClass, value, packageFqName) - else -> treeMaker.SimpleName(value.text) - } - } - - context(UnresolvedQualifiersRecorder) - private fun convertModifiers( - containingClass: PsiClass, - access: Long, - kind: ElementKind, - packageFqName: String, - allAnnotations: List, - metadata: Metadata?, - excludeNullabilityAnnotations: Boolean = false, - ): JCModifiers { - var seenDeprecated = false - fun convertAndAdd(list: JavacList, annotation: PsiAnnotation): JavacList { - seenDeprecated = seenDeprecated or annotation.hasQualifiedName("java.lang.Deprecated") - if (excludeNullabilityAnnotations && - (annotation.hasQualifiedName("org.jetbrains.annotations.NotNull") || annotation.hasQualifiedName("org.jetbrains.annotations.Nullable")) - ) return list - val annotationTree = convertAnnotation(containingClass, annotation, packageFqName) ?: return list - return list.prepend(annotationTree) - } - - var annotations = allAnnotations.reversed().fold(JavacList.nil(), ::convertAndAdd) - - if (!seenDeprecated && isDeprecated(access)) { - val type = treeMaker.RawType(Type.getType(java.lang.Deprecated::class.java)) - annotations = annotations.append(treeMaker.Annotation(type, JavacList.nil())) - } - if (metadata != null) { - annotations = annotations.prepend(convertMetadataAnnotation(metadata)) - } - - val flags = when (kind) { - ElementKind.ENUM -> access and CLASS_MODIFIERS and Opcodes.ACC_ABSTRACT.inv().toLong() - ElementKind.CLASS -> access and CLASS_MODIFIERS - ElementKind.METHOD -> access and METHOD_MODIFIERS - ElementKind.FIELD -> access and FIELD_MODIFIERS - ElementKind.PARAMETER -> access and PARAMETER_MODIFIERS - else -> throw IllegalArgumentException("Invalid element kind: $kind") - } - return treeMaker.Modifiers(flags, annotations) - } - - class KaptStub(val file: JCCompilationUnit, private val kaptMetadata: ByteArray) { - fun writeMetadataIfNeeded(forSource: File): File { - val metadataFile = File( - forSource.parentFile, - forSource.nameWithoutExtension + KaptStubLineInformation.KAPT_METADATA_EXTENSION - ) - - metadataFile.writeBytes(kaptMetadata) - return metadataFile - } - } - - context(UnresolvedQualifiersRecorder) - private fun convertField( - field: PsiField, - containingClass: PsiClass, - lineMappings: Kapt4LineMappingCollector, - packageFqName: String, - explicitInitializer: JCExpression? = null - ): JCVariableDecl? { - val fieldAnnotations = field.annotations.asList() - - if (isIgnored(fieldAnnotations)) return null - - val access = field.accessFlags - val modifiers = convertModifiers( - containingClass, - access, ElementKind.FIELD, packageFqName, - fieldAnnotations, - metadata = null - ) - - val name = field.name - if (!isValidIdentifier(name)) return null - - val type = field.type - - if (!checkIfValidTypeName(containingClass, type)) return null - - // Enum type must be an identifier (Javac requirement) - val typeExpression = if (isEnum(access)) { - treeMaker.SimpleName(treeMaker.getQualifiedName(type as PsiClassType).substringAfterLast('.')) - } else { - type.convertAndRecordErrors() - } - - lineMappings.registerField(containingClass, field) - val skip = field.navigationElement is KtParameter && !dumpDefaultParameterValues - val initializer = - explicitInitializer ?: convertPropertyInitializer(if (skip) null else field.initializer, field.type, field.isFinal) - return treeMaker.VarDef(modifiers, treeMaker.name(name), typeExpression, initializer).keepKdocCommentsIfNecessary(field) - } - - private fun convertPropertyInitializer(propertyInitializer: PsiExpression?, type: PsiType, usedDefault: Boolean): JCExpression? { - if (propertyInitializer != null || usedDefault) { - return when (propertyInitializer) { - is PsiLiteralExpression -> { - val rawValue = propertyInitializer.value - val rawNumberValue = rawValue as? Number - val actualValue = when (type) { - PsiType.BYTE -> rawNumberValue?.toByte() - PsiType.SHORT -> rawNumberValue?.toShort() - PsiType.INT -> rawNumberValue?.toInt() - PsiType.LONG -> rawNumberValue?.toLong() - PsiType.FLOAT -> rawNumberValue?.toFloat() - PsiType.DOUBLE -> rawNumberValue?.toDouble() - else -> null - } ?: rawValue - convertValueOfPrimitiveTypeOrString(actualValue) - } - is PsiPrefixExpression -> { - assert(propertyInitializer.operationSign.tokenType == JavaTokenType.MINUS) - val operand = convertPropertyInitializer(propertyInitializer.operand, type, usedDefault) - if (operand.toString().startsWith("-")) operand // overflow - else treeMaker.Unary(Tag.NEG, operand) - } - is PsiBinaryExpression -> { - assert(propertyInitializer.operationSign.tokenType == JavaTokenType.DIV) - treeMaker.Binary( - Tag.DIV, - convertPropertyInitializer(propertyInitializer.lOperand, type, false), - convertPropertyInitializer(propertyInitializer.rOperand, type, false) - ) - } - is PsiReferenceExpression -> - when (val resolved = propertyInitializer.resolve()) { - is PsiEnumConstant -> - treeMaker.FqName(resolved.containingClass!!.qualifiedName + "." + resolved.name) - else -> null - } - is PsiArrayInitializerExpression -> - treeMaker.NewArray( - null, JavacList.nil(), - mapJList(propertyInitializer.initializers) { convertPropertyInitializer(it, type.deepComponentType, false) } - ) - else -> convertLiteralExpression(getDefaultValue(type)) - } - } - - return null - } - - private fun convertLiteralExpression(value: Any?): JCExpression { - fun convertDeeper(value: Any?) = convertLiteralExpression(value) - - convertValueOfPrimitiveTypeOrString(value)?.let { return it } - - return when (value) { - null -> treeMaker.Literal(TypeTag.BOT, null) - - is ByteArray -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value.asIterable(), ::convertDeeper)) - is BooleanArray -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value.asIterable(), ::convertDeeper)) - is CharArray -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value.asIterable(), ::convertDeeper)) - is ShortArray -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value.asIterable(), ::convertDeeper)) - is IntArray -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value.asIterable(), ::convertDeeper)) - is LongArray -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value.asIterable(), ::convertDeeper)) - is FloatArray -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value.asIterable(), ::convertDeeper)) - is DoubleArray -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value.asIterable(), ::convertDeeper)) - 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).takeIf { isValidIdentifier(it) } ?: run { - compiler.log.report(kaptError("'${value[1]}' is an invalid Java enum value name")) - "InvalidFieldName" - } - - treeMaker.Select(treeMaker.RawType(enumType), treeMaker.name(valueName)) - } - - is List<*> -> treeMaker.NewArray(null, JavacList.nil(), mapJList(value, ::convertDeeper)) - - else -> throw IllegalArgumentException("Illegal literal expression value: $value (${value::class.java.canonicalName})") - } - } - - - private fun getDefaultValue(type: PsiType): Any? = when (type) { - PsiType.BYTE -> 0 - PsiType.BOOLEAN -> false - PsiType.CHAR -> '\u0000' - PsiType.SHORT -> 0 - PsiType.INT -> 0 - PsiType.LONG -> 0L - PsiType.FLOAT -> 0.0F - PsiType.DOUBLE -> 0.0 - else -> null - } - - private fun convertValueOfPrimitiveTypeOrString(value: Any?): JCExpression? { - fun specialFpValueNumerator(value: Double): Double = if (value.isNaN()) 0.0 else 1.0 * value.sign - val convertedValue = when (value) { - is Char -> treeMaker.Literal(TypeTag.CHAR, value.code) - is Byte -> treeMaker.TypeCast(treeMaker.TypeIdent(TypeTag.BYTE), treeMaker.Literal(TypeTag.INT, value.toInt())) - is Short -> treeMaker.TypeCast(treeMaker.TypeIdent(TypeTag.SHORT), treeMaker.Literal(TypeTag.INT, value.toInt())) - is Boolean, is Int, is Long, is String -> treeMaker.Literal(value) - is Float -> when { - value.isFinite() -> treeMaker.Literal(value) - else -> treeMaker.Binary( - Tag.DIV, - treeMaker.Literal(specialFpValueNumerator(value.toDouble()).toFloat()), - treeMaker.Literal(0.0F) - ) - } - - is Double -> when { - value.isFinite() -> treeMaker.Literal(value) - else -> treeMaker.Binary(Tag.DIV, treeMaker.Literal(specialFpValueNumerator(value)), treeMaker.Literal(0.0)) - } - null -> treeMaker.Literal(TypeTag.BOT, null) - else -> null - } - - return convertedValue - } - - context(UnresolvedQualifiersRecorder) - private fun convertMethod( - method: PsiMethod, - containingClass: PsiClass, - lineMappings: Kapt4LineMappingCollector, - packageFqName: String, - ): JCMethodDecl? { - if (isIgnored(method.annotations.asList())) return null - - val isConstructor = method.isConstructor - - val name = method.name - if (!isConstructor && !isValidIdentifier(name)) return null - val returnType = method.returnType ?: PsiType.VOID - val modifiers = convertModifiers( - containingClass, - if (containingClass.isEnum && isConstructor) - (method.accessFlags and VISIBILITY_MODIFIERS.inv()) - else - method.accessFlags, - ElementKind.METHOD, - packageFqName, - method.annotations.toList(), - metadata = null, - excludeNullabilityAnnotations = returnType == PsiType.VOID - ) - - if (method.hasModifierProperty(PsiModifier.DEFAULT)) { - modifiers.flags = modifiers.flags or Flags.DEFAULT - } - - val parametersInfo = method.getParametersInfo() - - if (!checkIfValidTypeName(containingClass, returnType) - || parametersInfo.any { !checkIfValidTypeName(containingClass, it.type) } - ) { - return null - } - - @Suppress("NAME_SHADOWING") - val jParameters = mapJListIndexed(parametersInfo) { index, info -> - val lastParameter = index == parametersInfo.lastIndex - val isArrayType = info.type is PsiArrayType - - val varargs = if (lastParameter && isArrayType && method.isVarArgs) Flags.VARARGS else 0L - val modifiers = convertModifiers( - containingClass, - Flags.PARAMETER or varargs, // Kapt never marked method parameters as "final" - ElementKind.PARAMETER, - packageFqName, - info.annotations, - metadata = null - ) - - val defaultName = info.name - val name = when { - isValidIdentifier(defaultName) -> defaultName - defaultName == SpecialNames.IMPLICIT_SET_PARAMETER.asString() -> "p0" - else -> "p${index}_${info.name.hashCode().ushr(1)}" - } - val type = info.type.convertAndRecordErrors() - treeMaker.VarDef(modifiers, treeMaker.name(name), type, null) - } - val jTypeParameters = mapJList(method.typeParameters) { convertTypeParameter(it) } - val jExceptionTypes = mapJList(method.throwsTypes) { treeMaker.TypeWithArguments(it as PsiType) } - val jReturnType = runUnless(isConstructor) { - returnType.convertAndRecordErrors() - } - - val defaultValue = (method as? PsiAnnotationMethod)?.defaultValue?.let { - convertPsiAnnotationMemberValue(containingClass, it, packageFqName) - } - - val body = if (defaultValue != null) { - null - } else if (method.isAbstract or (modifiers.flags and Flags.ABSTRACT.toLong() != 0L)) { - null - } else if (isConstructor && containingClass.isEnum) { - treeMaker.Block(0, JavacList.nil()) - } else if (isConstructor) { - val superConstructor = containingClass.superClass?.constructors?.firstOrNull { !it.isPrivate } - val superClassConstructorCall = if (superConstructor != null) { - val args = mapJList(superConstructor.parameterList.parameters) { param -> - convertLiteralExpression(getDefaultValue(param.type)) - } - val call = treeMaker.Apply(JavacList.nil(), treeMaker.SimpleName("super"), args) - JavacList.of(treeMaker.Exec(call)) - } else { - JavacList.nil() - } - treeMaker.Block(0, superClassConstructorCall) - } else if (returnType == PsiType.VOID) { - treeMaker.Block(0, JavacList.nil()) - } else { - val returnStatement = treeMaker.Return(convertLiteralExpression(getDefaultValue(returnType))) - treeMaker.Block(0, JavacList.of(returnStatement)) - } - - lineMappings.registerMethod(containingClass, method) - - return treeMaker.MethodDef( - modifiers, treeMaker.name(name), jReturnType, jTypeParameters, - jParameters, jExceptionTypes, - body, defaultValue - ).keepSignature(lineMappings, method).keepKdocCommentsIfNecessary(method) - } - - private fun JCMethodDecl.keepSignature(lineMappings: Kapt4LineMappingCollector, method: PsiMethod): JCMethodDecl { - lineMappings.registerSignature(this, method) - return this - } - - private fun T.keepKdocCommentsIfNecessary(element: PsiElement): T { - kdocCommentKeeper?.saveKDocComment(this, element) - return this - } - - private fun isIgnored(annotations: List?): Boolean { - val kaptIgnoredAnnotationFqName = KaptIgnored::class.java.canonicalName - return annotations?.any { it.hasQualifiedName(kaptIgnoredAnnotationFqName) } ?: false - } - - context(UnresolvedQualifiersRecorder) - private fun checkIfValidTypeName( - containingClass: PsiClass, - type: PsiType - ): Boolean { - when (type) { - is PsiArrayType -> return checkIfValidTypeName(containingClass, type.componentType) - is PsiPrimitiveType -> return true - } - - - val internalName = type.qualifiedName - // Ignore type names with Java keywords in it - if (internalName.split('/', '.').any { it in JAVA_KEYWORDS }) { - if (strictMode) { - reportKaptError( - "Can't generate a stub for '${internalName}'.", - "Type name '${type.qualifiedName}' contains a Java keyword." - ) - } - - return false - } - - val clazz = type.resolvedClass ?: return true - - if (doesInnerClassNameConflictWithOuter(clazz)) { - if (strictMode) { - reportKaptError( - "Can't generate a stub for '${clazz.qualifiedNameWithDollars}'.", - "Its name '${clazz.name}' is the same as one of the outer class names.", - "Java forbids it. Please change one of the class names." - ) - } - - return false - } - - reportIfIllegalTypeUsage(containingClass, type) - - return true - } - - private fun findContainingClassNode(clazz: PsiClass): PsiClass? { - return clazz.parent as? PsiClass - } - - // Java forbids outer and inner class names to be the same. Check if the names are different - private tailrec fun doesInnerClassNameConflictWithOuter( - clazz: PsiClass, - outerClass: PsiClass? = findContainingClassNode(clazz) - ): Boolean { - if (outerClass == null) return false - if (treeMaker.getSimpleName(clazz) == treeMaker.getSimpleName(outerClass)) return true - // Try to find the containing class for outerClassNode (to check the whole tree recursively) - val containingClassForOuterClass = findContainingClassNode(outerClass) ?: return false - return doesInnerClassNameConflictWithOuter(clazz, containingClassForOuterClass) - } - - context(UnresolvedQualifiersRecorder) - private fun reportIfIllegalTypeUsage( - containingClass: PsiClass, - type: PsiType - ) { - val typeName = type.simpleNameOrNull ?: return - if (typeName !in reportedTypes && typeName in importsFromRoot) { - reportedTypes += typeName - val msg = "${containingClass.qualifiedName}: Can't reference type '${typeName}' from default package in Java stub." - if (strictMode) reportKaptError(msg) - else logger.warn(msg) - } - } - - context(UnresolvedQualifiersRecorder) - @OptIn(ExperimentalContracts::class) - private inline fun PsiType?.convertAndRecordErrors( - ifNonError: () -> JCExpression = { treeMaker.TypeWithArguments(this!!) } - ): JCExpression { - contract { - callsInPlace(ifNonError, InvocationKind.EXACTLY_ONCE) - } - this?.recordErrorTypes() - return ifNonError() - } - - context(UnresolvedQualifiersRecorder) - private fun PsiType.recordErrorTypes() { - if (this is PsiEllipsisType) { - this.componentType.recordErrorTypes() - return - } - if (qualifiedNameOrNull == null) { - recordUnresolvedQualifier(qualifiedName) - } - when (this) { - is PsiClassType -> typeArguments().forEach { (it as? PsiType)?.recordErrorTypes() } - is PsiArrayType -> componentType.recordErrorTypes() - } - } - - private fun isValidQualifiedName(name: FqName) = name.pathSegments().all { isValidIdentifier(it.asString()) } - - private fun isValidIdentifier(name: String): Boolean { - if (name in JAVA_KEYWORDS) return false - - return !(name.isEmpty() - || !Character.isJavaIdentifierStart(name[0]) - || name.drop(1).any { !Character.isJavaIdentifierPart(it) }) - } - - private class ClassGenericSignature( - val typeParameters: JavacList, - val superClass: JCExpression, - val interfaces: JavacList, - val superClassIsObject: Boolean - ) - - context(UnresolvedQualifiersRecorder) - private fun parseClassSignature(psiClass: PsiClass): ClassGenericSignature { - val superClasses = mutableListOf() - val superInterfaces = mutableListOf() - - val superPsiClasses = psiClass.extendsListTypes.toList() - val superPsiInterfaces = psiClass.implementsListTypes.toList() - - fun addSuperType(superType: PsiClassType, destination: MutableList) { - if (psiClass.isAnnotationType && superType.qualifiedName == "java.lang.annotation.Annotation") return - destination += superType.convertAndRecordErrors() - } - - var superClassIsObject = false - - superPsiClasses.forEach { - addSuperType(it, superClasses) - superClassIsObject = superClassIsObject || it.qualifiedNameOrNull == "java.lang.Object" - } - for (superInterface in superPsiInterfaces) { - if (superInterface.qualifiedName.startsWith("kotlin.collections.")) continue - addSuperType(superInterface, superInterfaces) - } - - val jcTypeParameters = mapJList(psiClass.typeParameters) { convertTypeParameter(it) } - val jcSuperClass = superClasses.firstOrNull().takeUnless { psiClass.isInterface } ?: createJavaLangObjectType().also { - superClassIsObject = true - } - val jcInterfaces = JavacList.from(if (psiClass.isInterface) superClasses else superInterfaces) - return ClassGenericSignature(jcTypeParameters, jcSuperClass, jcInterfaces, superClassIsObject) - } - - private fun createJavaLangObjectType(): JCExpression { - return treeMaker.FqName("java.lang.Object") - } - - context(UnresolvedQualifiersRecorder) - private fun convertTypeParameter(typeParameter: PsiTypeParameter): JCTypeParameter { - val classBounds = mutableListOf() - val interfaceBounds = mutableListOf() - - val bounds = typeParameter.bounds - for (bound in bounds) { - val boundType = bound as? PsiType ?: continue - val jBound = boundType.convertAndRecordErrors() - if (boundType.resolvedClass?.isInterface == false) { - classBounds += jBound - } else { - interfaceBounds += jBound - } - } - if (classBounds.isEmpty() && interfaceBounds.isEmpty()) { - classBounds += createJavaLangObjectType() - } - return treeMaker.TypeParameter(treeMaker.name(typeParameter.name!!), JavacList.from(classBounds + interfaceBounds)) - } - - private class UnresolvedQualifiersRecorder(ktFiles: Iterable) { - val importsFromRoot: Set by lazy { - val importsFromRoot = - ktFiles - .flatMap { it.importDirectives } - .filter { !it.isAllUnder } - .mapNotNull { im -> im.importPath?.fqName?.takeIf { it.isOneSegmentFQN() } } - importsFromRoot.mapTo(mutableSetOf()) { it.asString() } - } - - private val _qualifiedNames = mutableSetOf() - private val _simpleNames = mutableSetOf() - val reportedTypes = mutableSetOf() - - val qualifiedNames: Set - get() = _qualifiedNames - val simpleNames: Set - get() = _simpleNames - - fun isEmpty(): Boolean { - return simpleNames.isEmpty() - } - - fun recordUnresolvedQualifier(qualifier: String) { - val separated = qualifier.split(".") - if (separated.size > 1) { - _qualifiedNames += qualifier - _simpleNames += separated.first() - } else { - _simpleNames += qualifier - } - } - } - - private fun calculateMetadata(lightClass: PsiClass): Metadata? { - if (stripMetadata) return null - - return with(analysisSession) { - when (lightClass) { - is KtLightClassForFacade -> - if (lightClass.multiFileClass) - lightClass.qualifiedName?.let { createMultifileClassMetadata(lightClass, it) } - else - lightClass.files.singleOrNull()?.calculateMetadata(elementMapping(lightClass)) - is SymbolLightClassForNamedClassLike -> - lightClass.kotlinOrigin?.calculateMetadata(elementMapping(lightClass)) - else -> null - } - } - } - - private fun createMultifileClassMetadata(lightClass: KtLightClassForFacade, qualifiedName: String): Metadata = - Metadata( - kind = KotlinClassHeader.Kind.MULTIFILE_CLASS.id, - metadataVersion = LanguageVersion.KOTLIN_2_0.toMetadataVersion().toArray(), - data1 = lightClass.files.map { - JvmFileClassUtil.manglePartName(qualifiedName.replace('.', '/'), it.name) - }.toTypedArray(), - extraInt = METADATA_JVM_IR_FLAG or METADATA_JVM_IR_STABLE_ABI_FLAG - ) - - private fun elementMapping(lightClass: PsiClass): Multimap = - HashMultimap.create().apply { - (lightClass.methods.asSequence() + lightClass.fields.asSequence() + lightClass.constructors.asSequence()).forEach { - put((it as KtLightElement<*, *>).kotlinOrigin, it) - } - } -} diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4TreeMaker.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4TreeMaker.kt deleted file mode 100644 index a24115d7751..00000000000 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/Kapt4TreeMaker.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.kapt4 - -import com.intellij.psi.* -import com.sun.tools.javac.code.BoundKind -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.Name -import com.sun.tools.javac.util.Names -import org.jetbrains.kotlin.builtins.StandardNames -import org.jetbrains.kotlin.codegen.AsmUtil -import org.jetbrains.org.objectweb.asm.Type -import org.jetbrains.org.objectweb.asm.Type.* - -internal class Kapt4TreeMaker( - context: Context -) : TreeMaker(context) { - val nameTable: Name.Table = Names.instance(context).table - - @Suppress("FunctionName") - fun RawType(type: Type): JCTree.JCExpression { - convertBuiltinType(type)?.let { return it } - if (type.sort == ARRAY) { - return TypeArray(RawType(AsmUtil.correctElementType(type))) - } - return FqName(type.internalName) - } - - @Suppress("FunctionName") - private fun RawType(type: PsiType): JCTree.JCExpression { - return when (type) { - is PsiArrayType -> TypeArray(RawType(type.componentType)) - is PsiWildcardType -> Wildcard(TypeBoundKind(BoundKind.UNBOUND), null) - else -> FqName(type.qualifiedName) - } - } - - @Suppress("FunctionName") - fun TypeWithArguments(type: PsiType): JCTree.JCExpression { - return when (type) { - is PsiArrayType -> TypeArray(TypeWithArguments(type.componentType)) - is PsiClassType -> { - val correctedType = if (isErroneous(type)) type.rawType() else type - SimpleName(correctedType.canonicalText.replace('$', '.')) - } // TODO: Produce a proper expression, see KT-60821 - is PsiWildcardType -> { - val argumentType = type.bound?.let { TypeWithArguments(it) } - when { - type.isExtends -> Wildcard(TypeBoundKind(BoundKind.EXTENDS), argumentType) - type.isSuper -> Wildcard(TypeBoundKind(BoundKind.SUPER), argumentType) - else -> Wildcard(TypeBoundKind(BoundKind.UNBOUND), argumentType) - } - } - - else -> RawType(type) - } - } - - private fun isErroneous(type: PsiType): Boolean { - if (type.canonicalText == StandardNames.NON_EXISTENT_CLASS.asString()) return true - if (type is PsiClassType) return type.parameters.any { isErroneous(it) } - return false - } - - @Suppress("FunctionName") - fun FqName(internalOrFqName: String): JCTree.JCExpression { - val path = getQualifiedName(internalOrFqName).convertSpecialFqName().split('.') - assert(path.isNotEmpty()) - return FqName(path) - } - - @Suppress("FunctionName") - private fun FqName(path: List): JCTree.JCExpression { - if (path.size == 1) return SimpleName(path.single()) - - var expr = Select(SimpleName(path[0]), name(path[1])) - for (index in 2..path.lastIndex) { - expr = Select(expr, name(path[index])) - } - return expr - } - - fun getQualifiedName(type: PsiClassType): String { - val klass = type.resolve() ?: return getQualifiedName(type.qualifiedName) - return getQualifiedName(klass) - } - - private fun getQualifiedName(type: PsiClass): String = getQualifiedName(type.qualifiedName!!) - - fun getSimpleName(clazz: PsiClass): String = clazz.name!! - - fun getQualifiedName(internalName: String): String { - val nameWithDots = internalName.replace('/', '.') - // This is a top-level class - if ('$' !in nameWithDots) return nameWithDots - - return nameWithDots.replace('$', '.') - } - - private fun String.convertSpecialFqName(): String { - // Hard-coded in ImplementationBodyCodegen, KOTLIN_MARKER_INTERFACES - if (this == "kotlin.jvm.internal.markers.KMutableMap\$Entry") { - return replace('$', '.') - } - - return this - } - - private 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) - } - - @Suppress("FunctionName") - fun SimpleName(name: String): JCTree.JCExpression = Ident(name(name)) - - fun name(name: String): Name = nameTable.fromString(name) - - companion object { - internal fun preRegister(context: Context) { - context.put(treeMakerKey, Context.Factory { Kapt4TreeMaker(it) }) - } - } -} \ No newline at end of file diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/StubGenerator.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/StubGenerator.kt new file mode 100644 index 00000000000..4ff9d56accf --- /dev/null +++ b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/StubGenerator.kt @@ -0,0 +1,782 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("UnstableApiUsage") + +package org.jetbrains.kotlin.kapt4 + +import com.google.common.collect.HashMultimap +import com.google.common.collect.Multimap +import com.intellij.lang.ASTNode +import com.intellij.psi.* +import com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtEnumEntrySymbol +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade +import org.jetbrains.kotlin.asJava.elements.* +import org.jetbrains.kotlin.asJava.findFacadeClass +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil +import org.jetbrains.kotlin.idea.references.KtReference +import org.jetbrains.kotlin.kapt3.base.KaptFlag +import org.jetbrains.kotlin.kapt3.base.stubs.KaptStubLineInformation +import org.jetbrains.kotlin.kapt3.stubs.MemberData +import org.jetbrains.kotlin.kapt3.stubs.extractComment +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.light.classes.symbol.classes.SymbolLightClassForNamedClassLike +import org.jetbrains.kotlin.load.java.JvmAnnotationNames.* +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader +import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion +import org.jetbrains.kotlin.name.* +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.parameterIndex +import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny +import org.jetbrains.kotlin.utils.toMetadataVersion +import org.jetbrains.kotlin.kapt3.base.KaptOptions +import org.jetbrains.kotlin.kapt3.stubs.MembersPositionComparator +import org.jetbrains.kotlin.psi.psiUtil.children +import org.jetbrains.kotlin.utils.Printer +import java.io.File + +internal fun generateStubs( + files: List, + options: KaptOptions, + onError: (messages: String) -> Unit, + analysisSession: KtAnalysisSession, + overriddenMetadataVersion: BinaryVersion? = null, + metadataRenderer: (Printer.(Metadata) -> Unit)? = null +): Map = + StubGenerator(files, options, onError, analysisSession, metadataRenderer, overriddenMetadataVersion).generateStubs() + +class KaptStub(val source: String, val kaptMetadata: ByteArray) { + fun writeMetadata(forSource: File) { + File(forSource.parentFile, forSource.nameWithoutExtension + KaptStubLineInformation.KAPT_METADATA_EXTENSION) + .apply { writeBytes(kaptMetadata) } + } +} + +private class StubGenerator( + private val files: List, + options: KaptOptions, + private val onError: (String) -> Unit, + private val analysisSession: KtAnalysisSession, + private val metadataRenderer: (Printer.(Metadata) -> Unit)? = null, + private val overriddenMetadataVersion: BinaryVersion? = null, +) { + private val strictMode = options[KaptFlag.STRICT] + private val stripMetadata = options[KaptFlag.STRIP_METADATA] + private val keepKdocComments = options[KaptFlag.KEEP_KDOC_COMMENTS_IN_STUBS] + private val dumpDefaultParameterValues = options[KaptFlag.DUMP_DEFAULT_PARAMETER_VALUES] + + + fun generateStubs(): Map = + buildSet { + files.flatMapTo(this) { file -> + file.children.filterIsInstance().mapNotNull { + it.toLightClass() + } + } + files.mapNotNullTo(this) { ktFile -> ktFile.findFacadeClass() } + }.associateWith { + FileGenerator(it).generateStub() + } + + + private inner class FileGenerator(private val topLevelClass: KtLightClass) { + private val packageName = (topLevelClass.parent as PsiJavaFile).packageName + private val lineMappings = Kapt4LineMappingCollector() + private val ktFiles = when (topLevelClass) { + is KtLightClassForFacade -> topLevelClass.files + else -> listOfNotNull(topLevelClass.kotlinOrigin?.containingKtFile) + } + private val importsFromRoot: Set by lazy { + ktFiles.flatMap { it.importDirectives } + .filter { !it.isAllUnder } + .mapNotNull { im -> im.importPath?.fqName?.takeIf { it.isOneSegmentFQN() }?.asString() } + .toSet() + } + + private val unresolvedQualifiedNames = mutableSetOf() + private val unresolvedSimpleNames = mutableSetOf() + private val reportedTypes = mutableSetOf() + + private fun recordUnresolvedQualifier(qualifier: String) { + val separated = qualifier.split(".") + if (separated.size > 1) { + unresolvedQualifiedNames += qualifier + unresolvedSimpleNames += separated.first() + } else { + unresolvedSimpleNames += qualifier + } + } + + fun generateStub(): KaptStub? { + val ktFiles = when (topLevelClass) { + is KtLightClassForFacade -> topLevelClass.files + else -> listOfNotNull(topLevelClass.kotlinOrigin?.containingKtFile) + } + + val classBody = with(ClassGenerator(topLevelClass)) { + printToString { printClass() } + } + + if (classBody.isEmpty()) return null + + val stub = printToString { + if (packageName.isNotEmpty()) { + printWithNoIndent("package ", packageName, ";\n\n") + } + ktFiles.forEach { printImports(it) } + printWithNoIndent(classBody) + } + + return KaptStub(stub, lineMappings.serialize()) + } + + private fun Printer.printImports(file: KtFile) { + if (unresolvedSimpleNames.isEmpty()) return + + val importedShortNames = mutableSetOf() + + // We prefer ordinary imports over aliased ones. + val sortedImportDirectives = file.importDirectives.partition { it.aliasName == null }.run { first + second } + + for (importDirective in sortedImportDirectives) { + val acceptableByName = when { + importDirective.isAllUnder -> unresolvedSimpleNames.isNotEmpty() + else -> { + val fqName = importDirective.importedFqName ?: continue + fqName.asString() in unresolvedQualifiedNames || fqName.shortName().identifier in unresolvedSimpleNames + } + } + + if (!acceptableByName) continue + + val importedSymbols = with(analysisSession) { + val importedReference = importDirective.importedReference + ?.getCalleeExpressionIfAny() + ?.references + ?.firstOrNull() as? KtReference + importedReference?.resolveToSymbols().orEmpty() + } + + val isAllUnderClassifierImport = importDirective.isAllUnder && importedSymbols.any { it is KtClassOrObjectSymbol } + val isCallableImport = !importDirective.isAllUnder && importedSymbols.any { it is KtCallableSymbol } + val isEnumEntryImport = !importDirective.isAllUnder && importedSymbols.any { it is KtEnumEntrySymbol } + + if (isAllUnderClassifierImport || isCallableImport || isEnumEntryImport) continue + + // Qualified name should be valid Java fq-name + val importedFqName = importDirective.importedFqName?.takeIf { it.pathSegments().size > 1 } ?: continue + if (!isValidQualifiedName(importedFqName)) continue + printWithNoIndent("import ") + when { + importDirective.isAllUnder -> printWithNoIndent(importedFqName.asString(), ".*") + importedShortNames.add(importedFqName.shortName().asString()) -> printWithNoIndent(importedFqName) + } + printlnWithNoIndent(";") + } + } + + private inner class ClassGenerator(private val psiClass: PsiClass) { + fun Printer.printClass() { + val simpleName = psiClass.name ?: return + if (!isValidIdentifier(simpleName)) return + if (!checkIfValidTypeName(psiClass.defaultType)) return + lineMappings.registerClass(psiClass) + + printComment(psiClass) + + val classWord = when { + psiClass.isAnnotationType -> "@interface" + psiClass.isInterface -> "interface" + psiClass.isEnum -> "enum" + else -> "class" + } + calculateMetadata(psiClass)?.let { printMetadata(it) } + printIndent() + printModifiers(psiClass) + printWithNoIndent(classWord, " ", simpleName) + printTypeParams(psiClass.typeParameters) + + psiClass.extendsList + ?.referencedTypes + ?.asList() + ?.let { if (!psiClass.isInterface) it.take(1) else it } + ?.takeIf { it.isNotEmpty() } + ?.let { superClasses -> + printWithNoIndent(" extends ") + superClasses.forEachIndexed { index, type -> + if (index > 0) printWithNoIndent(", ") + printType(type) + } + } + + psiClass.implementsList + ?.referencedTypes + ?.filterNot { it.canonicalText.startsWith("kotlin.collections.") } + ?.takeIf { it.isNotEmpty() } + ?.let { interfaces -> + printWithNoIndent(" implements ") + interfaces.forEachIndexed { index, type -> + if (index > 0) printWithNoIndent(", ") + printType(type) + } + } + printlnWithNoIndent("{") + pushIndent() + + if (psiClass.isEnum) { + val values = psiClass.fields + .filterIsInstance() + .filter { isValidIdentifier(it.name) } + values.forEachIndexed { index, value -> + value.annotations.forEach { + printAnnotation(it, true) + } + print(value.name) + if (index < values.size - 1) printlnWithNoIndent(",") + } + printlnWithNoIndent(";") + printlnWithNoIndent() + } + + val classPosition = lineMappings.getPosition(psiClass) + + val fieldsPositions = psiClass.fields + .filterNot { it is PsiEnumConstant } + .onEach { lineMappings.registerField(psiClass, it) } + .associateWith { MemberData(it.name, it.signature, lineMappings.getPosition(psiClass, it)) } + + fieldsPositions.keys.sortedWith(MembersPositionComparator(classPosition, fieldsPositions)).forEach { + printField(it) + } + + val methodsPositions = psiClass.methods + .filterNot { + it.isConstructor && psiClass is PsiEnumConstantInitializer + || psiClass.isEnum && it.isSyntheticStaticEnumMethod() + || it.hasAnnotation("kotlinx.kapt.KaptIgnored") + } + .onEach { lineMappings.registerMethod(psiClass, it) } + .associateWith { MemberData(it.name, it.signature, lineMappings.getPosition(psiClass, it)) } + + if (fieldsPositions.isNotEmpty() && methodsPositions.isNotEmpty()) printlnWithNoIndent() + methodsPositions.keys.sortedWith(MembersPositionComparator(classPosition, methodsPositions)) + .forEach { + lineMappings.registerSignature(javacSignature(it), it) + printMethod(it) + } + + if (psiClass.innerClasses.isNotEmpty() && (fieldsPositions.isNotEmpty() || methodsPositions.isNotEmpty())) println() + psiClass.innerClasses.forEach { + with(ClassGenerator(it)) { printClass() } + } + popIndent() + println("}") + } + + private fun Printer.printComment(element: PsiElement) { + if (!keepKdocComments) return + getKDocComment(element)?.let { comment -> + println("/**") + comment.split("\n").forEach { + println(" * ", it) + } + println("*/") + } + } + + private fun Printer.printField(psiVariable: PsiVariable) { + if (!isValidIdentifier(psiVariable.name!!)) return + if ((psiVariable is PsiField) && (psiVariable.containingClass != null) && !checkIfValidTypeName(psiVariable.type)) return + + printComment(psiVariable) + printModifiers(psiVariable) + printType(psiVariable.type) + printWithNoIndent(" ", psiVariable.name) + if (psiVariable.hasInitializer() && (dumpDefaultParameterValues || psiVariable.navigationElement !is KtParameter)) { + printWithNoIndent(" = ", psiVariable.initializer?.text) + } else if (psiVariable.isFinal) { + printWithNoIndent(" = ", defaultValue(psiVariable.type)) + } + printlnWithNoIndent(";") + printlnWithNoIndent() + } + + private fun Printer.printMethod(method: PsiMethod) { + if (!isValidIdentifier(method.name)) return + + if (method.returnType?.let { checkIfValidTypeName(it) } == false + || method.parameterList.parameters.any { !checkIfValidTypeName(it.type) } + ) return + + printComment(method) + printModifiers(method) + printTypeParams(method.typeParameters) + + method.returnType?.let { + printType(it) + printWithNoIndent(" ") + } + printWithNoIndent(method.name, "(") + method.parameterList.parameters.filter { isValidIdentifier(paramName(it)) }.forEachIndexed { index, param -> + if (index > 0) printWithNoIndent(", ") + printModifiers(param) + printType(param.type) + printWithNoIndent(" ", paramName(param)) + } + printWithNoIndent(")") + (method as? PsiAnnotationMethod)?.defaultValue?.let { + printWithNoIndent(" default ") + printAnnotationMemberValue(it) + } + + method.throwsList.referencedTypes.takeIf { it.isNotEmpty() }?.let { thrownTypes -> + printWithNoIndent(" throws ") + thrownTypes.forEachIndexed { index, typ -> + if (index > 0) printWithNoIndent(", ") + printType(typ) + } + } + + if (method.isAbstract) { + printlnWithNoIndent(";") + } else { + printlnWithNoIndent(" {") + pushIndent() + + if (method.isConstructor && !psiClass.isEnum) { + val superConstructor = method.containingClass?.superClass?.constructors?.firstOrNull { !it.isPrivate } + if (superConstructor != null) { + print("super(") + val args = superConstructor.parameterList.parameters.map { defaultValue(it.type) } + args.forEachIndexed { index, arg -> + if (index > 0) printWithNoIndent(", ") + printWithNoIndent(arg) + } + printlnWithNoIndent(");") + } + } else if (method.returnType != null && method.returnType != PsiType.VOID) { + println("return ", defaultValue(method.returnType!!), ";") + } + popIndent() + println("}") + } + + printlnWithNoIndent() + } + + private fun javacSignature(method: PsiMethod) = printToString { + print(method.name, "(") + method.parameterList.parameters.forEachIndexed{ index, parameter -> + if (index > 0) print(", ") + printTypeSignature(parameter.type) + } + print(")") + } + + private fun reportIfIllegalTypeUsage( + containingClass: PsiClass, + type: PsiType, + ) { + val typeName = type.simpleNameOrNull ?: return + if (typeName in importsFromRoot && reportedTypes.add(typeName)) { + onError("${containingClass.qualifiedName}: Can't reference type '${typeName}' from default package in Java stub.") + } + } + + private fun recordErrorTypes(type: PsiType) { + if (type is PsiEllipsisType) { + recordErrorTypes(type.componentType) + return + } + if (type.qualifiedNameOrNull == null) { + recordUnresolvedQualifier(type.qualifiedName) + } + when (type) { + is PsiClassType -> type.typeArguments().forEach { (it as? PsiType)?.let { recordErrorTypes(it) } } + is PsiArrayType -> recordErrorTypes(type.componentType) + } + } + + private fun elementMapping(lightClass: PsiClass): Multimap = + HashMultimap.create().apply { + (lightClass.methods.asSequence() + lightClass.fields.asSequence() + lightClass.constructors.asSequence()).forEach { + put((it as KtLightElement<*, *>).kotlinOrigin, it) + } + } + + private fun Printer.printType(type: PsiType) { + recordErrorTypes(type) + type.annotations.forEach { + printAnnotation(it, false) + } + printTypeSignature(type) + } + + private fun Printer.printTypeSignature(type: PsiType) { + printWithNoIndent((if (type is PsiClassType && isErroneous(type)) type.rawType() else type).canonicalText.replace('$', '.')) + } + + private fun Printer.printTypeParams(typeParameters: Array) { + if (typeParameters.isEmpty()) return + printWithNoIndent("<") + typeParameters.forEachIndexed { index, param -> + if (index > 0) printWithNoIndent(", ") + printWithNoIndent(param.name, " extends ") + if (param.extendsListTypes.isNotEmpty()) { + param.extendsListTypes.forEachIndexed { i, t -> + if (i > 0) printWithNoIndent(", ") + printType(t) + } + } else { + printWithNoIndent("java.lang.Object") + } + } + printWithNoIndent(">") + } + + private fun Printer.printAnnotationMemberValue(psiAnnotationMemberValue: PsiAnnotationMemberValue) { + when (psiAnnotationMemberValue) { + is PsiClassObjectAccessExpression -> + psiAnnotationMemberValue.text.takeIf { checkIfValidTypeName(psiAnnotationMemberValue.operand.type) } + ?.let { printWithNoIndent(it) } + is PsiArrayInitializerMemberValue -> { + printWithNoIndent("{") + psiAnnotationMemberValue.initializers.forEachIndexed { index, value -> + if (index > 0) printWithNoIndent(", ") + printAnnotationMemberValue(value) + } + printWithNoIndent("}") + } + is PsiAnnotation -> printAnnotation(psiAnnotationMemberValue, false) + else -> printWithNoIndent(psiAnnotationMemberValue.text) + } + } + + private fun convertDotQualifiedExpression(dotQualifiedExpression: KtDotQualifiedExpression): String? { + val qualifier = dotQualifiedExpression.lastChild as? KtNameReferenceExpression ?: return null + val name = qualifier.text.takeIf { isValidIdentifier(it) } ?: "InvalidFieldName" + val lhs = when (val left = dotQualifiedExpression.firstChild) { + is KtNameReferenceExpression -> left.getReferencedName() + is KtDotQualifiedExpression -> convertDotQualifiedExpression(left) ?: return null + else -> return null + } + return "$lhs.$name" + } + + private fun Printer.printModifiers(modifierListOwner: PsiModifierListOwner) { + val withIndentation = modifierListOwner !is PsiParameter + for (annotation in modifierListOwner.annotations) { + printAnnotation(annotation, withIndentation) + } + + if (withIndentation) printIndent() + if (!(modifierListOwner is PsiMethod && modifierListOwner.isConstructor && modifierListOwner.containingClass?.isEnum == true) && (modifierListOwner !is PsiEnumConstant)) { + for (modifier in PsiModifier.MODIFIERS.filter(modifierListOwner::hasModifierProperty)) { + if (modifier == PsiModifier.PRIVATE && (modifierListOwner as? PsiMember)?.containingClass?.isInterface == true) continue + if ((modifier != PsiModifier.FINAL && modifier != PsiModifier.ABSTRACT) || !(modifierListOwner is PsiClass && modifierListOwner.isEnum)) { + printWithNoIndent(modifier, " ") + } + } + } + } + + private fun Printer.printAnnotation(annotation: PsiAnnotation, separateLine: Boolean) { + fun collectNameParts(node: ASTNode, builder: StringBuilder) { + when (node) { + is LeafPsiElement -> + when (node.elementType) { + KtTokens.IDENTIFIER, KtTokens.DOT -> builder.append((node as ASTNode).text) + } + else -> node.children().forEach { collectNameParts(it, builder) } + } + } + + fun qualifiedName(node: ASTNode): String { + val callee = node.children().first { it.elementType == KtNodeTypes.CONSTRUCTOR_CALLEE } + return buildString { + collectNameParts(callee, this) + } + } + + val rawQualifiedName = when (annotation.qualifiedName) { + // A temporary fix for KT-60482 + "" -> + (annotation as? KtLightElement<*, *>)?.kotlinOrigin?.node?.let { qualifiedName(it) } + ?.also { recordUnresolvedQualifier(it) } + else -> annotation.qualifiedName + } ?: return + + + val qname = if (rawQualifiedName.startsWith(packageName) && rawQualifiedName.lastIndexOf('.') == packageName.length) + rawQualifiedName.substring(packageName.length + 1) + else rawQualifiedName + if (separateLine) printIndent() + printWithNoIndent("@", qname, "(") + + annotation.parameterList.attributes + .filter { it.name != null && isValidIdentifier(it.name!!) } + .forEachIndexed { index, attr -> + if (index > 0) printWithNoIndent(", ") + printAnnotationAttribute(attr) + } + printWithNoIndent(")") + if (separateLine) printlnWithNoIndent() else printWithNoIndent(" ") + } + + private fun Printer.printAnnotationAttribute(attr: PsiNameValuePair) { + val name = attr.name?.takeIf { isValidIdentifier(it) } ?: return + val value = if (attr.value == null) { + ((attr as? KtLightElementBase)?.kotlinOrigin as? KtDotQualifiedExpression)?.let { convertDotQualifiedExpression(it) } + } else { + when (val v = attr.value!!) { + is PsiClassObjectAccessExpression -> v.text.takeIf { checkIfValidTypeName(v.operand.type) } + is PsiArrayInitializerMemberValue -> + printToString { + printWithNoIndent("{") + v.initializers.forEachIndexed { index, value -> + if (index > 0) printWithNoIndent(", ") + printAnnotationMemberValue(value) + } + printWithNoIndent("}") + } + is PsiAnnotation -> printToString { printAnnotation(v, false) } + else -> v.text + } + } ?: return + printWithNoIndent(name, " = ", value) + } + + private fun calculateMetadata(lightClass: PsiClass): Metadata? = + if (stripMetadata) null + else with(analysisSession) { + when (lightClass) { + is KtLightClassForFacade -> + if (lightClass.multiFileClass) + lightClass.qualifiedName?.let { createMultifileClassMetadata(lightClass, it) } + else + lightClass.files.singleOrNull()?.calculateMetadata(elementMapping(lightClass)) + is SymbolLightClassForNamedClassLike -> + lightClass.kotlinOrigin?.calculateMetadata(elementMapping(lightClass)) + else -> null + } + } + + private fun createMultifileClassMetadata(lightClass: KtLightClassForFacade, qualifiedName: String): Metadata = + Metadata( + kind = KotlinClassHeader.Kind.MULTIFILE_CLASS.id, + metadataVersion = LanguageVersion.KOTLIN_2_0.toMetadataVersion().toArray(), + data1 = lightClass.files.map { + JvmFileClassUtil.manglePartName(qualifiedName.replace('.', '/'), it.name) + }.toTypedArray(), + extraInt = METADATA_JVM_IR_FLAG or METADATA_JVM_IR_STABLE_ABI_FLAG + ) + + private fun Printer.printMetadata(m: Metadata) { + if (metadataRenderer != null) { + metadataRenderer.invoke(this, m) + } else { + print("@kotlin.Metadata(k = ", m.kind, ", mv = {") + (overriddenMetadataVersion?.toArray() ?: m.metadataVersion).forEachIndexed { index, value -> + if (index > 0) printWithNoIndent(", ") + printWithNoIndent(value) + } + printWithNoIndent("}, d1 = {") + m.data1.forEachIndexed { i, s -> + if (i > 0) printWithNoIndent(", ") + printStringLiteral(s) + + } + printWithNoIndent("}, d2 = {") + m.data2.forEachIndexed { i, s -> + if (i > 0) printWithNoIndent(", ") + printStringLiteral(s) + + } + printWithNoIndent("}, xs= ") + printStringLiteral(m.extraString) + printWithNoIndent(", pn = ") + printStringLiteral(m.packageName) + printlnWithNoIndent(", xi = ", m.extraInt, ")") + } + } + + private fun checkIfValidTypeName(type: PsiType): Boolean { + when (type) { + is PsiArrayType -> return checkIfValidTypeName(type.componentType) + is PsiPrimitiveType -> return true + } + + val internalName = type.qualifiedName + // Ignore type names with Java keywords in it + if (internalName.split('/', '.').any { it in JAVA_KEYWORDS }) { + if (strictMode) { + onError("Can't generate a stub for '${internalName}'.\nType name '${type.qualifiedName}' contains a Java keyword.") + } + + return false + } + + val clazz = type.resolvedClass ?: return true + + if (doesInnerClassNameConflictWithOuter(clazz)) { + if (strictMode) { + onError( + "Can't generate a stub for '${clazz.qualifiedNameWithDollars}'.\n" + + "Its name '${clazz.name}' is the same as one of the outer class names." + + "\nJava forbids it. Please change one of the class names." + ) + } + + return false + } + + reportIfIllegalTypeUsage(psiClass, type) + + return true + } + + fun Printer.printStringLiteral(s: String) { + printWithNoIndent('\"') + s.forEach { + printWithNoIndent( + when (it) { + '\n' -> "\\n" + '\r' -> "\\r" + '\t' -> "\\t" + '"' -> "\\\"" + '\\' -> "\\\\" + else -> if (it.code in 32..128) it else "\\u%04X".format(it.code) + } + ) + } + printWithNoIndent('\"') + } + } + } +} + +private val JAVA_KEYWORDS = setOf( + "_", "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", + "double", "else", "enum", "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", + "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return", + "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", + "void", "volatile", "while" +) + +private inline fun printToString(block: Printer.() -> Unit): String = + buildString { + Printer(this).block() + } + + +private fun defaultValue(type: PsiType): String = + when (type) { + PsiType.BYTE -> "0" + PsiType.BOOLEAN -> "false" + PsiType.CHAR -> "\'\\u0000\'" + PsiType.SHORT -> "0" + PsiType.INT -> "0" + PsiType.LONG -> "0L" + PsiType.FLOAT -> "0.0F" + PsiType.DOUBLE -> "0.0" + else -> "null" + } + +private fun PsiMethod.isSyntheticStaticEnumMethod(): Boolean { + if (!isStatic) return false + return when (name) { + StandardNames.ENUM_VALUES.asString() -> parameters.isEmpty() + StandardNames.ENUM_VALUE_OF.asString() -> (parameters.singleOrNull()?.type as? PsiClassType)?.qualifiedName == "java.lang.String" + else -> false + } +} + +// Java forbids outer and inner class names to be the same. Check if the names are different +private tailrec fun doesInnerClassNameConflictWithOuter( + clazz: PsiClass, + outerClass: PsiClass? = findContainingClassNode(clazz), +): Boolean { + if (outerClass == null) return false + if (clazz.name == outerClass.name) return true + // Try to find the containing class for outerClassNode (to check the whole tree recursively) + val containingClassForOuterClass = findContainingClassNode(outerClass) ?: return false + return doesInnerClassNameConflictWithOuter(clazz, containingClassForOuterClass) +} + +private fun findContainingClassNode(clazz: PsiClass): PsiClass? = + clazz.parent as? PsiClass + +private fun isValidQualifiedName(name: FqName) = name.pathSegments().all { isValidIdentifier(it.asString()) } + +private fun isValidIdentifier(name: String): Boolean = + !(name.isEmpty() + || (name in JAVA_KEYWORDS) + || !Character.isJavaIdentifierStart(name[0]) + || name.drop(1).any { !Character.isJavaIdentifierPart(it) }) + +private fun paramName(info: PsiParameter): String { + val defaultName = info.name + return when { + isValidIdentifier(defaultName) -> defaultName + defaultName == SpecialNames.IMPLICIT_SET_PARAMETER.asString() -> "p0" + else -> "p${info.parameterIndex()}_${info.name.hashCode().ushr(1)}" + } +} + +private fun isErroneous(type: PsiType): Boolean { + if (type.canonicalText == StandardNames.NON_EXISTENT_CLASS.asString()) return true + if (type is PsiClassType) return type.parameters.any { isErroneous(it) } + return false +} + +private fun getKDocComment(psiElement: PsiElement): String? { + val ktElement = psiElement.extractOriginalKtDeclaration() ?: return null + if (psiElement is PsiField && ktElement is KtObjectDeclaration) { + // Do not write KDoc on object instance field + return null + } + + val docComment = when { + ktElement is KtProperty -> ktElement.docComment + ktElement.docComment == null && ktElement is KtPropertyAccessor -> ktElement.property.docComment + else -> ktElement.docComment + } ?: return null + + if (psiElement is PsiMethod && psiElement.isConstructor && ktElement is KtClassOrObject) { + // We don't want the class comment to be duplicated on () + return null + } + + return extractComment(docComment) +} + +private fun PsiElement.extractOriginalKtDeclaration(): KtDeclaration? { + // This when is needed to avoid recursion + val elementToExtract = when (this) { + is KtLightParameter -> when (kotlinOrigin) { + null -> method + else -> return kotlinOrigin + } + else -> this + } + + return when (elementToExtract) { + is KtLightMember<*> -> { + val origin = elementToExtract.lightMemberOrigin + origin?.auxiliaryOriginalElement ?: origin?.originalElement ?: elementToExtract.kotlinOrigin + } + is KtLightElement<*, *> -> elementToExtract.kotlinOrigin + else -> null + } as? KtDeclaration +} \ No newline at end of file diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/parseParameters.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/parseParameters.kt deleted file mode 100644 index 23f2ab40f9f..00000000000 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/parseParameters.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -@file:Suppress("UnstableApiUsage") - -package org.jetbrains.kotlin.kapt4 - -import com.intellij.psi.JvmPsiConversionHelper -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiMethod -import com.intellij.psi.PsiType - -internal class ParameterInfo( - val name: String, - val type: PsiType, - val annotations: List, -) - -internal fun PsiMethod.getParametersInfo(): List { - val typeConverter = JvmPsiConversionHelper.getInstance(project) - return this.parameterList.parameters.map { - ParameterInfo(it.name, typeConverter.convertType(it.type), it.annotations.asList()) - } -} diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/utils.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/utils.kt index 5c7ebff8226..44e141c2705 100644 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/utils.kt +++ b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/utils.kt @@ -10,52 +10,13 @@ import com.intellij.psi.* import com.intellij.psi.util.ClassUtil import com.intellij.psi.util.PsiTypesUtil import com.intellij.psi.util.PsiUtil -import org.jetbrains.kotlin.builtins.StandardNames -import org.jetbrains.org.objectweb.asm.Opcodes -internal val PsiModifierListOwner.isPublic: Boolean get() = hasModifier(JvmModifier.PUBLIC) + internal val PsiModifierListOwner.isPrivate: Boolean get() = hasModifier(JvmModifier.PRIVATE) -internal val PsiModifierListOwner.isProtected: Boolean get() = hasModifier(JvmModifier.PROTECTED) - internal val PsiModifierListOwner.isFinal: Boolean get() = hasModifier(JvmModifier.FINAL) internal val PsiModifierListOwner.isAbstract: Boolean get() = hasModifier(JvmModifier.ABSTRACT) - internal val PsiModifierListOwner.isStatic: Boolean get() = hasModifier(JvmModifier.STATIC) -internal val PsiModifierListOwner.isVolatile: Boolean get() = hasModifier(JvmModifier.VOLATILE) -internal val PsiModifierListOwner.isSynchronized: Boolean get() = hasModifier(JvmModifier.SYNCHRONIZED) -internal val PsiModifierListOwner.isNative: Boolean get() = hasModifier(JvmModifier.NATIVE) -internal val PsiModifierListOwner.isStrict: Boolean get() = hasModifier(JvmModifier.STRICTFP) -internal val PsiModifierListOwner.isTransient: Boolean get() = hasModifier(JvmModifier.TRANSIENT) -internal typealias JavacList = com.sun.tools.javac.util.List - -internal inline fun mapJList(values: Array?, f: (T) -> R?): JavacList { - return mapJList(values?.asList(), f) -} - -internal inline fun mapJList(values: Iterable?, f: (T) -> R?): JavacList { - if (values == null) return JavacList.nil() - - var result = JavacList.nil() - for (item in values) { - f(item)?.let { result = result.append(it) } - } - return result -} - -internal inline fun mapJListIndexed(values: Iterable?, f: (Int, T) -> R?): JavacList { - if (values == null) return JavacList.nil() - - var result = JavacList.nil() - values.forEachIndexed { index, item -> - f(index, item)?.let { result = result.append(it) } - } - return result -} - -internal operator fun JavacList.plus(other: JavacList): JavacList { - return this.appendList(other) -} internal val PsiMethod.signature: String get() = ClassUtil.getAsmMethodSignature(this) @@ -73,7 +34,7 @@ internal val PsiType.qualifiedName: String internal val PsiType.qualifiedNameOrNull: String? get() { if (this is PsiPrimitiveType) return name - if (this is PsiWildcardType) return this.bound?.qualifiedNameOrNull + if (this is PsiWildcardType) return bound?.qualifiedNameOrNull return when (val resolvedClass = resolvedClass) { is PsiTypeParameter -> resolvedClass.name else -> resolvedClass?.qualifiedName @@ -95,120 +56,6 @@ internal val PsiClass.defaultType: PsiType internal val PsiType.resolvedClass: PsiClass? get() = (this as? PsiClassType)?.resolve() -internal val PsiModifierListOwner.accessFlags: Long - get() = when (this) { - is PsiClass -> computeClassAccessFlags(this) - is PsiMethod -> computeMethodAccessFlags(this) - is PsiField -> computeFieldAccessFlags(this) - else -> 0 - }.toLong() - -private fun computeCommonAccessFlags(declaration: PsiModifierListOwner): Int { - /* - * int ACC_STATIC = 0x0008; // field, method; class isn't mentioned but actually used - * int ACC_PUBLIC = 0x0001; // class, field, method - * int ACC_PRIVATE = 0x0002; // class, field, method - * int ACC_PROTECTED = 0x0004; // class, field, method - * int ACC_FINAL = 0x0010; // class, field, method, parameter - * int ACC_DEPRECATED = 0x20000; // class, field, method - */ - var access = 0 - val visibilityFlag = when { - declaration.isPublic -> Opcodes.ACC_PUBLIC - declaration.isPrivate -> Opcodes.ACC_PRIVATE - declaration.isProtected -> Opcodes.ACC_PROTECTED - else -> 0 - } - access = access or visibilityFlag - if (declaration.isFinal) { - access = access or Opcodes.ACC_FINAL - } - if (declaration.annotations.any { it.hasQualifiedName(StandardNames.FqNames.deprecated.asString()) }) { - access = access or Opcodes.ACC_DEPRECATED - } - if (declaration.isStatic) { - access = access or Opcodes.ACC_STATIC - } - return access -} - -private fun computeClassAccessFlags(klass: PsiClass): Int { - /* - * int ACC_INTERFACE = 0x0200; // class - * int ACC_ABSTRACT = 0x0400; // class, method - * int ACC_ANNOTATION = 0x2000; // class - * int ACC_ENUM = 0x4000; // class(?) field inner - * int ACC_RECORD = 0x10000; // class - */ - var access = computeCommonAccessFlags(klass) - val classKindFlag = when { - klass.isInterface -> Opcodes.ACC_INTERFACE - klass.isEnum -> { - // enum can not be final - access = access and Opcodes.ACC_FINAL.inv() - Opcodes.ACC_ENUM - } - - klass.isRecord -> Opcodes.ACC_RECORD - else -> 0 - } - access = access or classKindFlag - if (klass.isAnnotationType) { - access = access or Opcodes.ACC_ANNOTATION - } - if (klass.isAbstract) { - access = access or Opcodes.ACC_ABSTRACT - } - return access -} - -private fun computeMethodAccessFlags(method: PsiMethod): Int { - /* - * int ACC_SYNCHRONIZED = 0x0020; // method - * int ACC_VARARGS = 0x0080; // method - * int ACC_NATIVE = 0x0100; // method - * int ACC_ABSTRACT = 0x0400; // class, method - * int ACC_STRICT = 0x0800; // method - */ - var access = computeCommonAccessFlags(method) - - if (method.isSynchronized) { - access = access or Opcodes.ACC_SYNCHRONIZED - } - if (method.isVarArgs) { - access = access or Opcodes.ACC_VARARGS - } - if (method.isNative) { - access = access or Opcodes.ACC_NATIVE - } - if (method.isAbstract) { - access = access or Opcodes.ACC_ABSTRACT - } - if (method.isStrict) { - access = access or Opcodes.ACC_STRICT - } - return access -} - -private fun computeFieldAccessFlags(field: PsiField): Int { - /* - * int ACC_VOLATILE = 0x0040; // field - * int ACC_TRANSIENT = 0x0080; // field - * int ACC_ENUM = 0x4000; // class(?) field inner - */ - var access = computeCommonAccessFlags(field) - if (field.isVolatile) { - access = access or Opcodes.ACC_VOLATILE - } - if (field.isTransient) { - access = access or Opcodes.ACC_TRANSIENT - } - if (field is PsiEnumConstant) { - access = access or Opcodes.ACC_ENUM - } - return access -} - internal val PsiClass.qualifiedNameWithDollars: String? get() { val packageName = PsiUtil.getPackageName(this) ?: return null @@ -221,9 +68,3 @@ internal val PsiClass.qualifiedNameWithDollars: String? val classNameWithDollars = className.replace(".", "$") return "$packageName.$classNameWithDollars" } - -private const val LONG_DEPRECATED = Opcodes.ACC_DEPRECATED.toLong() -private const val LONG_ENUM = Opcodes.ACC_ENUM.toLong() - -internal fun isDeprecated(access: Long) = (access and LONG_DEPRECATED) != 0L -internal fun isEnum(access: Long) = (access and LONG_ENUM) != 0L \ No newline at end of file diff --git a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt index 1576e157328..cf565ec0498 100644 --- a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt +++ b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.kapt4 import com.intellij.mock.MockProject import com.intellij.openapi.Disposable +import kotlinx.metadata.jvm.KotlinClassMetadata import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider @@ -15,13 +16,19 @@ import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISe import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.kapt3.base.KaptContext +import org.jetbrains.kotlin.kapt3.base.KaptFlag import org.jetbrains.kotlin.kapt3.base.KaptOptions +import org.jetbrains.kotlin.kapt3.base.javac.reportKaptError import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger import org.jetbrains.kotlin.kapt3.test.KaptMessageCollectorProvider import org.jetbrains.kotlin.kapt3.test.kaptOptionsProvider +import org.jetbrains.kotlin.kotlinp.Kotlinp +import org.jetbrains.kotlin.kotlinp.KotlinpSettings import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.services.* +import org.jetbrains.kotlin.utils.Printer import java.io.File internal class Kapt4Facade(private val testServices: TestServices) : @@ -64,7 +71,7 @@ private fun run( options: KaptOptions, applicationDisposable: Disposable, projectDisposable: Disposable, -): Pair> { +): Pair> { val standaloneAnalysisAPISession = buildStandaloneAnalysisAPISession(applicationDisposable, projectDisposable) { (project as MockProject).registerService( KtLifetimeTokenProvider::class.java, @@ -76,24 +83,45 @@ private fun run( val (module, psiFiles) = standaloneAnalysisAPISession.modulesWithFiles.entries.single() return KtAnalysisSessionProvider.getInstance(module.project).analyze(module) { - val context = Kapt4ContextForStubGeneration( + val context = KaptContext( options, withJdk = false, WriterBackedKaptLogger(isVerbose = false), - this@analyze, - psiFiles.filterIsInstance() ) - val generator = with(context) { Kapt4StubGenerator() } - context to generator.generateStubs() + val onError = { message: String -> + if (context.options[KaptFlag.STRICT]) { + context.reportKaptError(*message.split("\n").toTypedArray()) + } else { + context.logger.warn(message) + } + } + context to generateStubs(psiFiles.filterIsInstance(), options, onError,this@analyze, metadataRenderer = { renderMetadata(it) }) } } internal data class Kapt4ContextBinaryArtifact( - internal val kaptContext: Kapt4ContextForStubGeneration, - internal val kaptStubs: List + internal val kaptContext: KaptContext, + internal val kaptStubs: List ) : ResultingArtifact.Binary() { object Kind : BinaryKind("KaptArtifact") override val kind: BinaryKind get() = Kind } + +private fun Printer.renderMetadata(metadata: Metadata) { + val text = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true)).renderClassFile(KotlinClassMetadata.read(metadata)) + // "/*" and "*/" delimiters are used in kotlinp, for example to render type parameter names. Replace them with something else + // to avoid them being interpreted as Java comments. + val sanitized = text.split('\n') + .dropLast(1) + .map { + it.replace("/*", "(*").replace("*/", "*)") + } + println("/**") + sanitized.forEach { + println(" * ", it) + } + println(" */") + println("@kotlin.Metadata()") +} \ No newline at end of file diff --git a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Handler.kt b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Handler.kt index 68756e8595b..f969837141d 100644 --- a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Handler.kt +++ b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Handler.kt @@ -11,6 +11,7 @@ import com.sun.tools.javac.tree.JCTree import com.sun.tools.javac.util.JCDiagnostic import com.sun.tools.javac.util.List import com.sun.tools.javac.util.Log +import org.jetbrains.kotlin.kapt3.base.KaptContext import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLogBase import org.jetbrains.kotlin.kapt3.base.parseJavaFiles import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject @@ -32,7 +33,6 @@ import org.jetbrains.kotlin.test.utils.withExtension import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import java.io.File import java.util.* -import org.jetbrains.kotlin.kapt3.test.handlers.renderMetadata internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler( testServices, @@ -70,7 +70,7 @@ internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler { val (kaptContext, kaptStubs) = info val convertedFiles = kaptStubs.mapIndexed { index, stub -> - val sourceFile = createTempJavaFile("stub$index.java", stub.file.prettyPrint(kaptContext.context, ::renderMetadata)) - stub.writeMetadataIfNeeded(forSource = sourceFile) + val sourceFile = createTempJavaFile("stub$index.java", stub.source) + stub.writeMetadata(forSource = sourceFile) sourceFile }