[kotlin] Do not duplicate parameter info type annotation

#KTIJ-31759 Fixed

GitOrigin-RevId: 599c7f06a4e0a18d3daa930d7b199142d63108af
This commit is contained in:
Vladimir Dolzhenko
2024-11-25 12:52:28 +01:00
committed by intellij-monorepo-bot
parent 1a4a0b51b3
commit 4d8f67c14a
11 changed files with 135 additions and 45 deletions

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.base.analysis.api.utils
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotated
import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotationValue
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.name.StandardClassIds.Annotations.ParameterNames
@ApiStatus.Internal
fun KaAnnotationValue.isApplicableTargetSet(expectedTargetCallableId: CallableId): Boolean {
return when (this) {
is KaAnnotationValue.ArrayValue -> values.any { it.isApplicableTargetSet(expectedTargetCallableId) }
is KaAnnotationValue.EnumEntryValue -> callableId == expectedTargetCallableId
else -> false
}
}
fun KaAnnotated.hasApplicableAllowedTarget(annotationValueFilter: (KaAnnotationValue) -> Boolean): Boolean =
annotations
.firstOrNull { it.classId == StandardClassIds.Annotations.Target }
?.arguments
?.filter { it.name == ParameterNames.targetAllowedTargets }
?.any { annotationValueFilter(it.expression) } ?: false

View File

@@ -13,12 +13,14 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtilRt
import com.intellij.openapi.vfs.ReadonlyStatusHandler
import com.intellij.psi.*
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiType
import com.intellij.psi.SmartPointerManager
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotationValue
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisFromWriteAction
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisFromWriteAction
@@ -26,16 +28,16 @@ import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource
import org.jetbrains.kotlin.analysis.api.symbols.markers.KaAnnotatedSymbol
import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.idea.base.analysis.api.utils.hasApplicableAllowedTarget
import org.jetbrains.kotlin.idea.base.analysis.api.utils.isApplicableTargetSet
import org.jetbrains.kotlin.idea.base.codeInsight.ShortenReferencesFacility
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageUtil
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.*
import org.jetbrains.kotlin.name.StandardClassIds.Annotations.ParameterNames
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.types.Variance
@@ -223,33 +225,15 @@ object K2CreatePropertyFromUsageBuilder {
allowAnalysisOnEdt {
analyze(declaration) {
val symbol = findClass(classId) as? KaAnnotatedSymbol ?: return false
val annotationValues =
symbol.annotations
.firstOrNull { it.classId?.asSingleFqName() == StandardNames.FqNames.target }
?.arguments
?.filter { it.name == ParameterNames.targetAllowedTargets }
?.map { it.expression }
?: return false
for(value in annotationValues) {
if (value.isApplicableTargetSet(expectedTargetCallableId)) {
return true
}
return symbol.hasApplicableAllowedTarget {
it.isApplicableTargetSet(expectedTargetCallableId)
}
return false
}
}
}
return false
}
private fun KaAnnotationValue.isApplicableTargetSet(expectedTargetCallableId: CallableId): Boolean {
return when (this) {
is KaAnnotationValue.ArrayValue -> values.any { it.isApplicableTargetSet(expectedTargetCallableId) }
is KaAnnotationValue.EnumEntryValue -> callableId == expectedTargetCallableId
else -> false
}
}
private val fieldAnnotationTargetCallableId: CallableId =
CallableId(StandardClassIds.AnnotationTarget, Name.identifier(KotlinTarget.FIELD.name))

View File

