KT-60821 [KAPT] Generate stubs without KaptTreeMaker

Merge-request: KT-MR-12979
Merged-by: Pavel Mikhailovskii <Pavel.Mikhailovskii@jetbrains.com>
This commit is contained in:
strangepleasures
2023-11-16 21:14:36 +00:00
committed by Space Team
parent 0fba58eee7
commit 9c2ce475e2
21 changed files with 1035 additions and 1644 deletions
-1
View File
@@ -488,7 +488,6 @@ val projectsWithEnabledContextReceivers by extra {
":kotlinx-serialization-compiler-plugin.k2", ":kotlinx-serialization-compiler-plugin.k2",
":plugins:parcelize:parcelize-compiler:parcelize.k2", ":plugins:parcelize:parcelize-compiler:parcelize.k2",
":plugins:fir-plugin-prototype", ":plugins:fir-plugin-prototype",
":plugins:kapt4",
) )
} }
@@ -29,6 +29,12 @@ import kotlin.collections.List
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
import com.sun.tools.javac.util.List as JavacList import com.sun.tools.javac.util.List as JavacList
fun KaptContext.doAnnotationProcessing(
javaSourceFiles: List<File>,
processors: List<IncrementalProcessor>,
binaryTypesToReprocess: List<String> = emptyList()
) = doAnnotationProcessing(javaSourceFiles, processors, JavacList.nil(), binaryTypesToReprocess)
fun KaptContext.doAnnotationProcessing( fun KaptContext.doAnnotationProcessing(
javaSourceFiles: List<File>, javaSourceFiles: List<File>,
processors: List<IncrementalProcessor>, processors: List<IncrementalProcessor>,
@@ -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<T: KaptContext>(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<JCTree, Tokens.Comment> = emptyMap()) : DocCommentTable {
private val table = map.toMutableMap()
val map: Map<JCTree, Tokens.Comment>
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)
}
}
@@ -5,19 +5,31 @@
package org.jetbrains.kotlin.kapt3.stubs 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.JCTree
import com.sun.tools.javac.tree.TreeScanner
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.kapt3.KaptContextForStubGeneration 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.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.BindingContext 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.FieldNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode import org.jetbrains.org.objectweb.asm.tree.MethodNode
class Kapt3DocCommentKeeper(kaptContext: KaptContextForStubGeneration) : AbstractKDocCommentKeeper<KaptContextForStubGeneration>(kaptContext) { internal class Kapt3DocCommentKeeper(private val kaptContext: KaptContextForStubGeneration) {
private val docCommentTable = KaptDocCommentTable()
fun saveKDocComment(tree: JCTree, node: Any) { fun saveKDocComment(tree: JCTree, node: Any) {
val origin = kaptContext.origins[node] ?: return val origin = kaptContext.origins[node] ?: return
val psiElement = origin.element as? KtDeclaration ?: return val psiElement = origin.element as? KtDeclaration ?: return
@@ -45,4 +57,125 @@ class Kapt3DocCommentKeeper(kaptContext: KaptContextForStubGeneration) : Abstrac
saveKDocComment(tree, docComment) saveKDocComment(tree, docComment)
} }
}
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<JCTree, Tokens.Comment> = emptyMap()) : DocCommentTable {
private val table = map.toMutableMap()
val map: Map<JCTree, Tokens.Comment>
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
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.kapt3.stubs package org.jetbrains.kotlin.kapt3.stubs
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition 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 * 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). * build, making the build non-deterministic and dependent tasks run unnecessarily (see KT-40882).
*/ */
class MembersPositionComparator(val classSource: KotlinPosition?, val memberData: Map<JCTree, MemberData>) : class MembersPositionComparator<T>(val classSource: KotlinPosition?, val memberData: Map<T, MemberData>) :
Comparator<JCTree> { Comparator<T> {
override fun compare(o1: JCTree, o2: JCTree): Int { override fun compare(o1: T, o2: T): Int {
val data1 = memberData.getValue(o1) val data1 = memberData.getValue(o1)
val data2 = memberData.getValue(o2) val data2 = memberData.getValue(o2)
classSource ?: return compareDescriptors(data1, data2) classSource ?: return compareDescriptors(data1, data2)
@@ -51,4 +50,4 @@ class MembersPositionComparator(val classSource: KotlinPosition?, val memberData
return m1.descriptor.compareTo(m2.descriptor) return m1.descriptor.compareTo(m2.descriptor)
} }
} }
class MemberData(val name: String, val descriptor: String, val position: KotlinPosition?) class MemberData(val name: String, val descriptor: String, val position: KotlinPosition?)
@@ -44,6 +44,7 @@ class Test4 {
} }
enum class EnumError { enum class EnumError {
/** This is the one */
One { One {
override fun doIt() = "" override fun doIt() = ""
@@ -135,10 +135,10 @@ public enum Em {
@kotlin.Metadata() @kotlin.Metadata()
public final class Foo { public final class Foo {
private final boolean z = true; private final boolean z = true;
private final byte b = (byte)0; private final byte b = 0;
private final char c = 'c'; private final char c = 'c';
private final char c2 = '\n'; private final char c2 = '\n';
private final short sh = (short)10; private final short sh = 10;
private final int i = 10; private final int i = 10;
private final long l = -10L; private final long l = -10L;
private final float f = 1.0F; private final float f = 1.0F;
@@ -79,7 +79,7 @@ public final class JvmStaticTest {
private static final int one = 1; private static final int one = 1;
public static final int two = 2; public static final int two = 2;
public static final char c = 'C'; public static final char c = 'C';
public final byte three = (byte)3; public final byte three = 3;
public final char d = 'D'; public final char d = 'D';
public JvmStaticTest() { public JvmStaticTest() {
@@ -150,9 +150,9 @@ public final class PrimitiveTypes {
public static final int intMinValue = -2147483648; public static final int intMinValue = -2147483648;
public static final int intMaxValue = 2147483647; public static final int intMaxValue = 2147483647;
public static final int intHex = -1; public static final int intHex = -1;
public static final byte byte0 = (byte)0; public static final byte byte0 = 0;
public static final byte byte50 = (byte)50; public static final byte byte50 = 50;
public static final short short5 = (short)5; public static final short short5 = 5;
public static final char charC = 'C'; public static final char charC = 'C';
public static final char char0 = '\u0000'; public static final char char0 = '\u0000';
public static final char char10 = '\n'; public static final char char10 = '\n';
-12
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
description = "Annotation Processor for Kotlin" description = "Annotation Processor for Kotlin"
plugins { plugins {
@@ -13,7 +11,6 @@ dependencies {
implementation(project(":kotlin-annotation-processing-compiler")) implementation(project(":kotlin-annotation-processing-compiler"))
compileOnly(project(":kotlin-annotation-processing-base")) compileOnly(project(":kotlin-annotation-processing-base"))
compileOnly(project(":analysis:analysis-api-standalone")) compileOnly(project(":analysis:analysis-api-standalone"))
compileOnly(toolsJarApi())
embedded(project(":kotlin-annotation-processing-compiler")) { isTransitive = false } embedded(project(":kotlin-annotation-processing-compiler")) { isTransitive = false }
@@ -77,12 +74,3 @@ publish()
runtimeJar() runtimeJar()
sourcesJar() sourcesJar()
javadocJar() javadocJar()
allprojects {
tasks.withType(KotlinCompile::class).all {
kotlinOptions {
freeCompilerArgs += "-Xcontext-receivers"
}
}
}
@@ -5,8 +5,9 @@
package org.jetbrains.kotlin.kapt4 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.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider
import org.jetbrains.kotlin.analysis.api.standalone.KtAlwaysAccessibleLifetimeTokenProvider 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.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity 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.common.messages.OutputMessageUtil.formatOutputMessage
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot 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.KAPT_OPTIONS
import org.jetbrains.kotlin.kapt3.base.* import org.jetbrains.kotlin.kapt3.base.*
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger 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.base.util.info
import org.jetbrains.kotlin.kapt3.measureTimeMillis import org.jetbrains.kotlin.kapt3.measureTimeMillis
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger 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 org.jetbrains.kotlin.psi.KtFile
import java.io.File import java.io.File
@@ -91,52 +90,70 @@ private class Kapt4AnalysisHandlerExtension : FirAnalysisHandlerExtension() {
} }
return try { return try {
KtAnalysisSessionProvider.getInstance(module.project).analyze(module) { if (options.mode.generateStubs) {
Kapt4ContextForStubGeneration( KtAnalysisSessionProvider.getInstance(module.project).analyze(module) {
generateAndSaveStubs(
psiFiles.filterIsInstance<KtFile>(),
options,
logger,
configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES),
this,
configuration[CommonConfigurationKeys.METADATA_VERSION]
)
}
}
if (options.mode.runAnnotationProcessing) {
KaptContext(
options, options,
false, false,
logger, logger
this,
psiFiles.filterIsInstance<KtFile>(),
configuration[CommonConfigurationKeys.METADATA_VERSION]
).use { context -> ).use { context ->
if (options.mode.generateStubs) runProcessors(context, options)
generateStubs(context, messageCollector, configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES))
if (options.mode.runAnnotationProcessing)
runProcessors(context, options)
} }
true
} }
true
} catch (e: Exception) { } catch (e: Exception) {
logger.exception(e) logger.exception(e)
false false
} }
} }
private fun generateStubs(context: Kapt4ContextForStubGeneration, messageCollector: MessageCollector, reportOutputFiles: Boolean) { private fun generateAndSaveStubs(
val generator = with(context) { Kapt4StubGenerator() } files: List<KtFile>,
val (stubGenerationTime, classesToStubs) = measureTimeMillis { generator.generateStubs() } 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" } logger.info { "Java stub generation took $stubGenerationTime ms" }
val infoBuilder = if (context.logger.isVerbose) StringBuilder("Stubs for Kotlin classes: ") else null val infoBuilder = if (logger.isVerbose) StringBuilder("Stubs for Kotlin classes: ") else null
for ((lightClass, kaptStub) in classesToStubs) { for ((lightClass, kaptStub) in classesToStubs) {
if (kaptStub == null) continue if (kaptStub == null) continue
val stub = kaptStub.file val stub = kaptStub.source
val className = (stub.defs.first { it is JCTree.JCClassDecl } as JCTree.JCClassDecl).simpleName.toString() val className = lightClass.name
val packageName = (lightClass.parent as PsiJavaFile).packageName
val packageName = stub.getPackageNameJava9Aware()?.toString() ?: "" val stubsOutputDir = options.stubsOutputDir
val stubsOutputDir = context.options.stubsOutputDir
val packageDir = if (packageName.isEmpty()) stubsOutputDir else File(stubsOutputDir, packageName.replace('.', '/')) val packageDir = if (packageName.isEmpty()) stubsOutputDir else File(stubsOutputDir, packageName.replace('.', '/'))
packageDir.mkdirs() packageDir.mkdirs()
val generatedFile = File(packageDir, "$className.java") val generatedFile = File(packageDir, "$className.java")
generatedFile.writeText(stub.prettyPrint(context.context)) generatedFile.writeText(stub)
infoBuilder?.append(generatedFile.path) infoBuilder?.append(generatedFile.path)
kaptStub.writeMetadata(forSource = generatedFile)
kaptStub.writeMetadataIfNeeded(forSource = generatedFile)
if (reportOutputFiles) { if (reportOutputFiles) {
val ktFiles = when(lightClass) { val ktFiles = when(lightClass) {
@@ -144,17 +161,17 @@ private class Kapt4AnalysisHandlerExtension : FirAnalysisHandlerExtension() {
else -> listOfNotNull(lightClass.kotlinOrigin?.containingKtFile) else -> listOfNotNull(lightClass.kotlinOrigin?.containingKtFile)
} }
val report = formatOutputMessage(ktFiles.map { it.virtualFilePath }, generatedFile.path) 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") .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) val sources = options.collectJavaSourceFiles(context.sourcesToReprocess)
if (sources.isEmpty()) return if (sources.isEmpty()) return
EfficientProcessorLoader(options, context.logger).use { EfficientProcessorLoader(options, context.logger).use {
@@ -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<KtFile>,
val overriddenMetadataVersion: BinaryVersion? = null
) : KaptContext(options, withJdk, logger) {
val classes: Iterable<KtLightClass> = buildSet {
files.flatMapTo(this) { file ->
file.children.filterIsInstance<KtClassOrObject>().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)
}
}
@@ -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<Kapt4ContextForStubGeneration>(context) {
fun saveKDocComment(tree: JCTree, psiElement: PsiElement) {
val ktElement = psiElement.extractOriginalKtDeclaration<KtDeclaration>() ?: 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 <init>()
return
}
saveKDocComment(tree, docComment)
}
}
inline fun <reified T : KtDeclaration> 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
}
@@ -6,13 +6,12 @@
package org.jetbrains.kotlin.kapt4 package org.jetbrains.kotlin.kapt4
import com.intellij.psi.* import com.intellij.psi.*
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition 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.kotlin.kapt3.stubs.KaptLineMappingCollectorBase
import org.jetbrains.org.objectweb.asm.tree.ClassNode
internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() { internal class Kapt4LineMappingCollector : KaptLineMappingCollectorBase() {
fun registerClass(lightClass: PsiClass) { fun registerClass(lightClass: PsiClass) {
register(lightClass, lightClass.qualifiedNameWithSlashes) register(lightClass, lightClass.qualifiedNameWithSlashes)
} }
@@ -25,13 +24,8 @@ internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() {
register(field, lightClass.qualifiedNameWithSlashes + "#" + field.name) register(field, lightClass.qualifiedNameWithSlashes + "#" + field.name)
} }
fun registerSignature(declaration: JCTree.JCMethodDecl, method: PsiMethod) { fun getPosition(clazz: PsiClass): KotlinPosition? =
signatureInfo[declaration.getJavacSignature()] = method.name + method.signature lineInfo[clazz.qualifiedNameWithSlashes]
}
fun getPosition(lightClass: PsiClass): KotlinPosition? {
return lineInfo[lightClass.qualifiedNameWithSlashes]
}
fun getPosition(lightClass: PsiClass, method: PsiMethod): KotlinPosition? = fun getPosition(lightClass: PsiClass, method: PsiMethod): KotlinPosition? =
lineInfo[lightClass.qualifiedNameWithSlashes + "#" + method.name + method.signature] lineInfo[lightClass.qualifiedNameWithSlashes + "#" + method.name + method.signature]
@@ -47,4 +41,8 @@ internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() {
private val PsiClass.qualifiedNameWithSlashes: String private val PsiClass.qualifiedNameWithSlashes: String
get() = qualifiedNameWithDollars?.replace(".", "/") ?: "<no name provided>" get() = qualifiedNameWithDollars?.replace(".", "/") ?: "<no name provided>"
fun registerSignature(javacSignature: String, method: PsiMethod) {
signatureInfo[javacSignature] = method.name + method.signature
}
} }
@@ -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<KtLightClass, KaptStub?> {
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<JCTree>(classDeclaration)
// imports should be collected after class conversion to
val imports = ktFiles.fold(JavacList.nil<JCTree>()) { 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<JCTree> = 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<JCTree, MemberData>()
val fields = mapJList<PsiField, JCTree>(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<JCTree, MemberData>()
val methods = mapJList<PsiMethod, JCTree>(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<JCTree> {
if (unresolvedQualifiers.isEmpty()) return JavacList.nil()
val imports = mutableListOf<JCImport>()
val importedShortNames = mutableSetOf<String>()
// 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<String, Any>()
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
"<error>" -> (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<PsiAnnotation>,
metadata: Metadata?,
excludeNullabilityAnnotations: Boolean = false,
): JCModifiers {
var seenDeprecated = false
fun convertAndAdd(list: JavacList<JCAnnotation>, annotation: PsiAnnotation): JavacList<JCAnnotation> {
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<JCStatement>(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 : JCTree> T.keepKdocCommentsIfNecessary(element: PsiElement): T {
kdocCommentKeeper?.saveKDocComment(this, element)
return this
}
private fun isIgnored(annotations: List<PsiAnnotation>?): 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<JCTypeParameter>,
val superClass: JCExpression,
val interfaces: JavacList<JCExpression>,
val superClassIsObject: Boolean
)
context(UnresolvedQualifiersRecorder)
private fun parseClassSignature(psiClass: PsiClass): ClassGenericSignature {
val superClasses = mutableListOf<JCExpression>()
val superInterfaces = mutableListOf<JCExpression>()
val superPsiClasses = psiClass.extendsListTypes.toList()
val superPsiInterfaces = psiClass.implementsListTypes.toList()
fun addSuperType(superType: PsiClassType, destination: MutableList<JCExpression>) {
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<JCExpression>()
val interfaceBounds = mutableListOf<JCExpression>()
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<KtFile>) {
val importsFromRoot: Set<String> 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<String>()
private val _simpleNames = mutableSetOf<String>()
val reportedTypes = mutableSetOf<String>()
val qualifiedNames: Set<String>
get() = _qualifiedNames
val simpleNames: Set<String>
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<KtElement, PsiElement> =
HashMultimap.create<KtElement, PsiElement>().apply {
(lightClass.methods.asSequence() + lightClass.fields.asSequence() + lightClass.constructors.asSequence()).forEach {
put((it as KtLightElement<*, *>).kotlinOrigin, it)
}
}
}
@@ -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<String>): 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) })
}
}
}
@@ -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<KtFile>,
options: KaptOptions,
onError: (messages: String) -> Unit,
analysisSession: KtAnalysisSession,
overriddenMetadataVersion: BinaryVersion? = null,
metadataRenderer: (Printer.(Metadata) -> Unit)? = null
): Map<KtLightClass, KaptStub?> =
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<KtFile>,
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<KtLightClass, KaptStub?> =
buildSet {
files.flatMapTo(this) { file ->
file.children.filterIsInstance<KtClassOrObject>().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<String> by lazy {
ktFiles.flatMap { it.importDirectives }
.filter { !it.isAllUnder }
.mapNotNull { im -> im.importPath?.fqName?.takeIf { it.isOneSegmentFQN() }?.asString() }
.toSet()
}
private val unresolvedQualifiedNames = mutableSetOf<String>()
private val unresolvedSimpleNames = mutableSetOf<String>()
private val reportedTypes = mutableSetOf<String>()
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<String>()
// 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<PsiEnumConstant>()
.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<KtElement, PsiElement> =
HashMultimap.create<KtElement, PsiElement>().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<PsiTypeParameter>) {
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
"<error>" ->
(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 <init>()
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
}
@@ -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<PsiAnnotation>,
)
internal fun PsiMethod.getParametersInfo(): List<ParameterInfo> {
val typeConverter = JvmPsiConversionHelper.getInstance(project)
return this.parameterList.parameters.map {
ParameterInfo(it.name, typeConverter.convertType(it.type), it.annotations.asList())
}
}
@@ -10,52 +10,13 @@ import com.intellij.psi.*
import com.intellij.psi.util.ClassUtil import com.intellij.psi.util.ClassUtil
import com.intellij.psi.util.PsiTypesUtil import com.intellij.psi.util.PsiTypesUtil
import com.intellij.psi.util.PsiUtil 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.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.isFinal: Boolean get() = hasModifier(JvmModifier.FINAL)
internal val PsiModifierListOwner.isAbstract: Boolean get() = hasModifier(JvmModifier.ABSTRACT) internal val PsiModifierListOwner.isAbstract: Boolean get() = hasModifier(JvmModifier.ABSTRACT)
internal val PsiModifierListOwner.isStatic: Boolean get() = hasModifier(JvmModifier.STATIC) 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<T> = com.sun.tools.javac.util.List<T>
internal inline fun <T, R> mapJList(values: Array<T>?, f: (T) -> R?): JavacList<R> {
return mapJList(values?.asList(), f)
}
internal inline fun <T, R> mapJList(values: Iterable<T>?, f: (T) -> R?): JavacList<R> {
if (values == null) return JavacList.nil()
var result = JavacList.nil<R>()
for (item in values) {
f(item)?.let { result = result.append(it) }
}
return result
}
internal inline fun <T, R> mapJListIndexed(values: Iterable<T>?, f: (Int, T) -> R?): JavacList<R> {
if (values == null) return JavacList.nil()
var result = JavacList.nil<R>()
values.forEachIndexed { index, item ->
f(index, item)?.let { result = result.append(it) }
}
return result
}
internal operator fun <T : Any> JavacList<T>.plus(other: JavacList<T>): JavacList<T> {
return this.appendList(other)
}
internal val PsiMethod.signature: String internal val PsiMethod.signature: String
get() = ClassUtil.getAsmMethodSignature(this) get() = ClassUtil.getAsmMethodSignature(this)
@@ -73,7 +34,7 @@ internal val PsiType.qualifiedName: String
internal val PsiType.qualifiedNameOrNull: String? internal val PsiType.qualifiedNameOrNull: String?
get() { get() {
if (this is PsiPrimitiveType) return name 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) { return when (val resolvedClass = resolvedClass) {
is PsiTypeParameter -> resolvedClass.name is PsiTypeParameter -> resolvedClass.name
else -> resolvedClass?.qualifiedName else -> resolvedClass?.qualifiedName
@@ -95,120 +56,6 @@ internal val PsiClass.defaultType: PsiType
internal val PsiType.resolvedClass: PsiClass? internal val PsiType.resolvedClass: PsiClass?
get() = (this as? PsiClassType)?.resolve() 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? internal val PsiClass.qualifiedNameWithDollars: String?
get() { get() {
val packageName = PsiUtil.getPackageName(this) ?: return null val packageName = PsiUtil.getPackageName(this) ?: return null
@@ -221,9 +68,3 @@ internal val PsiClass.qualifiedNameWithDollars: String?
val classNameWithDollars = className.replace(".", "$") val classNameWithDollars = className.replace(".", "$")
return "$packageName.$classNameWithDollars" 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
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.kapt4
import com.intellij.mock.MockProject import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import kotlinx.metadata.jvm.KotlinClassMetadata
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider 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.asJava.classes.KtLightClass
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
import org.jetbrains.kotlin.config.CompilerConfiguration 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.KaptOptions
import org.jetbrains.kotlin.kapt3.base.javac.reportKaptError
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
import org.jetbrains.kotlin.kapt3.test.KaptMessageCollectorProvider import org.jetbrains.kotlin.kapt3.test.KaptMessageCollectorProvider
import org.jetbrains.kotlin.kapt3.test.kaptOptionsProvider 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.psi.KtFile
import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.services.* import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.utils.Printer
import java.io.File import java.io.File
internal class Kapt4Facade(private val testServices: TestServices) : internal class Kapt4Facade(private val testServices: TestServices) :
@@ -64,7 +71,7 @@ private fun run(
options: KaptOptions, options: KaptOptions,
applicationDisposable: Disposable, applicationDisposable: Disposable,
projectDisposable: Disposable, projectDisposable: Disposable,
): Pair<Kapt4ContextForStubGeneration, Map<KtLightClass, Kapt4StubGenerator.KaptStub?>> { ): Pair<KaptContext, Map<KtLightClass, KaptStub?>> {
val standaloneAnalysisAPISession = buildStandaloneAnalysisAPISession(applicationDisposable, projectDisposable) { val standaloneAnalysisAPISession = buildStandaloneAnalysisAPISession(applicationDisposable, projectDisposable) {
(project as MockProject).registerService( (project as MockProject).registerService(
KtLifetimeTokenProvider::class.java, KtLifetimeTokenProvider::class.java,
@@ -76,24 +83,45 @@ private fun run(
val (module, psiFiles) = standaloneAnalysisAPISession.modulesWithFiles.entries.single() val (module, psiFiles) = standaloneAnalysisAPISession.modulesWithFiles.entries.single()
return KtAnalysisSessionProvider.getInstance(module.project).analyze(module) { return KtAnalysisSessionProvider.getInstance(module.project).analyze(module) {
val context = Kapt4ContextForStubGeneration( val context = KaptContext(
options, options,
withJdk = false, withJdk = false,
WriterBackedKaptLogger(isVerbose = false), WriterBackedKaptLogger(isVerbose = false),
this@analyze,
psiFiles.filterIsInstance<KtFile>()
) )
val generator = with(context) { Kapt4StubGenerator() } val onError = { message: String ->
context to generator.generateStubs() if (context.options[KaptFlag.STRICT]) {
context.reportKaptError(*message.split("\n").toTypedArray())
} else {
context.logger.warn(message)
}
}
context to generateStubs(psiFiles.filterIsInstance<KtFile>(), options, onError,this@analyze, metadataRenderer = { renderMetadata(it) })
} }
} }
internal data class Kapt4ContextBinaryArtifact( internal data class Kapt4ContextBinaryArtifact(
internal val kaptContext: Kapt4ContextForStubGeneration, internal val kaptContext: KaptContext,
internal val kaptStubs: List<Kapt4StubGenerator.KaptStub> internal val kaptStubs: List<KaptStub>
) : ResultingArtifact.Binary<Kapt4ContextBinaryArtifact>() { ) : ResultingArtifact.Binary<Kapt4ContextBinaryArtifact>() {
object Kind : BinaryKind<Kapt4ContextBinaryArtifact>("KaptArtifact") object Kind : BinaryKind<Kapt4ContextBinaryArtifact>("KaptArtifact")
override val kind: BinaryKind<Kapt4ContextBinaryArtifact> override val kind: BinaryKind<Kapt4ContextBinaryArtifact>
get() = Kind 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()")
}
@@ -11,6 +11,7 @@ import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.util.JCDiagnostic import com.sun.tools.javac.util.JCDiagnostic
import com.sun.tools.javac.util.List import com.sun.tools.javac.util.List
import com.sun.tools.javac.util.Log 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.javac.KaptJavaLogBase
import org.jetbrains.kotlin.kapt3.base.parseJavaFiles import org.jetbrains.kotlin.kapt3.base.parseJavaFiles
import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject 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 org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.io.File import java.io.File
import java.util.* import java.util.*
import org.jetbrains.kotlin.kapt3.test.handlers.renderMetadata
internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler<Kapt4ContextBinaryArtifact>( internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler<Kapt4ContextBinaryArtifact>(
testServices, testServices,
@@ -70,7 +70,7 @@ internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler<Kapt4C
private fun checkJavaCompilerErrors( private fun checkJavaCompilerErrors(
module: TestModule, module: TestModule,
kaptContext: Kapt4ContextForStubGeneration, kaptContext: KaptContext,
actualDump: String actualDump: String
) { ) {
val expectedErrors = (module.directives[EXPECTED_ERROR] + module.directives[Kapt4TestDirectives.EXPECTED_ERROR_K2]).sorted() val expectedErrors = (module.directives[EXPECTED_ERROR] + module.directives[Kapt4TestDirectives.EXPECTED_ERROR_K2]).sorted()
@@ -116,8 +116,8 @@ internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler<Kapt4C
): List<JCTree.JCCompilationUnit> { ): List<JCTree.JCCompilationUnit> {
val (kaptContext, kaptStubs) = info val (kaptContext, kaptStubs) = info
val convertedFiles = kaptStubs.mapIndexed { index, stub -> val convertedFiles = kaptStubs.mapIndexed { index, stub ->
val sourceFile = createTempJavaFile("stub$index.java", stub.file.prettyPrint(kaptContext.context, ::renderMetadata)) val sourceFile = createTempJavaFile("stub$index.java", stub.source)
stub.writeMetadataIfNeeded(forSource = sourceFile) stub.writeMetadata(forSource = sourceFile)
sourceFile sourceFile
} }