Fix "Specify type explicitly" intention with generic type
#KT-27641 Fixed #KT-30252 Fixed
This commit is contained in:
@@ -18,7 +18,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.util
|
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.jvm.JavaToKotlinClassMap
|
||||||
|
import org.jetbrains.kotlin.builtins.replaceReturnType
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||||
@@ -124,35 +127,70 @@ fun KotlinType.getResolvableApproximations(
|
|||||||
): Sequence<KotlinType> {
|
): Sequence<KotlinType> {
|
||||||
return (listOf(this) + TypeUtils.getAllSupertypes(this))
|
return (listOf(this) + TypeUtils.getAllSupertypes(this))
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it.isResolvableInScope(scope, checkTypeParameters, allowIntersections) }
|
.mapNotNull {
|
||||||
.mapNotNull mapArgs@{
|
it.asTypeProjection()
|
||||||
val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { typeProjection ->
|
.fixTypeProjection(scope, checkTypeParameters, allowIntersections, isOutVariance = true)
|
||||||
typeProjection.type.isResolvableInScope(
|
?.type
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fun KotlinType.isAbstract(): Boolean {
|
||||||
val modality = (constructor.declarationDescriptor as? ClassDescriptor)?.modality
|
val modality = (constructor.declarationDescriptor as? ClassDescriptor)?.modality
|
||||||
return modality == Modality.ABSTRACT || modality == Modality.SEALED
|
return modality == Modality.ABSTRACT || modality == Modality.SEALED
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ import com.intellij.psi.PsiComment
|
|||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
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.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||||
@@ -131,17 +129,8 @@ class SpecifyTypeExplicitlyIntention : SelfTargetingRangeIntention<KtCallableDec
|
|||||||
val bindingContext = resolutionFacade.analyze(contextElement, BodyResolveMode.PARTIAL)
|
val bindingContext = resolutionFacade.analyze(contextElement, BodyResolveMode.PARTIAL)
|
||||||
val scope = contextElement.getResolutionScope(bindingContext, resolutionFacade)
|
val scope = contextElement.getResolutionScope(bindingContext, resolutionFacade)
|
||||||
|
|
||||||
var checkTypeParameters = true
|
|
||||||
val descriptor = exprType.constructor.declarationDescriptor
|
|
||||||
if (descriptor != null && descriptor is TypeParameterDescriptor) {
|
|
||||||
val owner = descriptor.containingDeclaration
|
|
||||||
if (owner is FunctionDescriptor && owner.typeParameters.contains(descriptor)) {
|
|
||||||
checkTypeParameters = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KotlinType.toResolvableApproximations(): List<KotlinType> =
|
fun KotlinType.toResolvableApproximations(): List<KotlinType> =
|
||||||
with(getResolvableApproximations(scope, checkTypeParameters).toList()) {
|
with(getResolvableApproximations(scope, checkTypeParameters = false).toList()) {
|
||||||
when {
|
when {
|
||||||
exprType.isNullabilityFlexible() -> flatMap {
|
exprType.isNullabilityFlexible() -> flatMap {
|
||||||
listOf(TypeUtils.makeNotNullable(it), TypeUtils.makeNullable(it))
|
listOf(TypeUtils.makeNotNullable(it), TypeUtils.makeNullable(it))
|
||||||
|
|||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
inline fun <reified T> foo<caret>() = of(T::class.java)
|
||||||
|
|
||||||
|
class Foo<F>
|
||||||
|
fun <F> of(c: Class<F>): Foo<Foo<F>> = Foo()
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
inline fun <reified T> foo(): Foo<Foo<T>> = of(T::class.java)
|
||||||
|
|
||||||
|
class Foo<F>
|
||||||
|
fun <F> of(c: Class<F>): Foo<Foo<F>> = Foo()
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
inline fun <reified T> foo<caret>() = of(T::class.java)
|
||||||
|
|
||||||
|
class Foo<F>
|
||||||
|
fun <F> of(c: Class<F>): Foo<F> = Foo()
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
inline fun <reified T> foo(): Foo<T> = of(T::class.java)
|
||||||
|
|
||||||
|
class Foo<F>
|
||||||
|
fun <F> of(c: Class<F>): Foo<F> = Foo()
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
class A<T>
|
||||||
|
class B<T>
|
||||||
|
class C<T>
|
||||||
|
class D<K, V>
|
||||||
|
class E
|
||||||
|
private fun test()<caret> = {
|
||||||
|
class Local
|
||||||
|
C<D<A<Local>, B<D<Local, A<E>>>>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
class A<T>
|
||||||
|
class B<T>
|
||||||
|
class C<T>
|
||||||
|
class D<K, V>
|
||||||
|
class E
|
||||||
|
private fun test(): () -> C<*> = {
|
||||||
|
class Local
|
||||||
|
C<D<A<Local>, B<D<Local, A<E>>>>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
class A<T>
|
||||||
|
class B<T>
|
||||||
|
class C<T>
|
||||||
|
class D<K, V>
|
||||||
|
class E
|
||||||
|
private fun test()<caret> = {
|
||||||
|
C<D<A<E>, B<D<C<E>, A<E>>>>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
class A<T>
|
||||||
|
class B<T>
|
||||||
|
class C<T>
|
||||||
|
class D<K, V>
|
||||||
|
class E
|
||||||
|
private fun test(): () -> C<D<A<E>, B<D<C<E>, A<E>>>>> = {
|
||||||
|
C<D<A<E>, B<D<C<E>, A<E>>>>>()
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
class F
|
||||||
|
class TestClass<V, K>
|
||||||
|
private fun test()<caret> = {
|
||||||
|
class Local
|
||||||
|
TestClass<F, Local>()
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
class F
|
||||||
|
class TestClass<V, K>
|
||||||
|
private fun test(): () -> TestClass<F, *> = {
|
||||||
|
class Local
|
||||||
|
TestClass<F, Local>()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
open class F
|
||||||
|
class TestClass<V, out K>
|
||||||
|
|
||||||
|
private fun test()<caret> = {
|
||||||
|
class Local : F()
|
||||||
|
TestClass<F, Local>()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
open class F
|
||||||
|
class TestClass<V, out K>
|
||||||
|
|
||||||
|
private fun test(): () -> TestClass<F, F> = {
|
||||||
|
class Local : F()
|
||||||
|
TestClass<F, Local>()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
open class F
|
||||||
|
class TestClass<V, out K>
|
||||||
|
|
||||||
|
private fun test()<caret> = {
|
||||||
|
class Local
|
||||||
|
TestClass<F, Local?>()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
open class F
|
||||||
|
class TestClass<V, out K>
|
||||||
|
|
||||||
|
private fun test(): () -> TestClass<F, Any?> = {
|
||||||
|
class Local
|
||||||
|
TestClass<F, Local?>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class TestClass<T>
|
||||||
|
private fun test()<caret> = {
|
||||||
|
class Local
|
||||||
|
TestClass<Local>()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
class TestClass<T>
|
||||||
|
private fun test(): () -> TestClass<*> = {
|
||||||
|
class Local
|
||||||
|
TestClass<Local>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
open class F
|
||||||
|
class B<out T>
|
||||||
|
class K<out T>
|
||||||
|
|
||||||
|
private fun check()<caret> = {
|
||||||
|
class Local : F()
|
||||||
|
B<K<Local>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
open class F
|
||||||
|
class B<out T>
|
||||||
|
class K<out T>
|
||||||
|
|
||||||
|
private fun check(): () -> B<K<F>> = {
|
||||||
|
class Local : F()
|
||||||
|
B<K<Local>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
open class F
|
||||||
|
class B<out T>
|
||||||
|
class K<T>
|
||||||
|
|
||||||
|
private fun check()<caret> = {
|
||||||
|
class Local : F()
|
||||||
|
B<K<Local>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
open class F
|
||||||
|
class B<out T>
|
||||||
|
class K<T>
|
||||||
|
|
||||||
|
private fun check(): () -> B<K<*>> = {
|
||||||
|
class Local : F()
|
||||||
|
B<K<Local>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
open class F
|
||||||
|
class B<T>
|
||||||
|
class K<out T>
|
||||||
|
|
||||||
|
private fun check()<caret> = {
|
||||||
|
class Local : F()
|
||||||
|
B<K<Local>>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
open class F
|
||||||
|
class B<T>
|
||||||
|
class K<out T>
|
||||||
|
|
||||||
|
private fun check(): () -> B<*> = {
|
||||||
|
class Local : F()
|
||||||
|
B<K<Local>>()
|
||||||
|
}
|
||||||
@@ -16296,11 +16296,31 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
runTest("idea/testData/intentions/specifyTypeExplicitly/genericClass.kt");
|
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")
|
@TestMetadata("genericFunction.kt")
|
||||||
public void testGenericFunction() throws Exception {
|
public void testGenericFunction() throws Exception {
|
||||||
runTest("idea/testData/intentions/specifyTypeExplicitly/genericFunction.kt");
|
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")
|
@TestMetadata("lambdaParam.kt")
|
||||||
public void testLambdaParam() throws Exception {
|
public void testLambdaParam() throws Exception {
|
||||||
runTest("idea/testData/intentions/specifyTypeExplicitly/lambdaParam.kt");
|
runTest("idea/testData/intentions/specifyTypeExplicitly/lambdaParam.kt");
|
||||||
@@ -16311,11 +16331,46 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
runTest("idea/testData/intentions/specifyTypeExplicitly/localClass.kt");
|
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")
|
@TestMetadata("loopParameter.kt")
|
||||||
public void testLoopParameter() throws Exception {
|
public void testLoopParameter() throws Exception {
|
||||||
runTest("idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt");
|
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")
|
@TestMetadata("overriddenAsNull.kt")
|
||||||
public void testOverriddenAsNull() throws Exception {
|
public void testOverriddenAsNull() throws Exception {
|
||||||
runTest("idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt");
|
runTest("idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user