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:
committed by
Space Team
parent
0fba58eee7
commit
9c2ce475e2
@@ -488,7 +488,6 @@ val projectsWithEnabledContextReceivers by extra {
|
||||
":kotlinx-serialization-compiler-plugin.k2",
|
||||
":plugins:parcelize:parcelize-compiler:parcelize.k2",
|
||||
":plugins:fir-plugin-prototype",
|
||||
":plugins:kapt4",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ import kotlin.collections.List
|
||||
import kotlin.system.measureTimeMillis
|
||||
import com.sun.tools.javac.util.List as JavacList
|
||||
|
||||
fun KaptContext.doAnnotationProcessing(
|
||||
javaSourceFiles: List<File>,
|
||||
processors: List<IncrementalProcessor>,
|
||||
binaryTypesToReprocess: List<String> = emptyList()
|
||||
) = doAnnotationProcessing(javaSourceFiles, processors, JavacList.nil(), binaryTypesToReprocess)
|
||||
|
||||
fun KaptContext.doAnnotationProcessing(
|
||||
javaSourceFiles: List<File>,
|
||||
processors: List<IncrementalProcessor>,
|
||||
|
||||
-152
@@ -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)
|
||||
}
|
||||
}
|
||||
+135
-2
@@ -5,19 +5,31 @@
|
||||
|
||||
package org.jetbrains.kotlin.kapt3.stubs
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
||||
import com.sun.tools.javac.parser.Tokens
|
||||
import com.sun.tools.javac.tree.DCTree
|
||||
import com.sun.tools.javac.tree.DocCommentTable
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import com.sun.tools.javac.tree.TreeScanner
|
||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
|
||||
import org.jetbrains.kotlin.kapt3.KaptContextForStubGeneration
|
||||
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
|
||||
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtObjectDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
|
||||
class Kapt3DocCommentKeeper(kaptContext: KaptContextForStubGeneration) : AbstractKDocCommentKeeper<KaptContextForStubGeneration>(kaptContext) {
|
||||
internal class Kapt3DocCommentKeeper(private val kaptContext: KaptContextForStubGeneration) {
|
||||
private val docCommentTable = KaptDocCommentTable()
|
||||
|
||||
fun saveKDocComment(tree: JCTree, node: Any) {
|
||||
val origin = kaptContext.origins[node] ?: return
|
||||
val psiElement = origin.element as? KtDeclaration ?: return
|
||||
@@ -45,4 +57,125 @@ class Kapt3DocCommentKeeper(kaptContext: KaptContextForStubGeneration) : Abstrac
|
||||
|
||||
saveKDocComment(tree, docComment)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
+4
-5
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.kapt3.stubs
|
||||
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition
|
||||
|
||||
/**
|
||||
@@ -20,9 +19,9 @@ import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition
|
||||
* The consequence is that the contents of the generated stub files may not be consistent across a clean build and an incremental
|
||||
* build, making the build non-deterministic and dependent tasks run unnecessarily (see KT-40882).
|
||||
*/
|
||||
class MembersPositionComparator(val classSource: KotlinPosition?, val memberData: Map<JCTree, MemberData>) :
|
||||
Comparator<JCTree> {
|
||||
override fun compare(o1: JCTree, o2: JCTree): Int {
|
||||
class MembersPositionComparator<T>(val classSource: KotlinPosition?, val memberData: Map<T, MemberData>) :
|
||||
Comparator<T> {
|
||||
override fun compare(o1: T, o2: T): Int {
|
||||
val data1 = memberData.getValue(o1)
|
||||
val data2 = memberData.getValue(o2)
|
||||
classSource ?: return compareDescriptors(data1, data2)
|
||||
@@ -51,4 +50,4 @@ class MembersPositionComparator(val classSource: KotlinPosition?, val memberData
|
||||
return m1.descriptor.compareTo(m2.descriptor)
|
||||
}
|
||||
}
|
||||
class MemberData(val name: String, val descriptor: String, val position: KotlinPosition?)
|
||||
class MemberData(val name: String, val descriptor: String, val position: KotlinPosition?)
|
||||
|
||||
@@ -44,6 +44,7 @@ class Test4 {
|
||||
}
|
||||
|
||||
enum class EnumError {
|
||||
/** This is the one */
|
||||
One {
|
||||
override fun doIt() = ""
|
||||
|
||||
|
||||
+2
-2
@@ -135,10 +135,10 @@ public enum Em {
|
||||
@kotlin.Metadata()
|
||||
public final class Foo {
|
||||
private final boolean z = true;
|
||||
private final byte b = (byte)0;
|
||||
private final byte b = 0;
|
||||
private final char c = 'c';
|
||||
private final char c2 = '\n';
|
||||
private final short sh = (short)10;
|
||||
private final short sh = 10;
|
||||
private final int i = 10;
|
||||
private final long l = -10L;
|
||||
private final float f = 1.0F;
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class JvmStaticTest {
|
||||
private static final int one = 1;
|
||||
public static final int two = 2;
|
||||
public static final char c = 'C';
|
||||
public final byte three = (byte)3;
|
||||
public final byte three = 3;
|
||||
public final char d = 'D';
|
||||
|
||||
public JvmStaticTest() {
|
||||
|
||||
@@ -150,9 +150,9 @@ public final class PrimitiveTypes {
|
||||
public static final int intMinValue = -2147483648;
|
||||
public static final int intMaxValue = 2147483647;
|
||||
public static final int intHex = -1;
|
||||
public static final byte byte0 = (byte)0;
|
||||
public static final byte byte50 = (byte)50;
|
||||
public static final short short5 = (short)5;
|
||||
public static final byte byte0 = 0;
|
||||
public static final byte byte50 = 50;
|
||||
public static final short short5 = 5;
|
||||
public static final char charC = 'C';
|
||||
public static final char char0 = '\u0000';
|
||||
public static final char char10 = '\n';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
description = "Annotation Processor for Kotlin"
|
||||
|
||||
plugins {
|
||||
@@ -13,7 +11,6 @@ dependencies {
|
||||
implementation(project(":kotlin-annotation-processing-compiler"))
|
||||
compileOnly(project(":kotlin-annotation-processing-base"))
|
||||
compileOnly(project(":analysis:analysis-api-standalone"))
|
||||
compileOnly(toolsJarApi())
|
||||
|
||||
embedded(project(":kotlin-annotation-processing-compiler")) { isTransitive = false }
|
||||
|
||||
@@ -77,12 +74,3 @@ publish()
|
||||
runtimeJar()
|
||||
sourcesJar()
|
||||
javadocJar()
|
||||
|
||||
|
||||
allprojects {
|
||||
tasks.withType(KotlinCompile::class).all {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += "-Xcontext-receivers"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.kapt4
|
||||
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import com.intellij.psi.PsiJavaFile
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
|
||||
import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider
|
||||
import org.jetbrains.kotlin.analysis.api.standalone.KtAlwaysAccessibleLifetimeTokenProvider
|
||||
@@ -14,7 +15,6 @@ import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISe
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil.formatOutputMessage
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
|
||||
@@ -26,11 +26,10 @@ import org.jetbrains.kotlin.kapt3.EfficientProcessorLoader
|
||||
import org.jetbrains.kotlin.kapt3.KAPT_OPTIONS
|
||||
import org.jetbrains.kotlin.kapt3.base.*
|
||||
import org.jetbrains.kotlin.kapt3.base.util.KaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.base.util.getPackageNameJava9Aware
|
||||
import org.jetbrains.kotlin.kapt3.base.util.info
|
||||
import org.jetbrains.kotlin.kapt3.measureTimeMillis
|
||||
import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.util.prettyPrint
|
||||
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.File
|
||||
|
||||
@@ -91,52 +90,70 @@ private class Kapt4AnalysisHandlerExtension : FirAnalysisHandlerExtension() {
|
||||
}
|
||||
|
||||
return try {
|
||||
KtAnalysisSessionProvider.getInstance(module.project).analyze(module) {
|
||||
Kapt4ContextForStubGeneration(
|
||||
if (options.mode.generateStubs) {
|
||||
KtAnalysisSessionProvider.getInstance(module.project).analyze(module) {
|
||||
generateAndSaveStubs(
|
||||
psiFiles.filterIsInstance<KtFile>(),
|
||||
options,
|
||||
logger,
|
||||
configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES),
|
||||
this,
|
||||
configuration[CommonConfigurationKeys.METADATA_VERSION]
|
||||
)
|
||||
}
|
||||
}
|
||||
if (options.mode.runAnnotationProcessing) {
|
||||
KaptContext(
|
||||
options,
|
||||
false,
|
||||
logger,
|
||||
this,
|
||||
psiFiles.filterIsInstance<KtFile>(),
|
||||
configuration[CommonConfigurationKeys.METADATA_VERSION]
|
||||
logger
|
||||
).use { context ->
|
||||
if (options.mode.generateStubs)
|
||||
generateStubs(context, messageCollector, configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES))
|
||||
|
||||
if (options.mode.runAnnotationProcessing)
|
||||
runProcessors(context, options)
|
||||
runProcessors(context, options)
|
||||
}
|
||||
true
|
||||
}
|
||||
true
|
||||
|
||||
} catch (e: Exception) {
|
||||
logger.exception(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateStubs(context: Kapt4ContextForStubGeneration, messageCollector: MessageCollector, reportOutputFiles: Boolean) {
|
||||
val generator = with(context) { Kapt4StubGenerator() }
|
||||
val (stubGenerationTime, classesToStubs) = measureTimeMillis { generator.generateStubs() }
|
||||
private fun generateAndSaveStubs(
|
||||
files: List<KtFile>,
|
||||
options: KaptOptions,
|
||||
logger: MessageCollectorBackedKaptLogger,
|
||||
reportOutputFiles: Boolean,
|
||||
analysisSession: KtAnalysisSession,
|
||||
overriddenMetadataVersion: BinaryVersion?
|
||||
) {
|
||||
fun onError(message: String) {
|
||||
if(options[KaptFlag.STRICT]) {
|
||||
logger.error(message)
|
||||
} else {
|
||||
logger.warn(message)
|
||||
}
|
||||
}
|
||||
val (stubGenerationTime, classesToStubs) = measureTimeMillis {
|
||||
generateStubs(files, options, ::onError, analysisSession, overriddenMetadataVersion)
|
||||
}
|
||||
|
||||
context.logger.info { "Java stub generation took $stubGenerationTime ms" }
|
||||
val infoBuilder = if (context.logger.isVerbose) StringBuilder("Stubs for Kotlin classes: ") else null
|
||||
logger.info { "Java stub generation took $stubGenerationTime ms" }
|
||||
val infoBuilder = if (logger.isVerbose) StringBuilder("Stubs for Kotlin classes: ") else null
|
||||
|
||||
for ((lightClass, kaptStub) in classesToStubs) {
|
||||
if (kaptStub == null) continue
|
||||
val stub = kaptStub.file
|
||||
val className = (stub.defs.first { it is JCTree.JCClassDecl } as JCTree.JCClassDecl).simpleName.toString()
|
||||
|
||||
val packageName = stub.getPackageNameJava9Aware()?.toString() ?: ""
|
||||
val stubsOutputDir = context.options.stubsOutputDir
|
||||
val stub = kaptStub.source
|
||||
val className = lightClass.name
|
||||
val packageName = (lightClass.parent as PsiJavaFile).packageName
|
||||
val stubsOutputDir = options.stubsOutputDir
|
||||
val packageDir = if (packageName.isEmpty()) stubsOutputDir else File(stubsOutputDir, packageName.replace('.', '/'))
|
||||
packageDir.mkdirs()
|
||||
|
||||
val generatedFile = File(packageDir, "$className.java")
|
||||
generatedFile.writeText(stub.prettyPrint(context.context))
|
||||
|
||||
generatedFile.writeText(stub)
|
||||
infoBuilder?.append(generatedFile.path)
|
||||
|
||||
kaptStub.writeMetadataIfNeeded(forSource = generatedFile)
|
||||
kaptStub.writeMetadata(forSource = generatedFile)
|
||||
|
||||
if (reportOutputFiles) {
|
||||
val ktFiles = when(lightClass) {
|
||||
@@ -144,17 +161,17 @@ private class Kapt4AnalysisHandlerExtension : FirAnalysisHandlerExtension() {
|
||||
else -> listOfNotNull(lightClass.kotlinOrigin?.containingKtFile)
|
||||
}
|
||||
val report = formatOutputMessage(ktFiles.map { it.virtualFilePath }, generatedFile.path)
|
||||
messageCollector.report(CompilerMessageSeverity.OUTPUT, report)
|
||||
logger.messageCollector.report(CompilerMessageSeverity.OUTPUT, report)
|
||||
}
|
||||
}
|
||||
|
||||
context.logger.info { infoBuilder.toString() }
|
||||
logger.info { infoBuilder.toString() }
|
||||
|
||||
File(context.options.stubsOutputDir, "error").apply { mkdirs() }.resolve("NonExistentClass.java")
|
||||
File(options.stubsOutputDir, "error").apply { mkdirs() }.resolve("NonExistentClass.java")
|
||||
.writeText("package error;\npublic class NonExistentClass {}\n")
|
||||
}
|
||||
|
||||
private fun runProcessors(context: Kapt4ContextForStubGeneration, options: KaptOptions) {
|
||||
private fun runProcessors(context: KaptContext, options: KaptOptions) {
|
||||
val sources = options.collectJavaSourceFiles(context.sourcesToReprocess)
|
||||
if (sources.isEmpty()) return
|
||||
EfficientProcessorLoader(options, context.logger).use {
|
||||
|
||||
@@ -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
|
||||
|
||||
import com.intellij.psi.*
|
||||
import com.sun.tools.javac.tree.JCTree
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightElement
|
||||
import org.jetbrains.kotlin.kapt3.base.stubs.KotlinPosition
|
||||
import org.jetbrains.kotlin.kapt3.base.stubs.getJavacSignature
|
||||
import org.jetbrains.kotlin.kapt3.stubs.KaptLineMappingCollectorBase
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
|
||||
internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() {
|
||||
internal class Kapt4LineMappingCollector : KaptLineMappingCollectorBase() {
|
||||
fun registerClass(lightClass: PsiClass) {
|
||||
register(lightClass, lightClass.qualifiedNameWithSlashes)
|
||||
}
|
||||
@@ -25,13 +24,8 @@ internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() {
|
||||
register(field, lightClass.qualifiedNameWithSlashes + "#" + field.name)
|
||||
}
|
||||
|
||||
fun registerSignature(declaration: JCTree.JCMethodDecl, method: PsiMethod) {
|
||||
signatureInfo[declaration.getJavacSignature()] = method.name + method.signature
|
||||
}
|
||||
|
||||
fun getPosition(lightClass: PsiClass): KotlinPosition? {
|
||||
return lineInfo[lightClass.qualifiedNameWithSlashes]
|
||||
}
|
||||
fun getPosition(clazz: PsiClass): KotlinPosition? =
|
||||
lineInfo[clazz.qualifiedNameWithSlashes]
|
||||
|
||||
fun getPosition(lightClass: PsiClass, method: PsiMethod): KotlinPosition? =
|
||||
lineInfo[lightClass.qualifiedNameWithSlashes + "#" + method.name + method.signature]
|
||||
@@ -47,4 +41,8 @@ internal class Kapt4LineMappingCollector: KaptLineMappingCollectorBase() {
|
||||
|
||||
private val PsiClass.qualifiedNameWithSlashes: String
|
||||
get() = qualifiedNameWithDollars?.replace(".", "/") ?: "<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.PsiTypesUtil
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
|
||||
internal val PsiModifierListOwner.isPublic: Boolean get() = hasModifier(JvmModifier.PUBLIC)
|
||||
|
||||
internal val PsiModifierListOwner.isPrivate: Boolean get() = hasModifier(JvmModifier.PRIVATE)
|
||||
internal val PsiModifierListOwner.isProtected: Boolean get() = hasModifier(JvmModifier.PROTECTED)
|
||||
|
||||
internal val PsiModifierListOwner.isFinal: Boolean get() = hasModifier(JvmModifier.FINAL)
|
||||
internal val PsiModifierListOwner.isAbstract: Boolean get() = hasModifier(JvmModifier.ABSTRACT)
|
||||
|
||||
internal val PsiModifierListOwner.isStatic: Boolean get() = hasModifier(JvmModifier.STATIC)
|
||||
internal val PsiModifierListOwner.isVolatile: Boolean get() = hasModifier(JvmModifier.VOLATILE)
|
||||
internal val PsiModifierListOwner.isSynchronized: Boolean get() = hasModifier(JvmModifier.SYNCHRONIZED)
|
||||
internal val PsiModifierListOwner.isNative: Boolean get() = hasModifier(JvmModifier.NATIVE)
|
||||
internal val PsiModifierListOwner.isStrict: Boolean get() = hasModifier(JvmModifier.STRICTFP)
|
||||
internal val PsiModifierListOwner.isTransient: Boolean get() = hasModifier(JvmModifier.TRANSIENT)
|
||||
|
||||
internal typealias JavacList<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
|
||||
get() = ClassUtil.getAsmMethodSignature(this)
|
||||
@@ -73,7 +34,7 @@ internal val PsiType.qualifiedName: String
|
||||
internal val PsiType.qualifiedNameOrNull: String?
|
||||
get() {
|
||||
if (this is PsiPrimitiveType) return name
|
||||
if (this is PsiWildcardType) return this.bound?.qualifiedNameOrNull
|
||||
if (this is PsiWildcardType) return bound?.qualifiedNameOrNull
|
||||
return when (val resolvedClass = resolvedClass) {
|
||||
is PsiTypeParameter -> resolvedClass.name
|
||||
else -> resolvedClass?.qualifiedName
|
||||
@@ -95,120 +56,6 @@ internal val PsiClass.defaultType: PsiType
|
||||
internal val PsiType.resolvedClass: PsiClass?
|
||||
get() = (this as? PsiClassType)?.resolve()
|
||||
|
||||
internal val PsiModifierListOwner.accessFlags: Long
|
||||
get() = when (this) {
|
||||
is PsiClass -> computeClassAccessFlags(this)
|
||||
is PsiMethod -> computeMethodAccessFlags(this)
|
||||
is PsiField -> computeFieldAccessFlags(this)
|
||||
else -> 0
|
||||
}.toLong()
|
||||
|
||||
private fun computeCommonAccessFlags(declaration: PsiModifierListOwner): Int {
|
||||
/*
|
||||
* int ACC_STATIC = 0x0008; // field, method; class isn't mentioned but actually used
|
||||
* int ACC_PUBLIC = 0x0001; // class, field, method
|
||||
* int ACC_PRIVATE = 0x0002; // class, field, method
|
||||
* int ACC_PROTECTED = 0x0004; // class, field, method
|
||||
* int ACC_FINAL = 0x0010; // class, field, method, parameter
|
||||
* int ACC_DEPRECATED = 0x20000; // class, field, method
|
||||
*/
|
||||
var access = 0
|
||||
val visibilityFlag = when {
|
||||
declaration.isPublic -> Opcodes.ACC_PUBLIC
|
||||
declaration.isPrivate -> Opcodes.ACC_PRIVATE
|
||||
declaration.isProtected -> Opcodes.ACC_PROTECTED
|
||||
else -> 0
|
||||
}
|
||||
access = access or visibilityFlag
|
||||
if (declaration.isFinal) {
|
||||
access = access or Opcodes.ACC_FINAL
|
||||
}
|
||||
if (declaration.annotations.any { it.hasQualifiedName(StandardNames.FqNames.deprecated.asString()) }) {
|
||||
access = access or Opcodes.ACC_DEPRECATED
|
||||
}
|
||||
if (declaration.isStatic) {
|
||||
access = access or Opcodes.ACC_STATIC
|
||||
}
|
||||
return access
|
||||
}
|
||||
|
||||
private fun computeClassAccessFlags(klass: PsiClass): Int {
|
||||
/*
|
||||
* int ACC_INTERFACE = 0x0200; // class
|
||||
* int ACC_ABSTRACT = 0x0400; // class, method
|
||||
* int ACC_ANNOTATION = 0x2000; // class
|
||||
* int ACC_ENUM = 0x4000; // class(?) field inner
|
||||
* int ACC_RECORD = 0x10000; // class
|
||||
*/
|
||||
var access = computeCommonAccessFlags(klass)
|
||||
val classKindFlag = when {
|
||||
klass.isInterface -> Opcodes.ACC_INTERFACE
|
||||
klass.isEnum -> {
|
||||
// enum can not be final
|
||||
access = access and Opcodes.ACC_FINAL.inv()
|
||||
Opcodes.ACC_ENUM
|
||||
}
|
||||
|
||||
klass.isRecord -> Opcodes.ACC_RECORD
|
||||
else -> 0
|
||||
}
|
||||
access = access or classKindFlag
|
||||
if (klass.isAnnotationType) {
|
||||
access = access or Opcodes.ACC_ANNOTATION
|
||||
}
|
||||
if (klass.isAbstract) {
|
||||
access = access or Opcodes.ACC_ABSTRACT
|
||||
}
|
||||
return access
|
||||
}
|
||||
|
||||
private fun computeMethodAccessFlags(method: PsiMethod): Int {
|
||||
/*
|
||||
* int ACC_SYNCHRONIZED = 0x0020; // method
|
||||
* int ACC_VARARGS = 0x0080; // method
|
||||
* int ACC_NATIVE = 0x0100; // method
|
||||
* int ACC_ABSTRACT = 0x0400; // class, method
|
||||
* int ACC_STRICT = 0x0800; // method
|
||||
*/
|
||||
var access = computeCommonAccessFlags(method)
|
||||
|
||||
if (method.isSynchronized) {
|
||||
access = access or Opcodes.ACC_SYNCHRONIZED
|
||||
}
|
||||
if (method.isVarArgs) {
|
||||
access = access or Opcodes.ACC_VARARGS
|
||||
}
|
||||
if (method.isNative) {
|
||||
access = access or Opcodes.ACC_NATIVE
|
||||
}
|
||||
if (method.isAbstract) {
|
||||
access = access or Opcodes.ACC_ABSTRACT
|
||||
}
|
||||
if (method.isStrict) {
|
||||
access = access or Opcodes.ACC_STRICT
|
||||
}
|
||||
return access
|
||||
}
|
||||
|
||||
private fun computeFieldAccessFlags(field: PsiField): Int {
|
||||
/*
|
||||
* int ACC_VOLATILE = 0x0040; // field
|
||||
* int ACC_TRANSIENT = 0x0080; // field
|
||||
* int ACC_ENUM = 0x4000; // class(?) field inner
|
||||
*/
|
||||
var access = computeCommonAccessFlags(field)
|
||||
if (field.isVolatile) {
|
||||
access = access or Opcodes.ACC_VOLATILE
|
||||
}
|
||||
if (field.isTransient) {
|
||||
access = access or Opcodes.ACC_TRANSIENT
|
||||
}
|
||||
if (field is PsiEnumConstant) {
|
||||
access = access or Opcodes.ACC_ENUM
|
||||
}
|
||||
return access
|
||||
}
|
||||
|
||||
internal val PsiClass.qualifiedNameWithDollars: String?
|
||||
get() {
|
||||
val packageName = PsiUtil.getPackageName(this) ?: return null
|
||||
@@ -221,9 +68,3 @@ internal val PsiClass.qualifiedNameWithDollars: String?
|
||||
val classNameWithDollars = className.replace(".", "$")
|
||||
return "$packageName.$classNameWithDollars"
|
||||
}
|
||||
|
||||
private const val LONG_DEPRECATED = Opcodes.ACC_DEPRECATED.toLong()
|
||||
private const val LONG_ENUM = Opcodes.ACC_ENUM.toLong()
|
||||
|
||||
internal fun isDeprecated(access: Long) = (access and LONG_DEPRECATED) != 0L
|
||||
internal fun isEnum(access: Long) = (access and LONG_ENUM) != 0L
|
||||
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.kapt4
|
||||
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.openapi.Disposable
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
|
||||
import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider
|
||||
@@ -15,13 +16,19 @@ import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISe
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.kapt3.base.KaptContext
|
||||
import org.jetbrains.kotlin.kapt3.base.KaptFlag
|
||||
import org.jetbrains.kotlin.kapt3.base.KaptOptions
|
||||
import org.jetbrains.kotlin.kapt3.base.javac.reportKaptError
|
||||
import org.jetbrains.kotlin.kapt3.base.util.WriterBackedKaptLogger
|
||||
import org.jetbrains.kotlin.kapt3.test.KaptMessageCollectorProvider
|
||||
import org.jetbrains.kotlin.kapt3.test.kaptOptionsProvider
|
||||
import org.jetbrains.kotlin.kotlinp.Kotlinp
|
||||
import org.jetbrains.kotlin.kotlinp.KotlinpSettings
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.model.*
|
||||
import org.jetbrains.kotlin.test.services.*
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
|
||||
internal class Kapt4Facade(private val testServices: TestServices) :
|
||||
@@ -64,7 +71,7 @@ private fun run(
|
||||
options: KaptOptions,
|
||||
applicationDisposable: Disposable,
|
||||
projectDisposable: Disposable,
|
||||
): Pair<Kapt4ContextForStubGeneration, Map<KtLightClass, Kapt4StubGenerator.KaptStub?>> {
|
||||
): Pair<KaptContext, Map<KtLightClass, KaptStub?>> {
|
||||
val standaloneAnalysisAPISession = buildStandaloneAnalysisAPISession(applicationDisposable, projectDisposable) {
|
||||
(project as MockProject).registerService(
|
||||
KtLifetimeTokenProvider::class.java,
|
||||
@@ -76,24 +83,45 @@ private fun run(
|
||||
val (module, psiFiles) = standaloneAnalysisAPISession.modulesWithFiles.entries.single()
|
||||
|
||||
return KtAnalysisSessionProvider.getInstance(module.project).analyze(module) {
|
||||
val context = Kapt4ContextForStubGeneration(
|
||||
val context = KaptContext(
|
||||
options,
|
||||
withJdk = false,
|
||||
WriterBackedKaptLogger(isVerbose = false),
|
||||
this@analyze,
|
||||
psiFiles.filterIsInstance<KtFile>()
|
||||
)
|
||||
val generator = with(context) { Kapt4StubGenerator() }
|
||||
context to generator.generateStubs()
|
||||
val onError = { message: String ->
|
||||
if (context.options[KaptFlag.STRICT]) {
|
||||
context.reportKaptError(*message.split("\n").toTypedArray())
|
||||
} else {
|
||||
context.logger.warn(message)
|
||||
}
|
||||
}
|
||||
context to generateStubs(psiFiles.filterIsInstance<KtFile>(), options, onError,this@analyze, metadataRenderer = { renderMetadata(it) })
|
||||
}
|
||||
}
|
||||
|
||||
internal data class Kapt4ContextBinaryArtifact(
|
||||
internal val kaptContext: Kapt4ContextForStubGeneration,
|
||||
internal val kaptStubs: List<Kapt4StubGenerator.KaptStub>
|
||||
internal val kaptContext: KaptContext,
|
||||
internal val kaptStubs: List<KaptStub>
|
||||
) : ResultingArtifact.Binary<Kapt4ContextBinaryArtifact>() {
|
||||
object Kind : BinaryKind<Kapt4ContextBinaryArtifact>("KaptArtifact")
|
||||
|
||||
override val kind: BinaryKind<Kapt4ContextBinaryArtifact>
|
||||
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.List
|
||||
import com.sun.tools.javac.util.Log
|
||||
import org.jetbrains.kotlin.kapt3.base.KaptContext
|
||||
import org.jetbrains.kotlin.kapt3.base.javac.KaptJavaLogBase
|
||||
import org.jetbrains.kotlin.kapt3.base.parseJavaFiles
|
||||
import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject
|
||||
@@ -32,7 +33,6 @@ import org.jetbrains.kotlin.test.utils.withExtension
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import org.jetbrains.kotlin.kapt3.test.handlers.renderMetadata
|
||||
|
||||
internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler<Kapt4ContextBinaryArtifact>(
|
||||
testServices,
|
||||
@@ -70,7 +70,7 @@ internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler<Kapt4C
|
||||
|
||||
private fun checkJavaCompilerErrors(
|
||||
module: TestModule,
|
||||
kaptContext: Kapt4ContextForStubGeneration,
|
||||
kaptContext: KaptContext,
|
||||
actualDump: String
|
||||
) {
|
||||
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> {
|
||||
val (kaptContext, kaptStubs) = info
|
||||
val convertedFiles = kaptStubs.mapIndexed { index, stub ->
|
||||
val sourceFile = createTempJavaFile("stub$index.java", stub.file.prettyPrint(kaptContext.context, ::renderMetadata))
|
||||
stub.writeMetadataIfNeeded(forSource = sourceFile)
|
||||
val sourceFile = createTempJavaFile("stub$index.java", stub.source)
|
||||
stub.writeMetadata(forSource = sourceFile)
|
||||
sourceFile
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user