diff --git a/ChangeLog.md b/ChangeLog.md index e71d5a897ac..75af42ee6b2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -117,6 +117,7 @@ These artifacts include extensions for the types available in the latter JDKs, s ###### Issues fixed +- [`KT-7722`](https://youtrack.jetbrains.com/issue/KT-7722) Approximate unresolvable types in "Create from Usage" quickfixes - [`KT-11115`](https://youtrack.jetbrains.com/issue/KT-11115) Implement Members: Fix base member detection when abstract and non-abstract members with matching signatures are inherited from an interface ###### New features 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 31086bc2529..6e7488413d4 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 @@ -31,6 +31,8 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.typeUtil.* +import org.jetbrains.kotlin.utils.SmartSet +import org.jetbrains.kotlin.utils.addToStdlib.singletonList fun KotlinType.approximateFlexibleTypes(preferNotNull: Boolean = false): KotlinType { if (isDynamic()) return this @@ -100,4 +102,31 @@ fun KotlinType.anonymousObjectSuperTypeOrNull(): KotlinType? { return immediateSupertypes().firstOrNull() ?: classDescriptor.builtIns.anyType } return null +} + +fun KotlinType.getResolvableApproximations(scope: LexicalScope?, checkTypeParameters: Boolean): Sequence { + return (singletonList() + TypeUtils.getAllSupertypes(this)) + .asSequence() + .filter { it.isResolvableInScope(scope, checkTypeParameters) } + .mapNotNull mapArgs@ { + val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { it.type.isResolvableInScope(scope, checkTypeParameters) } + if (resolvableArgs.containsAll(it.arguments)) return@mapArgs it + + val newArguments = (it.arguments zip it.constructor.parameters).map { + val (arg, param) = it + 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) + } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt index 00d70858a91..5bff0b7083e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt @@ -33,6 +33,8 @@ import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.approximateWithResolvableType import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.idea.util.isResolvableInScope +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.idea.util.getResolvableApproximations import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.endOffset @@ -40,9 +42,9 @@ import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode -import org.jetbrains.kotlin.types.* -import org.jetbrains.kotlin.utils.SmartSet -import org.jetbrains.kotlin.utils.addToStdlib.singletonList +import org.jetbrains.kotlin.types.ErrorUtils +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.isFlexible import org.jetbrains.kotlin.utils.ifEmpty class SpecifyTypeExplicitlyIntention : @@ -112,28 +114,7 @@ class SpecifyTypeExplicitlyIntention : val resolutionFacade = contextElement.getResolutionFacade() val bindingContext = resolutionFacade.analyze(contextElement, BodyResolveMode.PARTIAL) val scope = contextElement.getResolutionScope(bindingContext, resolutionFacade) - val types = (exprType.singletonList() + TypeUtils.getAllSupertypes(exprType)) - .filter { it.isResolvableInScope(scope, true) } - .mapNotNull mapArgs@ { - val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { it.type.isResolvableInScope(scope, true) } - if (resolvableArgs.containsAll(it.arguments)) return@mapArgs it - - val newArguments = (it.arguments zip it.constructor.parameters).map { - val (arg, param) = it - when { - arg in resolvableArgs -> arg - arg.projectionKind == Variance.OUT_VARIANCE || - param.variance == Variance.OUT_VARIANCE -> TypeProjectionImpl( - arg.projectionKind, - arg.type.approximateWithResolvableType(scope, true) - ) - else -> return@mapArgs null - } - } - - it.replace(newArguments) - } - .ifEmpty { return null } + val types = exprType.getResolvableApproximations(scope, true).toList().ifEmpty { return null } return object : ChooseValueExpression(types, types.first()) { override fun getLookupString(element: KotlinType) = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(element) override fun getResult(element: KotlinType) = IdeDescriptorRenderers.SOURCE_CODE.renderType(element) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt index b5f7eba0a65..629745e58e9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt @@ -146,7 +146,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { private val typeCandidates = HashMap>() - var placement: CallablePlacement by Delegates.notNull() + var placement: CallablePlacement? = null private val elementsToShorten = ArrayList() diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt index 820444223dc..74092bc16a3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt @@ -18,17 +18,20 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder import com.intellij.psi.PsiElement import com.intellij.util.ArrayUtil +import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.ClassInfo -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtExpression -import org.jetbrains.kotlin.psi.KtTypeReference +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.idea.util.getResolvableApproximations +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.typeUtil.makeNotNullable -import org.jetbrains.kotlin.types.typeUtil.supertypes import java.util.* /** @@ -86,14 +89,38 @@ abstract class TypeInfo(val variance: Variance) { open fun getPossibleNamesFromExpression(bindingContext: BindingContext): Array = ArrayUtil.EMPTY_STRING_ARRAY abstract fun getPossibleTypes(builder: CallableBuilder): List + private fun getScopeForTypeApproximation(config: CallableBuilderConfiguration, placement: CallablePlacement?): LexicalScope? { + if (placement == null) return config.originalElement.getResolutionScope() + + val containingElement = when (placement) { + is CallablePlacement.NoReceiver -> { + placement.containingElement + } + is CallablePlacement.WithReceiver -> { + val receiverClassDescriptor = + placement.receiverTypeCandidate.theType.constructor.declarationDescriptor + val classDeclaration = receiverClassDescriptor?.let { DescriptorToSourceUtils.getSourceFromDescriptor(it) } + if (!config.isExtension && classDeclaration != null) classDeclaration else config.currentFile + } + else -> throw IllegalArgumentException("Unexpected placement: $placement") + } + return when (containingElement) { + is KtClassOrObject -> (containingElement.resolveToDescriptor() as? ClassDescriptorWithResolutionScopes)?.scopeForMemberDeclarationResolution + is KtBlockExpression -> (containingElement.statements.firstOrNull() ?: containingElement).getResolutionScope() + is KtElement -> containingElement.getContainingKtFile().getResolutionScope() + else -> null + } + } + protected fun KotlinType?.getPossibleSupertypes(variance: Variance, callableBuilder: CallableBuilder): List { if (this == null || ErrorUtils.containsErrorType(this)) { return Collections.singletonList(callableBuilder.currentFileModule.builtIns.anyType) } - val single = Collections.singletonList(this) + val scope = getScopeForTypeApproximation(callableBuilder.config, callableBuilder.placement) + val approximations = getResolvableApproximations(scope, false) return when (variance) { - Variance.IN_VARIANCE -> single + supertypes() - else -> single + Variance.IN_VARIANCE -> approximations.toList() + else -> listOf(approximations.firstOrNull() ?: this) } } } diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/approximateAnonymousObjectRuntime.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/approximateAnonymousObjectRuntime.kt new file mode 100644 index 00000000000..fc9f4866e7f --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/approximateAnonymousObjectRuntime.kt @@ -0,0 +1,8 @@ +// "Create extension function 'List.foo'" "true" + +open class A + +fun main(args: Array) { + val list = listOf(1, 2, 4, 5) + list.foo { object : A() {} } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/approximateAnonymousObjectRuntime.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/approximateAnonymousObjectRuntime.kt.after new file mode 100644 index 00000000000..36dc73f85ed --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/approximateAnonymousObjectRuntime.kt.after @@ -0,0 +1,12 @@ +// "Create extension function 'List.foo'" "true" + +open class A + +fun main(args: Array) { + val list = listOf(1, 2, 4, 5) + list.foo { object : A() {} } +} + +fun List.foo(function: () -> A) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. +} diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/approximateLocalClassRuntime.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/approximateLocalClassRuntime.kt new file mode 100644 index 00000000000..1b33c16ba70 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/approximateLocalClassRuntime.kt @@ -0,0 +1,10 @@ +// "Create extension function 'List.foo'" "true" + +open class A + +fun main(args: Array) { + class Local : A() + + val list = listOf(1, 2, 4, 5) + list.foo { Local() } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/approximateLocalClassRuntime.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/approximateLocalClassRuntime.kt.after new file mode 100644 index 00000000000..14ec65548ac --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/approximateLocalClassRuntime.kt.after @@ -0,0 +1,14 @@ +// "Create extension function 'List.foo'" "true" + +open class A + +fun main(args: Array) { + class Local : A() + + val list = listOf(1, 2, 4, 5) + list.foo { Local() } +} + +fun List.foo(function: () -> A) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index bdc910bba8e..b103d0e2c06 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -1956,6 +1956,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createFunction/call"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); } + @TestMetadata("approximateAnonymousObjectRuntime.kt") + public void testApproximateAnonymousObjectRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/approximateAnonymousObjectRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("approximateLocalClassRuntime.kt") + public void testApproximateLocalClassRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/approximateLocalClassRuntime.kt"); + doTest(fileName); + } + @TestMetadata("argumentTypeMismatch.kt") public void testArgumentTypeMismatch() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/argumentTypeMismatch.kt");