diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index b45a58c4e57..334e7f5f200 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -29874,6 +29874,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/KotlinOverridesJava5.kt"); } + @Test + @TestMetadata("nonAsciiSecondChar.kt") + public void testNonAsciiSecondChar() throws Exception { + runTest("compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/nonAsciiSecondChar.kt"); + } + @Test @TestMetadata("OnlyAscii.kt") public void testOnlyAscii() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 4c1daa2a57f..e7846695e3e 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -29874,6 +29874,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/KotlinOverridesJava5.kt"); } + @Test + @TestMetadata("nonAsciiSecondChar.kt") + public void testNonAsciiSecondChar() throws Exception { + runTest("compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/nonAsciiSecondChar.kt"); + } + @Test @TestMetadata("OnlyAscii.kt") public void testOnlyAscii() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index 2d1434b1786..348a901b5d8 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -29874,6 +29874,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/KotlinOverridesJava5.kt"); } + @Test + @TestMetadata("nonAsciiSecondChar.kt") + public void testNonAsciiSecondChar() throws Exception { + runTest("compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/nonAsciiSecondChar.kt"); + } + @Test @TestMetadata("OnlyAscii.kt") public void testOnlyAscii() throws Exception { diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/resolve/FirJavaSyntheticNamesProvider.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/resolve/FirJavaSyntheticNamesProvider.kt index 7b90337a9dd..f2cdfd6ab4b 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/resolve/FirJavaSyntheticNamesProvider.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/resolve/FirJavaSyntheticNamesProvider.kt @@ -7,98 +7,14 @@ package org.jetbrains.kotlin.fir.resolve import org.jetbrains.kotlin.fir.NoMutableState import org.jetbrains.kotlin.fir.resolve.calls.FirSyntheticNamesProvider +import org.jetbrains.kotlin.load.java.getPropertyNamesCandidatesByAccessorName +import org.jetbrains.kotlin.load.java.possibleGetMethodNames +import org.jetbrains.kotlin.load.java.setMethodName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly -import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeAsciiOnly -import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly @NoMutableState object FirJavaSyntheticNamesProvider : FirSyntheticNamesProvider() { - private const val GETTER_PREFIX = "get" - private const val SETTER_PREFIX = "set" - private const val IS_PREFIX = "is" - - @Suppress("NOTHING_TO_INLINE") - private inline fun Char.isAsciiUpperCase() = this in 'A'..'Z' - - @Suppress("NOTHING_TO_INLINE") - private inline fun Char.isAsciiLowerCase() = this in 'a'..'z' - - override fun possibleGetterNamesByPropertyName(name: Name): List { - if (name.isSpecial) return emptyList() - val identifier = name.identifier - if (identifier.isEmpty()) return emptyList() - val firstChar = identifier[0] - if (!firstChar.isJavaIdentifierStart() || firstChar in 'A'..'Z') return emptyList() - val result = ArrayList(3) - val standardName = Name.identifier(GETTER_PREFIX + identifier.capitalizeAsciiOnly()) - val length = identifier.length - if (length == 1) { - if (identifier[0].isAsciiLowerCase()) { - // 'x' --> 'getX' but not 'X' --> 'getX' - result += standardName - } - } else if (identifier[1].isAsciiLowerCase()) { - if (identifier[0].isAsciiLowerCase()) { - // 'something' --> 'getSomething' classic case - result += standardName - } - var secondWordStart = 2 - while (secondWordStart < length && identifier[secondWordStart].isAsciiLowerCase()) { - secondWordStart++ - } - val capitalizedFirstWordName = Name.identifier( - GETTER_PREFIX + identifier.substring(0, secondWordStart).toUpperCaseAsciiOnly() + identifier.substring(secondWordStart) - ) - if (secondWordStart >= length || identifier[secondWordStart].isAsciiUpperCase()) { - // 'xyz' --> 'getXYZ' or 'xyzOfSomething' --> 'getXYZOfSomething' - result += capitalizedFirstWordName - } - } else if (length < 3 || !identifier[2].isAsciiUpperCase()) { - // 'xOfSomething' --> 'getXOfSomething' but not 'xYZ' --> 'getXYZ' - result += standardName - } - if (length > IS_PREFIX.length && identifier.startsWith(IS_PREFIX) && !identifier[IS_PREFIX.length].isAsciiLowerCase()) { - // 'isSomething' (but not 'is' or 'issomething') - result += name - } - return result - } - - override fun setterNameByGetterName(name: Name): Name? { - val identifier = name.identifier - val prefix = when { - identifier.startsWith(GETTER_PREFIX) -> GETTER_PREFIX - identifier.startsWith(IS_PREFIX) -> IS_PREFIX - else -> return null - } - return Name.identifier(SETTER_PREFIX + identifier.removePrefix(prefix)) - } - - override fun getterNameBySetterName(name: Name): Name? { - val identifier = name.identifier - val prefix = when { - identifier.startsWith(SETTER_PREFIX) -> SETTER_PREFIX - else -> return null - } - return Name.identifier(GETTER_PREFIX + identifier.removePrefix(prefix)) - } - - override fun possiblePropertyNamesByAccessorName(name: Name): List { - if (name.isSpecial) return emptyList() - val identifier = name.identifier - val prefix = when { - identifier.startsWith(GETTER_PREFIX) -> GETTER_PREFIX - identifier.startsWith(IS_PREFIX) -> "" - identifier.startsWith(SETTER_PREFIX) -> SETTER_PREFIX - else -> return emptyList() - } - val withoutPrefix = identifier.removePrefix(prefix) - val withoutPrefixName = Name.identifier(withoutPrefix.decapitalizeAsciiOnly()) - return if (prefix == SETTER_PREFIX) { - listOf(withoutPrefixName, Name.identifier(IS_PREFIX + withoutPrefix)) - } else { - listOf(withoutPrefixName) - } - } + override fun possibleGetterNamesByPropertyName(name: Name): List = possibleGetMethodNames(name) + override fun setterNameByGetterName(name: Name): Name = setMethodName(getMethodName = name) + override fun possiblePropertyNamesByAccessorName(name: Name): List = getPropertyNamesCandidatesByAccessorName(name) } diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/FirSyntheticNamesProvider.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/FirSyntheticNamesProvider.kt index d7f54115c64..be50cb4ac5e 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/FirSyntheticNamesProvider.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/FirSyntheticNamesProvider.kt @@ -11,8 +11,7 @@ import org.jetbrains.kotlin.name.Name abstract class FirSyntheticNamesProvider : FirSessionComponent { abstract fun possibleGetterNamesByPropertyName(name: Name): List - abstract fun setterNameByGetterName(name: Name): Name? - abstract fun getterNameBySetterName(name: Name): Name? + abstract fun setterNameByGetterName(name: Name): Name abstract fun possiblePropertyNamesByAccessorName(name: Name): List } diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/Synthetics.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/Synthetics.kt index a30e40eae04..9e913ecb976 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/Synthetics.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/resolve/calls/Synthetics.kt @@ -85,33 +85,31 @@ class FirSyntheticPropertiesScope( var matchingSetter: FirSimpleFunction? = null if (getterReturnType != null) { val setterName = syntheticNamesProvider.setterNameByGetterName(getterName) - if (setterName != null) { - baseScope.processFunctionsByName(setterName, fun(setterSymbol: FirFunctionSymbol<*>) { - if (matchingSetter != null) return - val setter = setterSymbol.fir as? FirSimpleFunction ?: return - val parameter = setter.valueParameters.singleOrNull() ?: return - if (setter.typeParameters.isNotEmpty() || setter.isStatic) return - val parameterType = (parameter.returnTypeRef as? FirResolvedTypeRef)?.type ?: return - // TODO: at this moment it works for cases like - // class Base { - // void setSomething(Object value) {} - // } - // class Derived extends Base { - // String getSomething() { return ""; } - // } - // In FE 1.0, we should have also Object getSomething() in class Base for this to work - // I think details here are worth designing - if (!AbstractTypeChecker.isSubtypeOf( - session.typeContext, - getterReturnType.withNullability(NOT_NULL, session.typeContext), - parameterType.withNullability(NOT_NULL, session.typeContext) - ) - ) { - return - } - matchingSetter = setter - }) - } + baseScope.processFunctionsByName(setterName, fun(setterSymbol: FirFunctionSymbol<*>) { + if (matchingSetter != null) return + val setter = setterSymbol.fir as? FirSimpleFunction ?: return + val parameter = setter.valueParameters.singleOrNull() ?: return + if (setter.typeParameters.isNotEmpty() || setter.isStatic) return + val parameterType = (parameter.returnTypeRef as? FirResolvedTypeRef)?.type ?: return + // TODO: at this moment it works for cases like + // class Base { + // void setSomething(Object value) {} + // } + // class Derived extends Base { + // String getSomething() { return ""; } + // } + // In FE 1.0, we should have also Object getSomething() in class Base for this to work + // I think details here are worth designing + if (!AbstractTypeChecker.isSubtypeOf( + session.typeContext, + getterReturnType.withNullability(NOT_NULL, session.typeContext), + parameterType.withNullability(NOT_NULL, session.typeContext) + ) + ) { + return + } + matchingSetter = setter + }) } val classLookupTag = getterSymbol.originalOrSelf().dispatchReceiverClassOrNull() diff --git a/compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/nonAsciiSecondChar.kt b/compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/nonAsciiSecondChar.kt new file mode 100644 index 00000000000..62112a07060 --- /dev/null +++ b/compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/nonAsciiSecondChar.kt @@ -0,0 +1,11 @@ +// FIR_IDENTICAL +// SKIP_TXT +// FILE: A.java +public class A { + public String getS4ClassRepresentation() { return ""; } +} + +// FILE: main.kt +fun foo(a: A) { + a.s4ClassRepresentation.length +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 476239b5da0..5a9e4507ef5 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -29970,6 +29970,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/KotlinOverridesJava5.kt"); } + @Test + @TestMetadata("nonAsciiSecondChar.kt") + public void testNonAsciiSecondChar() throws Exception { + runTest("compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/nonAsciiSecondChar.kt"); + } + @Test @TestMetadata("OnlyAscii.kt") public void testOnlyAscii() throws Exception {