From 112045f56c04e8de7126fdbd0f2068929aa0e098 Mon Sep 17 00:00:00 2001 From: Marius Volkhart Date: Thu, 26 Feb 2026 12:20:00 -0500 Subject: [PATCH] Fix getJvmName for @JvmRecord data class properties @JvmRecord data classes compile to Java records, whose component accessors use bare property names (e.g. name()) rather than bean-style getters (getName()). ResolverAAImpl.getJvmName unconditionally used JvmAbi.getterName() which always added the get prefix. Add a check for the @JvmRecord annotation alongside the existing annotation class check, since both use bare property names as accessor names. Fixes #2812 --- .../devtools/ksp/impl/ResolverAAImpl.kt | 15 +++++-- .../google/devtools/ksp/test/KSPAA17Test.kt | 43 +++++++++++++++++++ kotlin-analysis-api/testData/jvmNameRecord.kt | 12 ++++++ .../ksp/processor/JvmNameRecordProcessor.kt | 28 ++++++++++++ 4 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/KSPAA17Test.kt create mode 100644 kotlin-analysis-api/testData/jvmNameRecord.kt create mode 100644 test-utils/src/main/kotlin/com/google/devtools/ksp/processor/JvmNameRecordProcessor.kt diff --git a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt index 5288f22c54..afb64bb6bd 100644 --- a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt +++ b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt @@ -452,11 +452,18 @@ class ResolverAAImpl( return it } - if (accessor.receiver.closestClassDeclaration()?.classKind == ClassKind.ANNOTATION_CLASS) { - return accessor.receiver.simpleName.asString() - } - val name = accessor.receiver.simpleName.asString() + val containingClass = accessor.receiver.closestClassDeclaration() + + // Annotation classes and @JvmRecord data classes both use bare property names + // as accessor names (no get/set prefix). + if (containingClass?.classKind == ClassKind.ANNOTATION_CLASS || + containingClass != null && containingClass.annotations.any { + it.annotationType.resolve().declaration.qualifiedName?.asString() == "kotlin.jvm.JvmRecord" + } + ) { + return name + } // https://kotlinlang.org/docs/java-to-kotlin-interop.html#properties val prefixedName = when (accessor) { is KSPropertyGetter -> JvmAbi.getterName(name) diff --git a/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/KSPAA17Test.kt b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/KSPAA17Test.kt new file mode 100644 index 0000000000..e03fb8f1c0 --- /dev/null +++ b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/test/KSPAA17Test.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Google LLC + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * 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 com.google.devtools.ksp.test + +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.test.TestMetadata +import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder +import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.Execution +import org.junit.jupiter.api.parallel.ExecutionMode + +@Execution(ExecutionMode.SAME_THREAD) +class KSPAA17Test : AbstractKSPAATest() { + + override fun configureTest(builder: TestConfigurationBuilder) { + builder.defaultDirectives { + -JvmEnvironmentConfigurationDirectives.JVM_TARGET + JvmEnvironmentConfigurationDirectives.JVM_TARGET with JvmTarget.JVM_17 + } + } + + @TestMetadata("jvmNameRecord.kt") + @Test + fun testJvmNameRecord() { + runTest("../kotlin-analysis-api/testData/jvmNameRecord.kt") + } +} diff --git a/kotlin-analysis-api/testData/jvmNameRecord.kt b/kotlin-analysis-api/testData/jvmNameRecord.kt new file mode 100644 index 0000000000..a4d239b63a --- /dev/null +++ b/kotlin-analysis-api/testData/jvmNameRecord.kt @@ -0,0 +1,12 @@ +// TEST PROCESSOR: JvmNameRecordProcessor +// EXPECTED: +// (x, null), (y, null) +// (x, null), (y, null) +// END +// MODULE: main +// FILE: TestRecordClass.kt +@JvmRecord +data class TestRecordClass(val x: Int, val y: String) +// FILE: TestLibRecordClass.kt +@JvmRecord +data class TestLibRecordClass(val x: Int, val y: String) diff --git a/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/JvmNameRecordProcessor.kt b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/JvmNameRecordProcessor.kt new file mode 100644 index 0000000000..38e67b861e --- /dev/null +++ b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/JvmNameRecordProcessor.kt @@ -0,0 +1,28 @@ +package com.google.devtools.ksp.processor + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getClassDeclarationByName +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSAnnotated + +class JvmNameRecordProcessor : AbstractTestProcessor() { + val results = mutableListOf() + override fun toResult(): List { + return results + } + + @OptIn(KspExperimental::class) + override fun process(resolver: Resolver): List { + listOf("TestRecordClass", "TestLibRecordClass").forEach { clsName -> + resolver.getClassDeclarationByName(clsName)?.let { cls -> + results.add( + cls.getAllProperties().map { + "(${it.getter?.let { resolver.getJvmName(it) }}, " + + "${it.setter?.let { resolver.getJvmName(it) }})" + }.toList().joinToString() + ) + } + } + return emptyList() + } +}