From e98af7bfc12b57fa45be5585cffcf56c571351dd Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Fri, 7 Jul 2017 17:40:15 +0300 Subject: [PATCH] Create Class from Usage: Use type parameter bounds as supertypes #KT-15242 Fixed --- .../jetbrains/kotlin/idea/util/TypeUtils.kt | 15 ++++-- .../callableBuilder/CallableBuilder.kt | 49 ++++++++++--------- .../callableBuilder/CallableInfo.kt | 2 +- .../callableBuilder/templateExpressions.kt | 9 ++-- .../callableBuilder/typeUtils.kt | 14 +++++- ...eateClassFromTypeReferenceActionFactory.kt | 17 ++++++- .../typeReference/typeArgumentWithBound.kt | 8 +++ .../typeArgumentWithBound.kt.after | 12 +++++ .../typeArgumentWithMultipleBounds.kt | 9 ++++ .../typeArgumentWithMultipleBounds.kt.after | 13 +++++ .../idea/quickfix/QuickFixTestGenerated.java | 12 +++++ 11 files changed, 128 insertions(+), 32 deletions(-) create mode 100644 idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithBound.kt create mode 100644 idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithBound.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt create mode 100644 idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt.after 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 706c7bed6c2..834d74f2b0e 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 @@ -92,7 +92,12 @@ private fun KotlinType.hasAnnotationMaybeExternal(fqName: FqName) = with (annota findAnnotation(fqName) ?: findExternalAnnotation(fqName) } != null -fun KotlinType.isResolvableInScope(scope: LexicalScope?, checkTypeParameters: Boolean): Boolean { +fun KotlinType.isResolvableInScope(scope: LexicalScope?, checkTypeParameters: Boolean, allowIntersections: Boolean = false): Boolean { + if (constructor is IntersectionTypeConstructor) { + if (!allowIntersections) return false + return constructor.supertypes.all { it.isResolvableInScope(scope, checkTypeParameters, allowIntersections) } + } + if (canBeReferencedViaImport()) return true val descriptor = constructor.declarationDescriptor @@ -116,10 +121,14 @@ fun KotlinType.anonymousObjectSuperTypeOrNull(): KotlinType? { return null } -fun KotlinType.getResolvableApproximations(scope: LexicalScope?, checkTypeParameters: Boolean): Sequence { +fun KotlinType.getResolvableApproximations( + scope: LexicalScope?, + checkTypeParameters: Boolean, + allowIntersections: Boolean = false +): Sequence { return (listOf(this) + TypeUtils.getAllSupertypes(this)) .asSequence() - .filter { it.isResolvableInScope(scope, checkTypeParameters) } + .filter { it.isResolvableInScope(scope, checkTypeParameters, allowIntersections) } .mapNotNull mapArgs@ { val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { it.type.isResolvableInScope(scope, checkTypeParameters) } if (resolvableArgs.containsAll(it.arguments)) return@mapArgs it 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 ea61f781334..d7504110aa8 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 @@ -85,13 +85,13 @@ import java.util.* */ class TypeCandidate(val theType: KotlinType, scope: HierarchicalScope? = null) { val typeParameters: Array - var renderedType: String? = null + var renderedTypes: List = emptyList() private set var renderedTypeParameters: List? = null private set fun render(typeParameterNameMap: Map, fakeFunction: FunctionDescriptor?) { - renderedType = theType.renderShort(typeParameterNameMap) + renderedTypes = theType.renderShort(typeParameterNameMap) renderedTypeParameters = typeParameters.map { RenderedTypeParameter(it, it.containingDeclaration == fakeFunction, typeParameterNameMap[it]!!) } @@ -101,7 +101,7 @@ class TypeCandidate(val theType: KotlinType, scope: HierarchicalScope? = null) { val typeParametersInType = theType.getTypeParameters() if (scope == null) { typeParameters = typeParametersInType.toTypedArray() - renderedType = theType.renderShort(Collections.emptyMap()) + renderedTypes = theType.renderShort(Collections.emptyMap()) } else { typeParameters = getTypeParameterNamesNotInScope(typeParametersInType, scope).toTypedArray() @@ -117,8 +117,8 @@ data class RenderedTypeParameter( val text: String ) -fun List.getTypeByRenderedType(renderedType: String): KotlinType? = - firstOrNull { it.renderedType == renderedType }?.theType +fun List.getTypeByRenderedType(renderedTypes: List): KotlinType? = + firstOrNull { it.renderedTypes == renderedTypes }?.theType class CallableBuilderConfiguration( val callableInfos: List, @@ -421,7 +421,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { else null val ownerTypeString = if (isExtension) { - val renderedType = receiverTypeCandidate!!.renderedType!! + val renderedType = receiverTypeCandidate!!.renderedTypes.first() val isFunctionType = receiverTypeCandidate.theType.constructor.declarationDescriptor is FunctionClassDescriptor if (isFunctionType) "($renderedType)." else "$renderedType." } else "" @@ -569,22 +569,22 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { val typeRefsToShorten = ArrayList() if (config.isExtension) { - val receiverTypeText = receiverTypeCandidate!!.theType.renderLong(typeParameterNameMap) + val receiverTypeText = receiverTypeCandidate!!.theType.renderLong(typeParameterNameMap).first() val replacingTypeRef = KtPsiFactory(declaration).createType(receiverTypeText) val newTypeRef = (declaration as KtCallableDeclaration).setReceiverTypeReference(replacingTypeRef)!! typeRefsToShorten.add(newTypeRef) } - val returnTypeRef = declaration.getReturnTypeReference() - if (returnTypeRef != null) { + val returnTypeRefs = declaration.getReturnTypeReferences() + if (returnTypeRefs.isNotEmpty()) { val returnType = typeCandidates[callableInfo.returnTypeInfo]!!.getTypeByRenderedType( - returnTypeRef.text + returnTypeRefs.map { it.text } ?: throw AssertionError("Expression for return type shouldn't be empty: declaration = ${declaration.text}") ) if (returnType != null) { // user selected a given type - replaceWithLongerName(returnTypeRef, returnType) - typeRefsToShorten.add(declaration.getReturnTypeReference()!!) + replaceWithLongerName(returnTypeRefs, returnType) + typeRefsToShorten.addAll(declaration.getReturnTypeReferences()) } } @@ -595,11 +595,11 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { val parameterTypeRef = parameter.typeReference if (parameterTypeRef != null) { val parameterType = parameterTypeExpressions[i].typeCandidates.getTypeByRenderedType( - parameterTypeRef.text + listOf(parameterTypeRef.text) ?: throw AssertionError("Expression for parameter type shouldn't be empty: declaration = ${declaration.text}") ) if (parameterType != null) { - replaceWithLongerName(parameterTypeRef, parameterType) + replaceWithLongerName(listOf(parameterTypeRef), parameterType) parameterIndicesToShorten.add(i) } } @@ -705,11 +705,11 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { callableInfo.parameterInfos.asSequence() .flatMap { typeCandidates[it.typeInfo]!!.asSequence() } - .forEach { typeParameterMap[it.renderedType!!] = it.renderedTypeParameters!! } + .forEach { typeParameterMap[it.renderedTypes.first()] = it.renderedTypeParameters!! } if (declaration.getReturnTypeReference() != null) { typeCandidates[callableInfo.returnTypeInfo]!!.forEach { - typeParameterMap[it.renderedType!!] = it.renderedTypeParameters!! + typeParameterMap[it.renderedTypes.first()] = it.renderedTypeParameters!! } } @@ -745,7 +745,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { val parameterTypeToNamesMap = HashMap>() typeCandidates[parameter.typeInfo]!!.forEach { typeCandidate -> val suggestedNames = KotlinNameSuggester.suggestNamesByType(typeCandidate.theType, { true }) - parameterTypeToNamesMap[typeCandidate.renderedType!!] = suggestedNames.toTypedArray() + parameterTypeToNamesMap[typeCandidate.renderedTypes.first()] = suggestedNames.toTypedArray() } // add expression to builder @@ -758,9 +758,10 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { return typeParameters } - private fun replaceWithLongerName(typeRef: KtTypeReference, theType: KotlinType) { - val fullyQualifiedReceiverTypeRef = KtPsiFactory(typeRef).createType(theType.renderLong(typeParameterNameMap)) - typeRef.replace(fullyQualifiedReceiverTypeRef) + private fun replaceWithLongerName(typeRefs: List, theType: KotlinType) { + val psiFactory = KtPsiFactory(jetFileToEdit.project) + val fullyQualifiedReceiverTypeRefs = theType.renderLong(typeParameterNameMap).map { psiFactory.createType(it) } + (typeRefs zip fullyQualifiedReceiverTypeRefs).forEach { (shortRef, longRef) -> shortRef.replace(longRef) } } private fun transformToJavaMemberIfApplicable(declaration: KtNamedDeclaration): Boolean { @@ -1082,10 +1083,12 @@ internal fun placeDeclarationInContainer( return declarationInPlace } -internal fun KtNamedDeclaration.getReturnTypeReference(): KtTypeReference? { +internal fun KtNamedDeclaration.getReturnTypeReference() = getReturnTypeReferences().singleOrNull() + +internal fun KtNamedDeclaration.getReturnTypeReferences(): List { return when (this) { - is KtCallableDeclaration -> typeReference - is KtClassOrObject -> superTypeListEntries.firstOrNull()?.typeReference + is KtCallableDeclaration -> listOfNotNull(typeReference) + is KtClassOrObject -> superTypeListEntries.mapNotNull { it.typeReference } else -> throw AssertionError("Unexpected declaration kind: $text") } } 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 4dbce553714..2f8078eb185 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 @@ -117,7 +117,7 @@ abstract class TypeInfo(val variance: Variance) { return Collections.singletonList(callableBuilder.currentFileModule.builtIns.anyType) } val scope = getScopeForTypeApproximation(callableBuilder.config, callableBuilder.placement) - val approximations = getResolvableApproximations(scope, false) + val approximations = getResolvableApproximations(scope, false, true) return when (variance) { Variance.IN_VARIANCE -> approximations.toList() else -> listOf(approximations.firstOrNull() ?: this) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/templateExpressions.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/templateExpressions.kt index 1246bd5d983..9c3d1ecf55d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/templateExpressions.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/templateExpressions.kt @@ -104,14 +104,17 @@ internal class ParameterNameExpression( internal abstract class TypeExpression(val typeCandidates: List) : Expression() { class ForTypeReference(typeCandidates: List) : TypeExpression(typeCandidates) { override val cachedLookupElements: Array = - typeCandidates.map { LookupElementBuilder.create(it, it.renderedType!!) }.toTypedArray() + typeCandidates.map { LookupElementBuilder.create(it, it.renderedTypes.first()) }.toTypedArray() } class ForDelegationSpecifier(typeCandidates: List) : TypeExpression(typeCandidates) { override val cachedLookupElements: Array = typeCandidates.map { - val descriptor = it.theType.constructor.declarationDescriptor as ClassDescriptor - val text = it.renderedType!! + if (descriptor.kind == ClassKind.INTERFACE) "" else "()" + val types = it.theType.decomposeIntersection() + val text = (types zip it.renderedTypes).joinToString { (type, renderedType) -> + val descriptor = type.constructor.declarationDescriptor as ClassDescriptor + renderedType + if (descriptor.kind == ClassKind.INTERFACE) "" else "()" + } LookupElementBuilder.create(it, text) }.toTypedArray() } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/typeUtils.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/typeUtils.kt index 03437c872d8..08d1f615f59 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/typeUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/typeUtils.kt @@ -50,7 +50,15 @@ internal operator fun KotlinType.contains(descriptor: ClassifierDescriptor): Boo return constructor.declarationDescriptor == descriptor || arguments.any { descriptor in it.type } } -private fun KotlinType.render(typeParameterNameMap: Map, fq: Boolean): String { +internal fun KotlinType.decomposeIntersection(): List { + (constructor as? IntersectionTypeConstructor)?.let { + return it.supertypes.flatMap { it.decomposeIntersection() } + } + + return listOf(this) +} + +private fun KotlinType.renderSingle(typeParameterNameMap: Map, fq: Boolean): String { val substitution = typeParameterNameMap .mapValues { val name = Name.identifier(it.value) @@ -77,6 +85,10 @@ private fun KotlinType.render(typeParameterNameMap: Map, fq: Boolean): List { + return decomposeIntersection().map { it.renderSingle(typeParameterNameMap, fq) } +} + internal fun KotlinType.renderShort(typeParameterNameMap: Map) = render(typeParameterNameMap, false) internal fun KotlinType.renderLong(typeParameterNameMap: Map) = render(typeParameterNameMap, true) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createClass/CreateClassFromTypeReferenceActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createClass/CreateClassFromTypeReferenceActionFactory.kt index 7951ccae43f..bd6631e29b5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createClass/CreateClassFromTypeReferenceActionFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createClass/CreateClassFromTypeReferenceActionFactory.kt @@ -23,6 +23,9 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeIn import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeIntersector import org.jetbrains.kotlin.types.Variance import java.util.* @@ -58,6 +61,17 @@ object CreateClassFromTypeReferenceActionFactory : CreateClassFromUsageFactory foo() {} + +fun x() { + foo<Foo>() +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithBound.kt.after b/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithBound.kt.after new file mode 100644 index 00000000000..89201f307ad --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithBound.kt.after @@ -0,0 +1,12 @@ +// "Create class 'Foo'" "true" +interface I + +fun foo() {} + +fun x() { + foo() +} + +class Foo : I { + +} diff --git a/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt b/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt new file mode 100644 index 00000000000..0eb173c0c52 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt @@ -0,0 +1,9 @@ +// "Create class 'Foo'" "true" +open class A +interface I + +fun foo() where T : A {} + +fun x() { + foo<Foo>() +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt.after b/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt.after new file mode 100644 index 00000000000..7931e5bf323 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt.after @@ -0,0 +1,13 @@ +// "Create class 'Foo'" "true" +open class A +interface I + +fun foo() where T : A {} + +fun x() { + foo() +} + +class Foo : I, A() { + +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 8a86a1e8a30..5fdf40dee23 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -2279,6 +2279,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/traitNotQualifierNoTypeArgs.kt"); doTest(fileName); } + + @TestMetadata("typeArgumentWithBound.kt") + public void testTypeArgumentWithBound() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithBound.kt"); + doTest(fileName); + } + + @TestMetadata("typeArgumentWithMultipleBounds.kt") + public void testTypeArgumentWithMultipleBounds() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/typeArgumentWithMultipleBounds.kt"); + doTest(fileName); + } } }