Skip to content

Commit ec29145

Browse files
committed
Kapt3: Replace error/NonExistentClass with the actual type (from PSI) (KT-15421)
1 parent ee57446 commit ec29145

12 files changed

+385
-27
lines changed

plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Extension.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ abstract class AbstractKapt3Extension(
198198
logger.info { "Stubs compilation took $classFilesCompilationTime ms" }
199199
logger.info { "Compiled classes: " + compiledClasses.joinToString { it.name } }
200200

201-
return Pair(KaptContext(logger, compiledClasses, origins, options), generationState)
201+
return Pair(KaptContext(logger, bindingContext, compiledClasses, origins, options), generationState)
202202
}
203203

204204
private fun generateKotlinSourceStubs(kaptContext: KaptContext, generationState: GenerationState) {

plugins/kapt3/src/org/jetbrains/kotlin/kapt3/KaptContext.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ import org.jetbrains.kotlin.kapt3.javac.KaptJavaCompiler
2424
import org.jetbrains.kotlin.kapt3.javac.KaptJavaLog
2525
import org.jetbrains.kotlin.kapt3.javac.KaptTreeMaker
2626
import org.jetbrains.kotlin.kapt3.util.KaptLogger
27+
import org.jetbrains.kotlin.resolve.BindingContext
2728
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
2829
import org.jetbrains.org.objectweb.asm.tree.ClassNode
2930
import javax.tools.JavaFileManager
3031

3132
class KaptContext(
3233
val logger: KaptLogger,
34+
val bindingContext: BindingContext,
3335
val compiledClasses: List<ClassNode>,
3436
val origins: Map<Any, JvmDeclarationOrigin>,
3537
processorOptions: Map<String, String>

plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ import org.jetbrains.kotlin.kapt3.*
2828
import org.jetbrains.kotlin.kapt3.javac.KaptTreeMaker
2929
import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject
3030
import org.jetbrains.kotlin.kapt3.util.*
31-
import org.jetbrains.kotlin.psi.KtFile
31+
import org.jetbrains.kotlin.psi.*
3232
import org.jetbrains.kotlin.resolve.DescriptorUtils
3333
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
3434
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
35+
import org.jetbrains.kotlin.types.KotlinType
3536
import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
3637
import org.jetbrains.org.objectweb.asm.Opcodes
3738
import org.jetbrains.org.objectweb.asm.Type
@@ -76,7 +77,7 @@ class ClassFileToSourceStubConverter(
7677
get() = _bindings
7778

7879
private val fileManager = kaptContext.context.get(JavaFileManager::class.java) as JavacFileManager
79-
private val treeMaker = TreeMaker.instance(kaptContext.context) as KaptTreeMaker
80+
val treeMaker = TreeMaker.instance(kaptContext.context) as KaptTreeMaker
8081
private val signatureParser = SignatureParser(treeMaker)
8182

8283
private var done = false
@@ -228,7 +229,11 @@ class ClassFileToSourceStubConverter(
228229
val typeExpression = if (isEnum(field.access))
229230
treeMaker.SimpleName(type.className.substringAfterLast('.'))
230231
else
231-
getNotAnonymousType(descriptor) { signatureParser.parseFieldSignature(field.signature, treeMaker.Type(type)) }
232+
getNotAnonymousType(descriptor) {
233+
getNonErrorType((descriptor as? CallableDescriptor)?.returnType,
234+
ktTypeProvider = { (kaptContext.origins[field]?.element as? KtVariableDeclaration)?.typeReference },
235+
ifNonError = { signatureParser.parseFieldSignature(field.signature, treeMaker.Type(type)) })
236+
}
232237

233238
val value = field.value
234239

@@ -288,8 +293,9 @@ class ClassFileToSourceStubConverter(
288293

289294
val exceptionTypes = mapJList(method.exceptions) { treeMaker.FqName(it) }
290295

291-
val genericSignature = signatureParser.parseMethodSignature(method.signature, parameters, exceptionTypes, jcReturnType)
292-
val returnType = getNotAnonymousType(descriptor) { genericSignature.returnType }
296+
val valueParametersFromDescriptor = descriptor.valueParameters
297+
val (genericSignature, returnType) = extractMethodSignatureTypes(
298+
descriptor, exceptionTypes, jcReturnType, method, parameters, valueParametersFromDescriptor)
293299

294300
val defaultValue = method.annotationDefault?.let { convertLiteralExpression(it) }
295301

@@ -329,6 +335,65 @@ class ClassFileToSourceStubConverter(
329335
body, defaultValue)
330336
}
331337

338+
private fun extractMethodSignatureTypes(
339+
descriptor: CallableDescriptor,
340+
exceptionTypes: JavacList<JCExpression>,
341+
jcReturnType: JCExpression?, method: MethodNode,
342+
parameters: JavacList<JCVariableDecl>,
343+
valueParametersFromDescriptor: List<ValueParameterDescriptor>
344+
): Pair<SignatureParser.MethodGenericSignature, JCExpression?> {
345+
val genericSignature = signatureParser.parseMethodSignature(
346+
method.signature, parameters, exceptionTypes, jcReturnType,
347+
nonErrorParameterTypeProvider = { index, lazyType ->
348+
if (descriptor is PropertySetterDescriptor && valueParametersFromDescriptor.size == 1 && index == 0) {
349+
getNonErrorType(descriptor.correspondingProperty.returnType,
350+
ktTypeProvider = { (kaptContext.origins[method]?.element as? KtVariableDeclaration)?.typeReference },
351+
ifNonError = { lazyType() })
352+
}
353+
else if (descriptor is FunctionDescriptor && valueParametersFromDescriptor.size == parameters.size) {
354+
getNonErrorType(valueParametersFromDescriptor[index].type,
355+
ktTypeProvider = {
356+
(kaptContext.origins[method]?.element as? KtFunction)?.valueParameters?.get(index)?.typeReference
357+
},
358+
ifNonError = { lazyType() })
359+
}
360+
else {
361+
lazyType()
362+
}
363+
})
364+
365+
val returnType = getNotAnonymousType(descriptor) {
366+
getNonErrorType(descriptor.returnType,
367+
ktTypeProvider = {
368+
val element = kaptContext.origins[method]?.element
369+
when (element) {
370+
is KtFunction -> element.typeReference
371+
is KtProperty -> if (method.name.startsWith("get")) element.typeReference else null
372+
else -> null
373+
}
374+
},
375+
ifNonError = { genericSignature.returnType })
376+
}
377+
378+
return Pair(genericSignature, returnType)
379+
}
380+
381+
private inline fun <T : JCExpression?> getNonErrorType(
382+
type: KotlinType?,
383+
ktTypeProvider: () -> KtTypeReference?,
384+
ifNonError: () -> T
385+
): T {
386+
if (type?.containsErrorTypes() ?: false) {
387+
val ktType = ktTypeProvider()
388+
if (ktType != null) {
389+
@Suppress("UNCHECKED_CAST")
390+
return convertKtType(ktType, this) as T
391+
}
392+
}
393+
394+
return ifNonError()
395+
}
396+
332397
private inline fun <T : JCExpression?> getNotAnonymousType(descriptor: DeclarationDescriptor?, f: () -> T): T {
333398
if (descriptor is CallableDescriptor) {
334399
val returnTypeDescriptor = descriptor.returnType?.constructor?.declarationDescriptor

plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/SignatureParserVisitor.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,15 @@ class SignatureParser(val treeMaker: KaptTreeMaker) {
131131
signature: String?,
132132
rawParameters: JavacList<JCVariableDecl>,
133133
rawExceptionTypes: JavacList<JCExpression>,
134-
rawReturnType: JCExpression?
134+
rawReturnType: JCExpression?,
135+
nonErrorParameterTypeProvider: (Int, () -> JCExpression) -> JCExpression
135136
): MethodGenericSignature {
136137
if (signature == null) {
137-
return MethodGenericSignature(JavacList.nil(), rawParameters, rawExceptionTypes, rawReturnType)
138+
val parameters = mapJListIndexed(rawParameters) { index, it ->
139+
val nonErrorType = nonErrorParameterTypeProvider(index) { it.vartype }
140+
treeMaker.VarDef(it.modifiers, it.getName(), nonErrorType, it.initializer)
141+
}
142+
return MethodGenericSignature(JavacList.nil(), parameters, rawExceptionTypes, rawReturnType)
138143
}
139144

140145
val root = parse(signature)
@@ -149,7 +154,9 @@ class SignatureParser(val treeMaker: KaptTreeMaker) {
149154
val offset = rawParameters.size - parameterTypes.size
150155
val jcParameters = mapJListIndexed(parameterTypes) { index, it ->
151156
val rawParameter = rawParameters[index + offset]
152-
treeMaker.VarDef(rawParameter.modifiers, rawParameter.getName(), parseType(it.children.single()), rawParameter.initializer)
157+
val nonErrorType = nonErrorParameterTypeProvider(index) { parseType(it.children.single()) }
158+
159+
treeMaker.VarDef(rawParameter.modifiers, rawParameter.getName(), nonErrorType, rawParameter.initializer)
153160
}
154161
val jcExceptionTypes = mapJList(exceptionTypes) { parseType(it) }
155162
val jcReturnType = if (rawReturnType == null) null else parseType(returnTypes.single().children.single())
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2010-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.kapt3.stubs
18+
19+
import com.sun.tools.javac.code.BoundKind
20+
import com.sun.tools.javac.tree.JCTree
21+
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
22+
import org.jetbrains.kotlin.kapt3.mapJList
23+
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
24+
import org.jetbrains.kotlin.psi.*
25+
import org.jetbrains.kotlin.resolve.BindingContext
26+
import org.jetbrains.kotlin.types.KotlinType
27+
28+
internal fun convertKtType(
29+
reference: KtTypeReference?,
30+
converter: ClassFileToSourceStubConverter,
31+
shouldBeBoxed: Boolean = false,
32+
gotTypeElement: KtTypeElement? = null
33+
): JCTree.JCExpression {
34+
val type = gotTypeElement ?: reference?.typeElement
35+
36+
if (reference != null) {
37+
val kotlinType = converter.kaptContext.bindingContext[BindingContext.TYPE, reference]
38+
if (kotlinType != null && !kotlinType.containsErrorTypes()) {
39+
val signatureWriter = BothSignatureWriter(BothSignatureWriter.Mode.TYPE)
40+
converter.typeMapper.mapType(kotlinType, signatureWriter,
41+
if (shouldBeBoxed) TypeMappingMode.GENERIC_ARGUMENT else TypeMappingMode.DEFAULT)
42+
43+
return SignatureParser(converter.treeMaker).parseFieldSignature(
44+
signatureWriter.toString(), getDefaultTypeForUnknownType(converter))
45+
}
46+
}
47+
48+
return when (type) {
49+
is KtUserType -> convertUserType(type, converter)
50+
is KtNullableType -> {
51+
// Prevent infinite recursion
52+
val innerType = type.innerType ?: return getDefaultTypeForUnknownType(converter)
53+
convertKtType(reference, converter, shouldBeBoxed = true, gotTypeElement = innerType)
54+
}
55+
is KtFunctionType -> convertFunctionType(type, converter)
56+
else -> getDefaultTypeForUnknownType(converter)
57+
}
58+
}
59+
60+
private fun getDefaultTypeForUnknownType(converter: ClassFileToSourceStubConverter) = converter.treeMaker.FqName("error.NonExistentClass")
61+
62+
private fun convertUserType(type: KtUserType, converter: ClassFileToSourceStubConverter): JCTree.JCExpression {
63+
val qualifierExpression = type.qualifier?.let { convertUserType(it, converter) }
64+
val referencedName = type.referencedName ?: "error"
65+
val treeMaker = converter.treeMaker
66+
67+
val baseExpression = if (qualifierExpression == null) {
68+
treeMaker.SimpleName(referencedName)
69+
} else {
70+
treeMaker.Select(qualifierExpression, treeMaker.name(referencedName))
71+
}
72+
73+
val arguments = type.typeArguments
74+
if (arguments.isEmpty()) {
75+
return baseExpression
76+
}
77+
78+
return treeMaker.TypeApply(baseExpression, mapJList(arguments) { convertTypeProjection(it, converter) })
79+
}
80+
81+
private fun convertTypeProjection(type: KtTypeProjection, converter: ClassFileToSourceStubConverter): JCTree.JCExpression {
82+
val reference = type.typeReference
83+
val treeMaker = converter.treeMaker
84+
85+
return when (type.projectionKind) {
86+
KtProjectionKind.IN -> treeMaker.Wildcard(treeMaker.TypeBoundKind(BoundKind.SUPER),
87+
convertKtType(reference, converter, shouldBeBoxed = true))
88+
KtProjectionKind.OUT -> treeMaker.Wildcard(treeMaker.TypeBoundKind(BoundKind.EXTENDS),
89+
convertKtType(reference, converter, shouldBeBoxed = true))
90+
KtProjectionKind.STAR -> treeMaker.Wildcard(treeMaker.TypeBoundKind(BoundKind.UNBOUND), null)
91+
KtProjectionKind.NONE -> return convertKtType(reference, converter, shouldBeBoxed = true)
92+
}
93+
}
94+
95+
private fun convertFunctionType(type: KtFunctionType, converter: ClassFileToSourceStubConverter): JCTree.JCExpression {
96+
val receiverType = type.receiverTypeReference
97+
var parameterTypes = mapJList(type.parameters) { convertKtType(it.typeReference, converter) }
98+
val returnType = convertKtType(type.returnTypeReference, converter)
99+
100+
if (receiverType != null) {
101+
parameterTypes = parameterTypes.prepend(convertKtType(receiverType, converter))
102+
}
103+
104+
parameterTypes = parameterTypes.append(returnType)
105+
106+
val treeMaker = converter.treeMaker
107+
return treeMaker.TypeApply(treeMaker.SimpleName("Function" + (parameterTypes.size - 1)), parameterTypes)
108+
}
109+
110+
fun KotlinType.containsErrorTypes(): Boolean {
111+
if (this.isError) return true
112+
if (this.arguments.any { it.type.containsErrorTypes() }) return true
113+
return false
114+
}

plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
5959
val typeMapper = factory.generationState.typeMapper
6060

6161
val logger = KaptLogger(isVerbose = true, messageCollector = messageCollector)
62-
val kaptContext = KaptContext(logger, classBuilderFactory.compiledClasses,
62+
val kaptContext = KaptContext(logger, factory.generationState.bindingContext, classBuilderFactory.compiledClasses,
6363
classBuilderFactory.origins, processorOptions = emptyMap())
6464
try {
6565
check(kaptContext, typeMapper, txtFile, wholeFile)
@@ -82,9 +82,14 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
8282

8383
abstract class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3Test() {
8484
override fun check(kaptRunner: KaptContext, typeMapper: KotlinTypeMapper, txtFile: File, wholeFile: File) {
85-
val generateNonExistentClass = wholeFile.useLines { lines -> lines.any { it.trim() == "// NON_EXISTENT_CLASS" } }
85+
fun isOptionSet(name: String) = wholeFile.useLines { lines -> lines.any { it.trim() == "// $name" } }
86+
87+
val generateNonExistentClass = isOptionSet("NON_EXISTENT_CLASS")
88+
val validate = !isOptionSet("NO_VALIDATION")
89+
8690
val javaFiles = convert(kaptRunner, typeMapper, generateNonExistentClass)
87-
kaptRunner.compiler.enterTrees(javaFiles)
91+
92+
if (validate) kaptRunner.compiler.enterTrees(javaFiles)
8893

8994
val actualRaw = javaFiles.sortedBy { it.sourceFile.name }.joinToString (FILE_SEPARATOR)
9095
val actual = StringUtil.convertLineSeparators(actualRaw.trim({ it <= ' ' })).trimTrailingWhitespacesAndAddNewlineAtEOF()

plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ public void testNonExistentClass() throws Exception {
186186
doTest(fileName);
187187
}
188188

189+
@TestMetadata("nonExistentClassTypesConversion.kt")
190+
public void testNonExistentClassTypesConversion() throws Exception {
191+
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/nonExistentClassTypesConversion.kt");
192+
doTest(fileName);
193+
}
194+
189195
@TestMetadata("primitiveTypes.kt")
190196
public void testPrimitiveTypes() throws Exception {
191197
String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/primitiveTypes.kt");

plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/JavaKaptContextTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.kapt3.KaptContext
2222
import org.jetbrains.kotlin.kapt3.diagnostic.KaptError
2323
import org.jetbrains.kotlin.kapt3.doAnnotationProcessing
2424
import org.jetbrains.kotlin.kapt3.util.KaptLogger
25+
import org.jetbrains.kotlin.resolve.BindingContext
2526
import org.junit.Assert.*
2627
import org.junit.Test
2728
import java.io.File
@@ -70,7 +71,7 @@ class JavaKaptContextTest {
7071
}
7172
}
7273

73-
return true;
74+
return true
7475
}
7576

7677
override fun getSupportedAnnotationTypes() = setOf("test.MyAnnotation")
@@ -79,6 +80,7 @@ class JavaKaptContextTest {
7980

8081
private fun doAnnotationProcessing(javaSourceFile: File, processor: Processor, outputDir: File) {
8182
KaptContext(KaptLogger(isVerbose = true, messageCollector = messageCollector),
83+
bindingContext = BindingContext.EMPTY,
8284
compiledClasses = emptyList(),
8385
origins = emptyMap(),
8486
processorOptions = emptyMap()

plugins/kapt3/testData/converter/nonExistentClass.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// NON_EXISTENT_CLASS
2+
// NO_VALIDATION
23

34
@Suppress("UNRESOLVED_REFERENCE")
45
object NonExistentType {

0 commit comments

Comments
 (0)