Skip to content

Commit 12e3cbe

Browse files
authored
Add APIs to get JVM names of functions and property accessors (#141)
This API `getJvmName` and `mapToJvmSignature` are also marked as `KspExperimental` and require explicit opt-in. See https://kotlinlang.org/docs/reference/opt-in-requirements.html for details.
1 parent 489dbfb commit 12e3cbe

File tree

7 files changed

+282
-4
lines changed

7 files changed

+282
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.google.devtools.ksp
2+
3+
@RequiresOptIn(message = "This API is experimental." +
4+
"It may be changed in the future without notice or might be removed.")
5+
@Retention(AnnotationRetention.BINARY)
6+
annotation class KspExperimental

api/src/main/kotlin/com/google/devtools/ksp/processing/Resolver.kt

+45
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package com.google.devtools.ksp.processing
2020

21+
import com.google.devtools.ksp.KspExperimental
2122
import com.google.devtools.ksp.symbol.*
2223

2324
/**
@@ -75,6 +76,7 @@ interface Resolver {
7576
/**
7677
* map a declaration to jvm signature.
7778
*/
79+
@KspExperimental
7880
fun mapToJvmSignature(declaration: KSDeclaration): String
7981

8082
/**
@@ -142,4 +144,47 @@ interface Resolver {
142144
function: KSFunctionDeclaration,
143145
containing: KSType
144146
): KSFunction
147+
148+
/**
149+
* Returns the jvm name of the given function.
150+
*
151+
* The jvm name of a function might depend on the Kotlin Compiler version hence it is not guaranteed to be
152+
* compatible between different compiler versions except for the rules outlined in the Java interoperability
153+
* documentation: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html.
154+
*
155+
* If the [declaration] is annotated with [JvmName], that name will be returned from this function.
156+
*
157+
* Note that this might be different from the name declared in the Kotlin source code in two cases:
158+
* a) If the function receives or returns an inline class, its name will be mangled according to
159+
* https://kotlinlang.org/docs/reference/inline-classes.html#mangling.
160+
* b) If the function is declared as internal, it will include a suffix with the module name.
161+
*
162+
* NOTE: As inline classes are an experimental feature, the result of this function might change based on the
163+
* kotlin version used in the project.
164+
*/
165+
@KspExperimental
166+
fun getJvmName(declaration: KSFunctionDeclaration): String
167+
168+
/**
169+
* Returns the jvm name of the given property accessor.
170+
*
171+
* The jvm name of an accessor might depend on the Kotlin Compiler version hence it is not guaranteed to be
172+
* compatible between different compiler versions except for the rules outlined in the Java interoperability
173+
* documentation: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html.
174+
*
175+
* If the [accessor] is annotated with [JvmName], that name will be returned from this function.
176+
*
177+
* By default, this name will match the name calculated according to
178+
* https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#properties.
179+
* Note that the result of this function might be different from that name in two cases:
180+
* a) If the property's type is an internal class, accessor's name will be mangled according to
181+
* https://kotlinlang.org/docs/reference/inline-classes.html#mangling.
182+
* b) If the function is declared as internal, it will include a suffix with the module name.
183+
*
184+
* NOTE: As inline classes are an experimental feature, the result of this function might change based on the
185+
* kotlin version used in the project.
186+
* see: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#properties
187+
*/
188+
@KspExperimental
189+
fun getJvmName(accessor: KSPropertyAccessor): String
145190
}

compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt

+35-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package com.google.devtools.ksp.processing.impl
2020

21+
import com.google.devtools.ksp.KspExperimental
2122
import com.google.devtools.ksp.closestClassDeclaration
2223
import com.google.devtools.ksp.isOpen
2324
import com.google.devtools.ksp.isVisibleFrom
@@ -31,7 +32,6 @@ import org.jetbrains.kotlin.container.get
3132
import org.jetbrains.kotlin.descriptors.*
3233
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
3334
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
34-
import com.google.devtools.ksp.closestClassDeclaration
3535
import com.google.devtools.ksp.processing.KSBuiltIns
3636
import com.google.devtools.ksp.processing.Resolver
3737
import com.google.devtools.ksp.symbol.*
@@ -42,6 +42,9 @@ import com.google.devtools.ksp.symbol.impl.java.*
4242
import com.google.devtools.ksp.symbol.impl.kotlin.*
4343
import com.google.devtools.ksp.symbol.impl.synthetic.KSTypeReferenceSyntheticImpl
4444
import com.google.devtools.ksp.symbol.impl.synthetic.KSConstructorSyntheticImpl
45+
import com.google.devtools.ksp.symbol.impl.synthetic.KSPropertyGetterSyntheticImpl
46+
import com.google.devtools.ksp.symbol.impl.synthetic.KSPropertySetterSyntheticImpl
47+
import org.jetbrains.kotlin.codegen.OwnerKind
4548
import org.jetbrains.kotlin.load.java.components.TypeUsage
4649
import org.jetbrains.kotlin.load.java.lazy.JavaResolverComponents
4750
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
@@ -55,7 +58,6 @@ import org.jetbrains.kotlin.load.java.structure.impl.JavaFieldImpl
5558
import org.jetbrains.kotlin.load.java.structure.impl.JavaMethodImpl
5659
import org.jetbrains.kotlin.load.java.structure.impl.JavaTypeImpl
5760
import org.jetbrains.kotlin.load.java.structure.impl.JavaTypeParameterImpl
58-
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
5961
import org.jetbrains.kotlin.name.FqName
6062
import org.jetbrains.kotlin.name.Name
6163
import org.jetbrains.kotlin.psi.*
@@ -149,8 +151,6 @@ class ResolverImpl(
149151
}
150152
}
151153
ksFiles.map { it.accept(visitor, Unit) }
152-
153-
154154
}
155155

156156
override fun getAllFiles(): List<KSFile> {
@@ -248,6 +248,7 @@ class ResolverImpl(
248248
return KSTypeReferenceSyntheticImpl.getCached(type)
249249
}
250250

251+
@KspExperimental
251252
override fun mapToJvmSignature(declaration: KSDeclaration): String {
252253
return when (declaration) {
253254
is KSClassDeclaration -> resolveClassDeclaration(declaration)?.let { typeMapper.mapType(it).descriptor } ?: ""
@@ -363,6 +364,16 @@ class ResolverImpl(
363364
} as PropertyDescriptor?
364365
}
365366

367+
fun resolvePropertyAccessorDeclaration(accessor: KSPropertyAccessor): PropertyAccessorDescriptor? {
368+
return when (accessor) {
369+
is KSPropertyAccessorDescriptorImpl -> accessor.descriptor
370+
is KSPropertyAccessorImpl -> resolveDeclaration(accessor.ktPropertyAccessor)
371+
is KSPropertySetterSyntheticImpl -> resolvePropertyDeclaration(accessor.receiver)?.setter
372+
is KSPropertyGetterSyntheticImpl -> resolvePropertyDeclaration(accessor.receiver)?.getter
373+
else -> throw IllegalStateException("unexpected class: ${accessor.javaClass}")
374+
} as PropertyAccessorDescriptor?
375+
}
376+
366377
fun resolveJavaType(psi: PsiType): KotlinType {
367378
val javaType = JavaTypeImpl.create(psi)
368379
return javaTypeResolver.transformJavaType(javaType, TypeUsage.COMMON.toAttributes())
@@ -491,6 +502,26 @@ class ResolverImpl(
491502
}
492503
}
493504

505+
@KspExperimental
506+
override fun getJvmName(accessor: KSPropertyAccessor) :String {
507+
val descriptor = resolvePropertyAccessorDeclaration(accessor)
508+
509+
return descriptor?.let {
510+
// KotlinTypeMapper.mapSignature always uses OwnerKind.IMPLEMENTATION
511+
typeMapper.mapFunctionName(descriptor, OwnerKind.IMPLEMENTATION)
512+
} ?: error("Cannot find descriptor for $accessor")
513+
}
514+
515+
@KspExperimental
516+
override fun getJvmName(declaration: KSFunctionDeclaration) :String {
517+
// function names might be mangled if they receive inline class parameters or they are internal
518+
val descriptor = resolveFunctionDeclaration(declaration)
519+
return descriptor?.let {
520+
// KotlinTypeMapper.mapSignature always uses OwnerKind.IMPLEMENTATION
521+
typeMapper.mapFunctionName(descriptor, OwnerKind.IMPLEMENTATION)
522+
} ?: error("Cannot find descriptor for $declaration")
523+
}
524+
494525
override fun getTypeArgument(typeRef: KSTypeReference, variance: Variance): KSTypeArgument {
495526
return KSTypeArgumentLiteImpl.getCached(typeRef, variance)
496527
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.google.devtools.ksp.processor
2+
3+
import com.google.devtools.ksp.KspExperimental
4+
import com.google.devtools.ksp.getClassDeclarationByName
5+
import com.google.devtools.ksp.processing.Resolver
6+
import com.google.devtools.ksp.symbol.KSClassDeclaration
7+
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
8+
import com.google.devtools.ksp.symbol.KSNode
9+
import com.google.devtools.ksp.symbol.KSPropertyGetter
10+
import com.google.devtools.ksp.symbol.KSPropertySetter
11+
import com.google.devtools.ksp.symbol.Modifier
12+
import com.google.devtools.ksp.visitor.KSTopDownVisitor
13+
14+
@KspExperimental
15+
@Suppress("unused") // used by the test code
16+
class MangledNamesProcessor : AbstractTestProcessor() {
17+
private val results = mutableListOf<String>()
18+
override fun toResult() = results
19+
20+
override fun process(resolver: Resolver) {
21+
val mangleSourceNames = mutableMapOf<String, String>()
22+
resolver.getAllFiles().forEach {
23+
it.accept(MangledNamesVisitor(resolver), mangleSourceNames)
24+
}
25+
val mangledDependencyNames = LinkedHashMap<String, String>()
26+
// also collect results from library dependencies to ensure we resolve module name property
27+
resolver.getClassDeclarationByName("libPackage.Foo")?.accept(
28+
MangledNamesVisitor(resolver), mangledDependencyNames
29+
)
30+
results.addAll(
31+
mangleSourceNames.entries.map { (decl, name) ->
32+
"$decl -> $name"
33+
}
34+
)
35+
results.addAll(
36+
mangledDependencyNames.entries.map { (decl, name) ->
37+
"$decl -> $name"
38+
}
39+
)
40+
}
41+
42+
private class MangledNamesVisitor(
43+
val resolver: Resolver
44+
) : KSTopDownVisitor<MutableMap<String, String>, Unit>() {
45+
override fun defaultHandler(node: KSNode, data: MutableMap<String, String>) {
46+
}
47+
48+
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: MutableMap<String, String>) {
49+
if (classDeclaration.modifiers.contains(Modifier.INLINE)) {
50+
// do not visit inline classes
51+
return
52+
}
53+
// put a header for readable output
54+
data[classDeclaration.qualifiedName!!.asString()] = "declarations"
55+
super.visitClassDeclaration(classDeclaration, data)
56+
}
57+
58+
override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: MutableMap<String, String>) {
59+
if (function.simpleName.asString() in IGNORED_FUNCTIONS) return
60+
super.visitFunctionDeclaration(function, data)
61+
data[function.simpleName.asString()] = resolver.getJvmName(function)
62+
}
63+
64+
override fun visitPropertyGetter(getter: KSPropertyGetter, data: MutableMap<String, String>) {
65+
super.visitPropertyGetter(getter, data)
66+
data["get-${getter.receiver.simpleName.asString()}"] = resolver.getJvmName(getter)
67+
}
68+
69+
override fun visitPropertySetter(setter: KSPropertySetter, data: MutableMap<String, String>) {
70+
super.visitPropertySetter(setter, data)
71+
data["set-${setter.receiver.simpleName.asString()}"] = resolver.getJvmName(setter)
72+
}
73+
74+
companion object {
75+
// do not report these functions as they are generated only in byte code and do not affect the test.
76+
val IGNORED_FUNCTIONS = listOf("equals", "hashCode", "toString")
77+
}
78+
}
79+
}

compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processor/MapSignatureProcessor.kt

+2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818

1919
package com.google.devtools.ksp.processor
2020

21+
import com.google.devtools.ksp.KspExperimental
2122
import com.google.devtools.ksp.getClassDeclarationByName
2223
import com.google.devtools.ksp.processing.Resolver
2324

25+
@KspExperimental
2426
class MapSignatureProcessor : AbstractTestProcessor() {
2527
private val result = mutableListOf<String>()
2628

compiler-plugin/src/test/java/com/google/devtools/ksp/test/KotlinKSPTestGenerated.java

+5
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ public void testMakeNullable() throws Exception {
147147
runTest("testData/api/makeNullable.kt");
148148
}
149149

150+
@TestMetadata("mangledNames.kt")
151+
public void testMangledNames() throws Exception {
152+
runTest("testData/api/mangledNames.kt");
153+
}
154+
150155
@TestMetadata(("multipleModules.kt"))
151156
public void testMultipleModules() throws Exception {
152157
runTest("testData/api/multipleModules.kt");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
// WITH_RUNTIME
19+
// TEST PROCESSOR: MangledNamesProcessor
20+
// EXPECTED:
21+
// mainPackage.Foo -> declarations
22+
// get-normalProp -> getNormalProp
23+
// set-normalProp -> setNormalProp
24+
// get-inlineProp -> getInlineProp-HRn7Rpw
25+
// set-inlineProp -> setInlineProp-E03SJzc
26+
// get-internalProp -> getInternalProp$mainModule
27+
// set-internalProp -> setInternalProp$mainModule
28+
// get-internalInlineProp -> getInternalInlineProp-HRn7Rpw$mainModule
29+
// set-internalInlineProp -> setInternalInlineProp-E03SJzc$mainModule
30+
// get-jvmNameProp -> explicitGetterName
31+
// set-jvmNameProp -> explicitSetterName
32+
// normalFun -> normalFun
33+
// hasJvmName -> explicitJvmName
34+
// inlineReceivingFun -> inlineReceivingFun-E03SJzc
35+
// inlineReturningFun -> inlineReturningFun-HRn7Rpw
36+
// internalInlineReceivingFun -> internalInlineReceivingFun-E03SJzc$mainModule
37+
// internalInlineReturningFun -> internalInlineReturningFun-HRn7Rpw$mainModule
38+
// fileLevelInternalFun -> fileLevelInternalFun
39+
// fileLevelInlineReceivingFun -> fileLevelInlineReceivingFun-E03SJzc
40+
// fileLevelInlineReturningFun -> fileLevelInlineReturningFun
41+
// fileLevelInternalInlineReceivingFun -> fileLevelInternalInlineReceivingFun-E03SJzc
42+
// fileLevelInternalInlineReturningFun -> fileLevelInternalInlineReturningFun
43+
// libPackage.Foo -> declarations
44+
// get-inlineProp -> getInlineProp-b_MPbnQ
45+
// set-inlineProp -> setInlineProp-mQ73O9w
46+
// get-internalInlineProp -> getInternalInlineProp-b_MPbnQ$lib
47+
// set-internalInlineProp -> setInternalInlineProp-mQ73O9w$lib
48+
// get-internalProp -> getInternalProp$lib
49+
// set-internalProp -> setInternalProp$lib
50+
// get-jvmNameProp -> explicitGetterName
51+
// set-jvmNameProp -> explicitSetterName
52+
// get-normalProp -> getNormalProp
53+
// set-normalProp -> setNormalProp
54+
// hasJvmName -> explicitJvmName
55+
// inlineReceivingFun -> inlineReceivingFun-mQ73O9w
56+
// inlineReturningFun -> inlineReturningFun-b_MPbnQ
57+
// internalInlineReceivingFun -> internalInlineReceivingFun-mQ73O9w$lib
58+
// internalInlineReturningFun -> internalInlineReturningFun-b_MPbnQ$lib
59+
// normalFun -> normalFun
60+
// END
61+
// MODULE: lib
62+
// FILE: input.kt
63+
/**
64+
* control group
65+
*/
66+
package libPackage;
67+
inline class Inline1(val value:String)
68+
class Foo {
69+
var normalProp:String = TODO()
70+
var inlineProp: Inline1 = TODO()
71+
internal var internalProp: String = TODO()
72+
internal var internalInlineProp: Inline1 = TODO()
73+
@get:JvmName("explicitGetterName")
74+
@set:JvmName("explicitSetterName")
75+
var jvmNameProp:String
76+
fun normalFun() {}
77+
@JvmName("explicitJvmName")
78+
fun hasJvmName() {}
79+
fun inlineReceivingFun(value: Inline1) {}
80+
fun inlineReturningFun(): Inline1 = TODO()
81+
internal fun internalInlineReceivingFun(value: Inline1) {}
82+
internal fun internalInlineReturningFun(): Inline1 = TODO()
83+
}
84+
85+
// MODULE: mainModule(lib)
86+
// FILE: input.kt
87+
package mainPackage;
88+
inline class Inline1(val value:String)
89+
class Foo {
90+
var normalProp:String = TODO()
91+
var inlineProp: Inline1 = TODO()
92+
internal var internalProp: String = TODO()
93+
internal var internalInlineProp: Inline1 = TODO()
94+
@get:JvmName("explicitGetterName")
95+
@set:JvmName("explicitSetterName")
96+
var jvmNameProp:String
97+
fun normalFun() {}
98+
@JvmName("explicitJvmName")
99+
fun hasJvmName() {}
100+
fun inlineReceivingFun(value: Inline1) {}
101+
fun inlineReturningFun(): Inline1 = TODO()
102+
internal fun internalInlineReceivingFun(value: Inline1) {}
103+
internal fun internalInlineReturningFun(): Inline1 = TODO()
104+
}
105+
106+
internal fun fileLevelInternalFun(): Unit = TODO()
107+
fun fileLevelInlineReceivingFun(inline1: Inline1): Unit = TODO()
108+
fun fileLevelInlineReturningFun(): Inline1 = TODO()
109+
fun fileLevelInternalInlineReceivingFun(inline1: Inline1): Unit = TODO()
110+
fun fileLevelInternalInlineReturningFun(): Inline1 = TODO()

0 commit comments

Comments
 (0)