@@ -13,21 +13,24 @@ import com.intellij.ui.JBColor
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotation
import org.jetbrains.kotlin.analysis.api.components.KaSubtypingErrorTypePolicy
import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource
import org.jetbrains.kotlin.analysis.api.signatures.KaVariableSignature
import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol
import org.jetbrains.kotlin.analysis.api.types.KaErrorType
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.idea.base.analysis.api.utils.CallParameterInfoProvider
import org.jetbrains.kotlin.idea.base.analysis.api.utils.collectCallCandidates
import org.jetbrains.kotlin.idea.base.analysis.api.utils.defaultValue
import org.jetbrains.kotlin.idea.base.analysis.api.utils.*
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.codeinsights.impl.base.parameterInfo.KotlinParameterInfoBase
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.NULLABILITY_ANNOTATIONS
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.startOffset
@@ -40,21 +43,21 @@ class KotlinHighLevelFunctionParameterInfoHandler :
KotlinHighLevelParameterInfoWithCallHandlerBase<KtValueArgumentList, KtValueArgument>(
KtValueArgumentList::class, KtValueArgument::class
) {
override fun getActualParameters(arguments: KtValueArgumentList) = arguments.arguments.toTypedArray()
override fun getActualParameters(arguments: KtValueArgumentList): Array<KtValueArgument?> = arguments.arguments.toTypedArray()
override fun getActualParametersRBraceType(): KtSingleValueToken = KtTokens.RPAR
override fun getArgumentListAllowedParentClasses() = setOf(KtCallElement::class.java)
override fun getArgumentListAllowedParentClasses(): Set<Class<KtCallElement>> = setOf(KtCallElement::class.java)
}
class KotlinHighLevelLambdaParameterInfoHandler :
KotlinHighLevelParameterInfoWithCallHandlerBase<KtLambdaArgument, KtLambdaArgument>(KtLambdaArgument::class, KtLambdaArgument::class) {
override fun getActualParameters(lambdaArgument: KtLambdaArgument) = arrayOf(lambdaArgument)
override fun getActualParameters(lambdaArgument: KtLambdaArgument): Array<KtLambdaArgument> = arrayOf(lambdaArgument)
override fun getActualParametersRBraceType(): KtSingleValueToken = KtTokens.RBRACE
override fun getArgumentListAllowedParentClasses() = setOf(KtLambdaArgument::class.java)
override fun getArgumentListAllowedParentClasses(): Set<Class<KtLambdaArgument>> = setOf(KtLambdaArgument::class.java)
override fun getCurrentArgumentIndex(context: UpdateParameterInfoContext, argumentList: KtLambdaArgument): Int {
val size = (argumentList.parent as? KtCallElement)?.valueArguments?.size ?: 1
@@ -65,7 +68,7 @@ class KotlinHighLevelLambdaParameterInfoHandler :
class KotlinHighLevelArrayAccessParameterInfoHandler :
KotlinHighLevelParameterInfoWithCallHandlerBase<KtContainerNode, KtExpression>(KtContainerNode::class, KtExpression::class) {
override fun getArgumentListAllowedParentClasses() = setOf(KtArrayAccessExpression::class.java)
override fun getArgumentListAllowedParentClasses(): Set<Class<KtArrayAccessExpression>> = setOf(KtArrayAccessExpression::class.java)
override fun getActualParameters(containerNode: KtContainerNode): Array<out KtExpression> =
containerNode.allChildren.filterIsInstance<KtExpression>().toList().toTypedArray()
@@ -93,13 +96,16 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
)
private const val SINGLE_LINE_PARAMETERS_COUNT = 3
private val ANNOTATION_TARGET_TYPE = CallableId(StandardClassIds.AnnotationTarget, Name.identifier(AnnotationTarget.TYPE.name))
private val ANNOTATION_TARGET_VALUE_PARAMETER = CallableId(StandardClassIds.AnnotationTarget, Name.identifier(AnnotationTarget.VALUE_PARAMETER.name))
}
override fun getActualParameterDelimiterType(): KtSingleValueToken = KtTokens.COMMA
override fun getArgListStopSearchClasses(): Set<Class<out KtElement>> = STOP_SEARCH_CLASSES
override fun getArgumentListClass() = argumentListClass.java
override fun getArgumentListClass(): Class<TArgumentList> = argumentListClass.java
override fun showParameterInfo(element: TArgumentList, context: CreateParameterInfoContext) {
context.showHint(element, element.textRange.startOffset, this)
@@ -261,7 +267,8 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
parameter.symbol.annotations
.filter {
// For primary constructor parameters, the annotation use site must be "param" or unspecified.
it.useSiteTarget == null || it.useSiteTarget == AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER
(it.useSiteTarget == null || it.useSiteTarget == AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER) &&
!it.isAnnotatedWithTypeUseOnly()
}
.mapNotNull { it.classId?.asSingleFqName() }
.filter { it !in NULLABILITY_ANNOTATIONS }
@@ -286,6 +293,14 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
}
}
context(KaSession)
private fun KaAnnotation.isAnnotatedWithTypeUseOnly(): Boolean =
(constructorSymbol?.containingSymbol as? KaClassSymbol)
?.hasApplicableAllowedTarget {
it.isApplicableTargetSet(ANNOTATION_TARGET_TYPE) &&
!it.isApplicableTargetSet(ANNOTATION_TARGET_VALUE_PARAMETER)
} ?: false
private fun calculateHighlightParameterIndex(
arguments: List<KtExpression?>,
currentArgumentIndex: Int,
@@ -542,4 +557,4 @@ abstract class KotlinHighLevelParameterInfoWithCallHandlerBase<TArgumentList : K
data class CandidateInfo(
var callInfo: CallInfo? = null // Populated in updateParameterInfo()
)
}
}

