diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/TypeUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/TypeUtils.kt index 02cd24a3a3a..ab552c830ba 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/TypeUtils.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/TypeUtils.kt @@ -18,7 +18,10 @@ package org.jetbrains.kotlin.idea.util +import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType +import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap +import org.jetbrains.kotlin.builtins.replaceReturnType import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor @@ -124,35 +127,70 @@ fun KotlinType.getResolvableApproximations( ): Sequence { return (listOf(this) + TypeUtils.getAllSupertypes(this)) .asSequence() - .filter { it.isResolvableInScope(scope, checkTypeParameters, allowIntersections) } - .mapNotNull mapArgs@{ - val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { typeProjection -> - typeProjection.type.isResolvableInScope( - scope, - checkTypeParameters - ) - } - if (resolvableArgs.containsAll(it.arguments)) return@mapArgs it - - val newArguments = (it.arguments zip it.constructor.parameters).map { pair -> - val (arg, param) = pair - when { - arg in resolvableArgs -> arg - - arg.projectionKind == Variance.OUT_VARIANCE || - param.variance == Variance.OUT_VARIANCE -> TypeProjectionImpl( - arg.projectionKind, - arg.type.approximateWithResolvableType(scope, checkTypeParameters) - ) - - else -> return@mapArgs null - } - } - - it.replace(newArguments) + .mapNotNull { + it.asTypeProjection() + .fixTypeProjection(scope, checkTypeParameters, allowIntersections, isOutVariance = true) + ?.type } } +private fun TypeProjection.fixTypeProjection( + scope: LexicalScope?, + checkTypeParameters: Boolean, + allowIntersections: Boolean, + isOutVariance: Boolean +): TypeProjection? { + if (!type.isResolvableInScope(scope, checkTypeParameters, allowIntersections)) return null + if (type.arguments.isEmpty()) return this + + val resolvableArgs = type.arguments.filterTo(SmartSet.create()) { typeProjection -> + typeProjection.type.isResolvableInScope(scope, checkTypeParameters, allowIntersections) + } + + if (resolvableArgs.containsAll(type.arguments)) { + fun fixArguments(type: KotlinType): KotlinType? = type.replace( + (type.arguments zip type.constructor.parameters).map { (arg, param) -> + if (arg.isStarProjection) arg + else arg.fixTypeProjection( + scope, + checkTypeParameters, + allowIntersections, + isOutVariance = isOutVariance && param.variance == Variance.OUT_VARIANCE + ) ?: when { + !isOutVariance -> return null + param.variance == Variance.OUT_VARIANCE -> arg.type.approximateWithResolvableType( + scope, + checkTypeParameters + ).asTypeProjection() + else -> type.replaceArgumentsWithStarProjections().arguments.first() + } + }) + + return if (type.isBuiltinFunctionalType) { + val returnType = type.getReturnTypeFromFunctionType() + type.replaceReturnType(fixArguments(returnType) ?: return null).asTypeProjection() + } else fixArguments(type)?.asTypeProjection() + } + + if (!isOutVariance) return null + + val newArguments = (type.arguments zip type.constructor.parameters).map { (arg, param) -> + when { + arg in resolvableArgs -> arg + + arg.projectionKind == Variance.OUT_VARIANCE || + param.variance == Variance.OUT_VARIANCE -> TypeProjectionImpl( + arg.projectionKind, + arg.type.approximateWithResolvableType(scope, checkTypeParameters) + ) + + else -> return if (isOutVariance) type.replaceArgumentsWithStarProjections().asTypeProjection() else null + } + } + + return type.replace(newArguments).asTypeProjection() +} + fun KotlinType.isAbstract(): Boolean { val modality = (constructor.declarationDescriptor as? ClassDescriptor)?.modality return modality == Modality.ABSTRACT || modality == Modality.SEALED diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt index fa67e3cb4e6..826a9d9cd96 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt @@ -26,9 +26,7 @@ import com.intellij.psi.PsiComment import com.intellij.psi.PsiDocumentManager import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor -import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.ShortenReferences @@ -131,17 +129,8 @@ class SpecifyTypeExplicitlyIntention : SelfTargetingRangeIntention = - with(getResolvableApproximations(scope, checkTypeParameters).toList()) { + with(getResolvableApproximations(scope, checkTypeParameters = false).toList()) { when { exprType.isNullabilityFlexible() -> flatMap { listOf(TypeUtils.makeNotNullable(it), TypeUtils.makeNullable(it)) diff --git a/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt b/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt new file mode 100644 index 00000000000..1cc185a9a87 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME +inline fun foo() = of(T::class.java) + +class Foo +fun of(c: Class): Foo> = Foo() \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt.after b/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt.after new file mode 100644 index 00000000000..fb1306f78d8 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME +inline fun foo(): Foo> = of(T::class.java) + +class Foo +fun of(c: Class): Foo> = Foo() \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt b/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt new file mode 100644 index 00000000000..65cf79255ed --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME +inline fun foo() = of(T::class.java) + +class Foo +fun of(c: Class): Foo = Foo() \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt.after b/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt.after new file mode 100644 index 00000000000..0af3434bfba --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME +inline fun foo(): Foo = of(T::class.java) + +class Foo +fun of(c: Class): Foo = Foo() \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt b/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt new file mode 100644 index 00000000000..3bda45ef53c --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt @@ -0,0 +1,9 @@ +class A +class B +class C +class D +class E +private fun test() = { + class Local + C, B>>>>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt.after b/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt.after new file mode 100644 index 00000000000..54f24b98afe --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt.after @@ -0,0 +1,9 @@ +class A +class B +class C +class D +class E +private fun test(): () -> C<*> = { + class Local + C, B>>>>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt b/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt new file mode 100644 index 00000000000..e9dbbb12133 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt @@ -0,0 +1,8 @@ +class A +class B +class C +class D +class E +private fun test() = { + C, B, A>>>>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt.after b/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt.after new file mode 100644 index 00000000000..3c4afd1fce2 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt.after @@ -0,0 +1,8 @@ +class A +class B +class C +class D +class E +private fun test(): () -> C, B, A>>>> = { + C, B, A>>>>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt new file mode 100644 index 00000000000..053a69837e2 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt @@ -0,0 +1,6 @@ +class F +class TestClass +private fun test() = { + class Local + TestClass() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt.after b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt.after new file mode 100644 index 00000000000..0a34e57241e --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt.after @@ -0,0 +1,6 @@ +class F +class TestClass +private fun test(): () -> TestClass = { + class Local + TestClass() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt new file mode 100644 index 00000000000..c16e81425de --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt @@ -0,0 +1,7 @@ +open class F +class TestClass + +private fun test() = { + class Local : F() + TestClass() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt.after b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt.after new file mode 100644 index 00000000000..b733d747b95 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt.after @@ -0,0 +1,7 @@ +open class F +class TestClass + +private fun test(): () -> TestClass = { + class Local : F() + TestClass() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt new file mode 100644 index 00000000000..b3464b8aa46 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt @@ -0,0 +1,7 @@ +open class F +class TestClass + +private fun test() = { + class Local + TestClass() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt.after b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt.after new file mode 100644 index 00000000000..40da2097f01 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt.after @@ -0,0 +1,7 @@ +open class F +class TestClass + +private fun test(): () -> TestClass = { + class Local + TestClass() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt b/idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt new file mode 100644 index 00000000000..f55e1997aa1 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt @@ -0,0 +1,5 @@ +class TestClass +private fun test() = { + class Local + TestClass() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt.after b/idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt.after new file mode 100644 index 00000000000..5b06d1eed5c --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt.after @@ -0,0 +1,5 @@ +class TestClass +private fun test(): () -> TestClass<*> = { + class Local + TestClass() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/outClass.kt b/idea/testData/intentions/specifyTypeExplicitly/outClass.kt new file mode 100644 index 00000000000..5e44b395e73 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/outClass.kt @@ -0,0 +1,8 @@ +open class F +class B +class K + +private fun check() = { + class Local : F() + B>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/outClass.kt.after b/idea/testData/intentions/specifyTypeExplicitly/outClass.kt.after new file mode 100644 index 00000000000..7c1dd82390e --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/outClass.kt.after @@ -0,0 +1,8 @@ +open class F +class B +class K + +private fun check(): () -> B> = { + class Local : F() + B>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/outClass2.kt b/idea/testData/intentions/specifyTypeExplicitly/outClass2.kt new file mode 100644 index 00000000000..3d3d93f32a2 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/outClass2.kt @@ -0,0 +1,8 @@ +open class F +class B +class K + +private fun check() = { + class Local : F() + B>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/outClass2.kt.after b/idea/testData/intentions/specifyTypeExplicitly/outClass2.kt.after new file mode 100644 index 00000000000..40792c6bcbd --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/outClass2.kt.after @@ -0,0 +1,8 @@ +open class F +class B +class K + +private fun check(): () -> B> = { + class Local : F() + B>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/outClass3.kt b/idea/testData/intentions/specifyTypeExplicitly/outClass3.kt new file mode 100644 index 00000000000..1ea42c06934 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/outClass3.kt @@ -0,0 +1,8 @@ +open class F +class B +class K + +private fun check() = { + class Local : F() + B>() +} \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/outClass3.kt.after b/idea/testData/intentions/specifyTypeExplicitly/outClass3.kt.after new file mode 100644 index 00000000000..c890ec14fd8 --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/outClass3.kt.after @@ -0,0 +1,8 @@ +open class F +class B +class K + +private fun check(): () -> B<*> = { + class Local : F() + B>() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 060a7c3a689..d6ef10acca6 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -16296,11 +16296,31 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/specifyTypeExplicitly/genericClass.kt"); } + @TestMetadata("genericClassWithTypeParameters.kt") + public void testGenericClassWithTypeParameters() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt"); + } + + @TestMetadata("genericClassWithTypeParameters2.kt") + public void testGenericClassWithTypeParameters2() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt"); + } + @TestMetadata("genericFunction.kt") public void testGenericFunction() throws Exception { runTest("idea/testData/intentions/specifyTypeExplicitly/genericFunction.kt"); } + @TestMetadata("innerTypeParameter.kt") + public void testInnerTypeParameter() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt"); + } + + @TestMetadata("innerTypeParameter2.kt") + public void testInnerTypeParameter2() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt"); + } + @TestMetadata("lambdaParam.kt") public void testLambdaParam() throws Exception { runTest("idea/testData/intentions/specifyTypeExplicitly/lambdaParam.kt"); @@ -16311,11 +16331,46 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/specifyTypeExplicitly/localClass.kt"); } + @TestMetadata("localClassInSecondTypeParameter.kt") + public void testLocalClassInSecondTypeParameter() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt"); + } + + @TestMetadata("localClassInSecondTypeParameter2.kt") + public void testLocalClassInSecondTypeParameter2() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt"); + } + + @TestMetadata("localClassInSecondTypeParameter3.kt") + public void testLocalClassInSecondTypeParameter3() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt"); + } + + @TestMetadata("localClassInTypeParameter.kt") + public void testLocalClassInTypeParameter() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt"); + } + @TestMetadata("loopParameter.kt") public void testLoopParameter() throws Exception { runTest("idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt"); } + @TestMetadata("outClass.kt") + public void testOutClass() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/outClass.kt"); + } + + @TestMetadata("outClass2.kt") + public void testOutClass2() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/outClass2.kt"); + } + + @TestMetadata("outClass3.kt") + public void testOutClass3() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/outClass3.kt"); + } + @TestMetadata("overriddenAsNull.kt") public void testOverriddenAsNull() throws Exception { runTest("idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt");