diff --git a/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/FirCallableCompletionContributor.kt b/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/FirCallableCompletionContributor.kt index 2b5a6b4a7773..73be1fe5130e 100644 --- a/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/FirCallableCompletionContributor.kt +++ b/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/FirCallableCompletionContributor.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion import org.jetbrains.kotlin.analysis.api.scopes.KaScope import org.jetbrains.kotlin.analysis.api.signatures.KaCallableSignature import org.jetbrains.kotlin.analysis.api.signatures.KaFunctionSignature +import org.jetbrains.kotlin.analysis.api.signatures.KaVariableSignature import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.types.KaErrorType import org.jetbrains.kotlin.analysis.api.types.KaType @@ -38,6 +39,7 @@ import org.jetbrains.kotlin.idea.completion.lookups.factories.FunctionInsertionH import org.jetbrains.kotlin.idea.completion.reference import org.jetbrains.kotlin.idea.completion.weighers.CallableWeigher.callableWeight import org.jetbrains.kotlin.idea.completion.weighers.WeighingContext +import org.jetbrains.kotlin.idea.core.NotPropertiesService import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.positionContext.KotlinNameReferencePositionContext import org.jetbrains.kotlin.idea.util.positionContext.KotlinSimpleNameReferencePositionContext @@ -49,6 +51,8 @@ import org.jetbrains.kotlin.resolve.ArrayFqNames import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.utils.exceptions.KotlinIllegalArgumentExceptionWithAttachments +private val NOT_PROPERTIES = NotPropertiesService.DEFAULT.toSet() + internal open class FirCallableCompletionContributor( parameters: KotlinFirCompletionParameters, sink: LookupElementSink, @@ -649,6 +653,25 @@ internal open class FirCallableCompletionContributor( } } + /** + * If the signature is that of certain synthetic java properties (e.g. `AtomicInteger.getAndIncrement`), + * we do not want to use the synthetic property (e.g. `andIncrement`) because it would be unnatural as they are not real properties. + * For these cases, this function will return the signature of the underlying Java getter instead. + * + * @see NotPropertiesService + */ + context(KaSession) + @OptIn(KaExperimentalApi::class) + private fun KaCallableSignature<*>.getJavaGetterSignatureIfNotProperty(): KaCallableSignature<*>? { + if (this !is KaVariableSignature<*>) return null + val symbol = symbol + if (symbol !is KaSyntheticJavaPropertySymbol || symbol.javaSetterSymbol != null) return null + + val fqName = symbol.javaGetterSymbol.callableId?.asSingleFqName()?.asString() ?: return null + if (fqName !in NOT_PROPERTIES) return null + return symbol.javaGetterSymbol.asSignature() + } + /** * Note, that [isImportDefinitelyNotRequired] should be set to true only if the callable is available without import, and it doesn't * require import or fully-qualified name to be resolved unambiguously. @@ -659,11 +682,19 @@ internal open class FirCallableCompletionContributor( scopeKind: KaScopeKind, isImportDefinitelyNotRequired: Boolean = false, options: CallableInsertionOptions = getOptions(signature, isImportDefinitelyNotRequired), - ): CallableWithMetadataForCompletion = CallableWithMetadataForCompletion( - _signature = signature, - options = options, - symbolOrigin = CompletionSymbolOrigin.Scope(scopeKind), - ) + ): CallableWithMetadataForCompletion { + val javaGetterIfNotProperty = signature.getJavaGetterSignatureIfNotProperty() + val optionsToUse = if (javaGetterIfNotProperty != null && options.insertionStrategy == CallableInsertionStrategy.AsIdentifier) { + options.copy(insertionStrategy = CallableInsertionStrategy.AsCall) + } else { + options + } + return CallableWithMetadataForCompletion( + _signature = javaGetterIfNotProperty ?: signature, + options = optionsToUse, + symbolOrigin = CompletionSymbolOrigin.Scope(scopeKind), + ) + } private fun isUninitializedCallable( position: PsiElement, diff --git a/plugins/kotlin/completion/testData/basic/java/syntheticExtensions/ShowAccessorsForAtomicOperations.kt b/plugins/kotlin/completion/testData/basic/java/syntheticExtensions/ShowAccessorsForAtomicOperations.kt new file mode 100644 index 000000000000..cb3264b13c12 --- /dev/null +++ b/plugins/kotlin/completion/testData/basic/java/syntheticExtensions/ShowAccessorsForAtomicOperations.kt @@ -0,0 +1,17 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +import java.util.concurrent.atomic.AtomicInteger + +fun test(a: AtomicInteger) { + a. +} + +// EXIST: getAndDecrement +// EXIST: getAndIncrement +// EXIST: getAndSet +// EXIST: getAndUpdate +// EXIST: getAndAdd +// ABSENT: andDecrement +// ABSENT: andIncrement +// ABSENT: andSet +// ABSENT: andUpdate +// ABSENT: andAdd \ No newline at end of file diff --git a/plugins/kotlin/completion/testData/basic/java/syntheticExtensions/SuppressedByNotPropertyList.kt b/plugins/kotlin/completion/testData/basic/java/syntheticExtensions/SuppressedByNotPropertyList.kt index fa5dd4b132e6..d20708ec55f2 100644 --- a/plugins/kotlin/completion/testData/basic/java/syntheticExtensions/SuppressedByNotPropertyList.kt +++ b/plugins/kotlin/completion/testData/basic/java/syntheticExtensions/SuppressedByNotPropertyList.kt @@ -5,6 +5,5 @@ fun main(args: Array) { s. } -// IGNORE_K2 // EXIST: {"lookupString":"getInputStream","tailText":"()","typeText":"InputStream!","attributes":"bold","allLookupStrings":"getInputStream","itemText":"getInputStream"} // ABSENT: inputStream \ No newline at end of file diff --git a/plugins/kotlin/completion/tests-k1/test/org/jetbrains/kotlin/idea/completion/test/K1JvmBasicCompletionTestGenerated.java b/plugins/kotlin/completion/tests-k1/test/org/jetbrains/kotlin/idea/completion/test/K1JvmBasicCompletionTestGenerated.java index f99f3b2d2cd8..2551398e9104 100644 --- a/plugins/kotlin/completion/tests-k1/test/org/jetbrains/kotlin/idea/completion/test/K1JvmBasicCompletionTestGenerated.java +++ b/plugins/kotlin/completion/tests-k1/test/org/jetbrains/kotlin/idea/completion/test/K1JvmBasicCompletionTestGenerated.java @@ -4482,6 +4482,11 @@ public abstract class K1JvmBasicCompletionTestGenerated extends AbstractK1JvmBas runTest("../testData/basic/java/syntheticExtensions/SafeCall.kt"); } + @TestMetadata("ShowAccessorsForAtomicOperations.kt") + public void testShowAccessorsForAtomicOperations() throws Exception { + runTest("../testData/basic/java/syntheticExtensions/ShowAccessorsForAtomicOperations.kt"); + } + @TestMetadata("ShowGetSetOnSecondCompletion.kt") public void testShowGetSetOnSecondCompletion() throws Exception { runTest("../testData/basic/java/syntheticExtensions/ShowGetSetOnSecondCompletion.kt"); diff --git a/plugins/kotlin/completion/tests-k1/test/org/jetbrains/kotlin/idea/completion/test/K1MLPerformanceCompletionTestGenerated.java b/plugins/kotlin/completion/tests-k1/test/org/jetbrains/kotlin/idea/completion/test/K1MLPerformanceCompletionTestGenerated.java index f96ffa52762b..b949f597a01d 100644 --- a/plugins/kotlin/completion/tests-k1/test/org/jetbrains/kotlin/idea/completion/test/K1MLPerformanceCompletionTestGenerated.java +++ b/plugins/kotlin/completion/tests-k1/test/org/jetbrains/kotlin/idea/completion/test/K1MLPerformanceCompletionTestGenerated.java @@ -4482,6 +4482,11 @@ public abstract class K1MLPerformanceCompletionTestGenerated extends AbstractK1M runTest("../testData/basic/java/syntheticExtensions/SafeCall.kt"); } + @TestMetadata("ShowAccessorsForAtomicOperations.kt") + public void testShowAccessorsForAtomicOperations() throws Exception { + runTest("../testData/basic/java/syntheticExtensions/ShowAccessorsForAtomicOperations.kt"); + } + @TestMetadata("ShowGetSetOnSecondCompletion.kt") public void testShowGetSetOnSecondCompletion() throws Exception { runTest("../testData/basic/java/syntheticExtensions/ShowGetSetOnSecondCompletion.kt"); diff --git a/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/K2JvmBasicCompletionTestGenerated.java b/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/K2JvmBasicCompletionTestGenerated.java index da757d9e8db1..016f81a24943 100644 --- a/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/K2JvmBasicCompletionTestGenerated.java +++ b/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/K2JvmBasicCompletionTestGenerated.java @@ -4482,6 +4482,11 @@ public abstract class K2JvmBasicCompletionTestGenerated extends AbstractK2JvmBas runTest("../../completion/testData/basic/java/syntheticExtensions/SafeCall.kt"); } + @TestMetadata("ShowAccessorsForAtomicOperations.kt") + public void testShowAccessorsForAtomicOperations() throws Exception { + runTest("../../completion/testData/basic/java/syntheticExtensions/ShowAccessorsForAtomicOperations.kt"); + } + @TestMetadata("ShowGetSetOnSecondCompletion.kt") public void testShowGetSetOnSecondCompletion() throws Exception { runTest("../../completion/testData/basic/java/syntheticExtensions/ShowGetSetOnSecondCompletion.kt");