View File

@@ -1,11 +1,9 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.fir.parameterInfo
import org.jetbrains.kotlin.idea.base.test.IgnoreTests
import org.jetbrains.kotlin.idea.fir.invalidateCaches
import org.jetbrains.kotlin.idea.parameterInfo.AbstractParameterInfoTest
import org.jetbrains.kotlin.idea.test.runAll
import java.nio.file.Paths
abstract class AbstractFirParameterInfoTest : AbstractParameterInfoTest() {
@@ -15,10 +13,4 @@ abstract class AbstractFirParameterInfoTest : AbstractParameterInfoTest() {
{ super.tearDown() },
)
}
override fun doTest(fileName: String) {
IgnoreTests.runTestIfNotDisabledByFileDirective(Paths.get(fileName), IgnoreTests.DIRECTIVES.IGNORE_K2) {
super.doTest(fileName)
}
}
}

View File

@@ -771,4 +771,23 @@ public abstract class FirParameterInfoTestGenerated extends AbstractFirParameter
runTest("../../idea/tests/testData/parameterInfo/withLib3/useJavaSAMFromLib.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../idea/tests/testData/parameterInfo/withLib4")
public static class WithLib4 extends AbstractFirParameterInfoTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("annotationWithTypeUse.kt")
public void testAnnotationWithTypeUse() throws Exception {
runTest("../../idea/tests/testData/parameterInfo/withLib4/annotationWithTypeUse.kt");
}
}
}

View File

@@ -16,6 +16,7 @@ import com.intellij.util.PathUtil
import com.intellij.util.ThrowableRunnable
import org.jetbrains.kotlin.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.base.test.IgnoreTests
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils
import org.jetbrains.kotlin.idea.test.*
import org.jetbrains.kotlin.idea.test.util.slashedPath
@@ -24,6 +25,7 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.junit.Assert
import java.io.File
import java.nio.file.Paths
abstract class AbstractParameterInfoTest : KotlinLightCodeInsightFixtureTestCase() {
private var mockLibraryFacility: MockLibraryFacility? = null
@@ -46,7 +48,16 @@ abstract class AbstractParameterInfoTest : KotlinLightCodeInsightFixtureTestCase
ThrowableRunnable { super.tearDown() },
)
protected open fun doTest(fileName: String) {
protected fun doTest(fileName: String) {
IgnoreTests.runTestIfNotDisabledByFileDirective(
Paths.get(fileName),
IgnoreTests.DIRECTIVES.of(pluginMode)
) {
doActualTest(fileName)
}
}
private fun doActualTest(fileName: String) {
val prefix = FileUtil.getNameWithoutExtension(PathUtil.getFileName(fileName))
val mainFile = File(FileUtil.toSystemDependentName(fileName))
mainFile.parentFile

View File

@@ -752,4 +752,23 @@ public abstract class ParameterInfoTestGenerated extends AbstractParameterInfoTe
runTest("testData/parameterInfo/withLib3/useJavaSAMFromLib.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/parameterInfo/withLib4")
public static class WithLib4 extends AbstractParameterInfoTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K1;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("annotationWithTypeUse.kt")
public void testAnnotationWithTypeUse() throws Exception {
runTest("testData/parameterInfo/withLib4/annotationWithTypeUse.kt");
}
}
}

View File

@@ -0,0 +1,12 @@
// IGNORE_K1
package test
import p.ABC
fun foo() {
ABC(<caret>)
}
/*
Text: (<highlight>title: @Ann String!</highlight>), Disabled: false, Strikeout: false, Green: true
*/

View File

@@ -0,0 +1,5 @@
package p;
public class ABC {
public ABC(@Ann String title){}
}

View File

@@ -0,0 +1,8 @@
package p;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.TYPE_USE})
public @interface Ann {
}

View File

@@ -198,7 +198,7 @@ private fun assembleWorkspace(): TWorkspace = workspace(KotlinPluginMode.K2) {
testClass<AbstractFirParameterInfoTest> {
model(
"parameterInfo", pattern = Patterns.forRegex("^([\\w\\-_]+)\\.(kt|java)$"), isRecursive = true,
excludedDirectories = listOf("withLib1/sharedLib", "withLib2/sharedLib", "withLib3/sharedLib")
excludedDirectories = listOf("withLib1/sharedLib", "withLib2/sharedLib", "withLib3/sharedLib", "withLib4/sharedLib")
)
}