Create Class from Usage: Use type parameter bounds as supertypes

#KT-15242 Fixed
This commit is contained in:
Alexey Sedunov
2017-07-07 17:40:15 +03:00
parent 95769dc9d3
commit e98af7bfc1
11 changed files with 128 additions and 32 deletions
@@ -92,7 +92,12 @@ private fun KotlinType.hasAnnotationMaybeExternal(fqName: FqName) = with (annota
findAnnotation(fqName) ?: findExternalAnnotation(fqName) findAnnotation(fqName) ?: findExternalAnnotation(fqName)
} != null } != 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 if (canBeReferencedViaImport()) return true
val descriptor = constructor.declarationDescriptor val descriptor = constructor.declarationDescriptor
@@ -116,10 +121,14 @@ fun KotlinType.anonymousObjectSuperTypeOrNull(): KotlinType? {
return null return null
} }
fun KotlinType.getResolvableApproximations(scope: LexicalScope?, checkTypeParameters: Boolean): Sequence<KotlinType> { fun KotlinType.getResolvableApproximations(
scope: LexicalScope?,
checkTypeParameters: Boolean,
allowIntersections: Boolean = false
): Sequence<KotlinType> {
return (listOf(this) + TypeUtils.getAllSupertypes(this)) return (listOf(this) + TypeUtils.getAllSupertypes(this))
.asSequence() .asSequence()
.filter { it.isResolvableInScope(scope, checkTypeParameters) } .filter { it.isResolvableInScope(scope, checkTypeParameters, allowIntersections) }
.mapNotNull mapArgs@ { .mapNotNull mapArgs@ {
val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { it.type.isResolvableInScope(scope, checkTypeParameters) } val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { it.type.isResolvableInScope(scope, checkTypeParameters) }
if (resolvableArgs.containsAll(it.arguments)) return@mapArgs it if (resolvableArgs.containsAll(it.arguments)) return@mapArgs it
@@ -85,13 +85,13 @@ import java.util.*
*/ */
class TypeCandidate(val theType: KotlinType, scope: HierarchicalScope? = null) { class TypeCandidate(val theType: KotlinType, scope: HierarchicalScope? = null) {
val typeParameters: Array<TypeParameterDescriptor> val typeParameters: Array<TypeParameterDescriptor>
var renderedType: String? = null var renderedTypes: List<String> = emptyList()
private set private set
var renderedTypeParameters: List<RenderedTypeParameter>? = null var renderedTypeParameters: List<RenderedTypeParameter>? = null
private set private set
fun render(typeParameterNameMap: Map<TypeParameterDescriptor, String>, fakeFunction: FunctionDescriptor?) { fun render(typeParameterNameMap: Map<TypeParameterDescriptor, String>, fakeFunction: FunctionDescriptor?) {
renderedType = theType.renderShort(typeParameterNameMap) renderedTypes = theType.renderShort(typeParameterNameMap)
renderedTypeParameters = typeParameters.map { renderedTypeParameters = typeParameters.map {
RenderedTypeParameter(it, it.containingDeclaration == fakeFunction, typeParameterNameMap[it]!!) RenderedTypeParameter(it, it.containingDeclaration == fakeFunction, typeParameterNameMap[it]!!)
} }
@@ -101,7 +101,7 @@ class TypeCandidate(val theType: KotlinType, scope: HierarchicalScope? = null) {
val typeParametersInType = theType.getTypeParameters() val typeParametersInType = theType.getTypeParameters()
if (scope == null) { if (scope == null) {
typeParameters = typeParametersInType.toTypedArray() typeParameters = typeParametersInType.toTypedArray()
renderedType = theType.renderShort(Collections.emptyMap()) renderedTypes = theType.renderShort(Collections.emptyMap())
} }
else { else {
typeParameters = getTypeParameterNamesNotInScope(typeParametersInType, scope).toTypedArray() typeParameters = getTypeParameterNamesNotInScope(typeParametersInType, scope).toTypedArray()
@@ -117,8 +117,8 @@ data class RenderedTypeParameter(
val text: String val text: String
) )
fun List<TypeCandidate>.getTypeByRenderedType(renderedType: String): KotlinType? = fun List<TypeCandidate>.getTypeByRenderedType(renderedTypes: List<String>): KotlinType? =
firstOrNull { it.renderedType == renderedType }?.theType firstOrNull { it.renderedTypes == renderedTypes }?.theType
class CallableBuilderConfiguration( class CallableBuilderConfiguration(
val callableInfos: List<CallableInfo>, val callableInfos: List<CallableInfo>,
@@ -421,7 +421,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
else null else null
val ownerTypeString = if (isExtension) { val ownerTypeString = if (isExtension) {
val renderedType = receiverTypeCandidate!!.renderedType!! val renderedType = receiverTypeCandidate!!.renderedTypes.first()
val isFunctionType = receiverTypeCandidate.theType.constructor.declarationDescriptor is FunctionClassDescriptor val isFunctionType = receiverTypeCandidate.theType.constructor.declarationDescriptor is FunctionClassDescriptor
if (isFunctionType) "($renderedType)." else "$renderedType." if (isFunctionType) "($renderedType)." else "$renderedType."
} else "" } else ""
@@ -569,22 +569,22 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
val typeRefsToShorten = ArrayList<KtElement>() val typeRefsToShorten = ArrayList<KtElement>()
if (config.isExtension) { if (config.isExtension) {
val receiverTypeText = receiverTypeCandidate!!.theType.renderLong(typeParameterNameMap) val receiverTypeText = receiverTypeCandidate!!.theType.renderLong(typeParameterNameMap).first()
val replacingTypeRef = KtPsiFactory(declaration).createType(receiverTypeText) val replacingTypeRef = KtPsiFactory(declaration).createType(receiverTypeText)
val newTypeRef = (declaration as KtCallableDeclaration).setReceiverTypeReference(replacingTypeRef)!! val newTypeRef = (declaration as KtCallableDeclaration).setReceiverTypeReference(replacingTypeRef)!!
typeRefsToShorten.add(newTypeRef) typeRefsToShorten.add(newTypeRef)
} }
val returnTypeRef = declaration.getReturnTypeReference() val returnTypeRefs = declaration.getReturnTypeReferences()
if (returnTypeRef != null) { if (returnTypeRefs.isNotEmpty()) {
val returnType = typeCandidates[callableInfo.returnTypeInfo]!!.getTypeByRenderedType( 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}") ?: throw AssertionError("Expression for return type shouldn't be empty: declaration = ${declaration.text}")
) )
if (returnType != null) { if (returnType != null) {
// user selected a given type // user selected a given type
replaceWithLongerName(returnTypeRef, returnType) replaceWithLongerName(returnTypeRefs, returnType)
typeRefsToShorten.add(declaration.getReturnTypeReference()!!) typeRefsToShorten.addAll(declaration.getReturnTypeReferences())
} }
} }
@@ -595,11 +595,11 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
val parameterTypeRef = parameter.typeReference val parameterTypeRef = parameter.typeReference
if (parameterTypeRef != null) { if (parameterTypeRef != null) {
val parameterType = parameterTypeExpressions[i].typeCandidates.getTypeByRenderedType( val parameterType = parameterTypeExpressions[i].typeCandidates.getTypeByRenderedType(
parameterTypeRef.text listOf(parameterTypeRef.text)
?: throw AssertionError("Expression for parameter type shouldn't be empty: declaration = ${declaration.text}") ?: throw AssertionError("Expression for parameter type shouldn't be empty: declaration = ${declaration.text}")
) )
if (parameterType != null) { if (parameterType != null) {
replaceWithLongerName(parameterTypeRef, parameterType) replaceWithLongerName(listOf(parameterTypeRef), parameterType)
parameterIndicesToShorten.add(i) parameterIndicesToShorten.add(i)
} }
} }
@@ -705,11 +705,11 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
callableInfo.parameterInfos.asSequence() callableInfo.parameterInfos.asSequence()
.flatMap { typeCandidates[it.typeInfo]!!.asSequence() } .flatMap { typeCandidates[it.typeInfo]!!.asSequence() }
.forEach { typeParameterMap[it.renderedType!!] = it.renderedTypeParameters!! } .forEach { typeParameterMap[it.renderedTypes.first()] = it.renderedTypeParameters!! }
if (declaration.getReturnTypeReference() != null) { if (declaration.getReturnTypeReference() != null) {
typeCandidates[callableInfo.returnTypeInfo]!!.forEach { 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<String, Array<String>>() val parameterTypeToNamesMap = HashMap<String, Array<String>>()
typeCandidates[parameter.typeInfo]!!.forEach { typeCandidate -> typeCandidates[parameter.typeInfo]!!.forEach { typeCandidate ->
val suggestedNames = KotlinNameSuggester.suggestNamesByType(typeCandidate.theType, { true }) val suggestedNames = KotlinNameSuggester.suggestNamesByType(typeCandidate.theType, { true })
parameterTypeToNamesMap[typeCandidate.renderedType!!] = suggestedNames.toTypedArray() parameterTypeToNamesMap[typeCandidate.renderedTypes.first()] = suggestedNames.toTypedArray()
} }
// add expression to builder // add expression to builder
@@ -758,9 +758,10 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
return typeParameters return typeParameters
} }
private fun replaceWithLongerName(typeRef: KtTypeReference, theType: KotlinType) { private fun replaceWithLongerName(typeRefs: List<KtTypeReference>, theType: KotlinType) {
val fullyQualifiedReceiverTypeRef = KtPsiFactory(typeRef).createType(theType.renderLong(typeParameterNameMap)) val psiFactory = KtPsiFactory(jetFileToEdit.project)
typeRef.replace(fullyQualifiedReceiverTypeRef) val fullyQualifiedReceiverTypeRefs = theType.renderLong(typeParameterNameMap).map { psiFactory.createType(it) }
(typeRefs zip fullyQualifiedReceiverTypeRefs).forEach { (shortRef, longRef) -> shortRef.replace(longRef) }
} }
private fun transformToJavaMemberIfApplicable(declaration: KtNamedDeclaration): Boolean { private fun transformToJavaMemberIfApplicable(declaration: KtNamedDeclaration): Boolean {
@@ -1082,10 +1083,12 @@ internal fun <D : KtNamedDeclaration> placeDeclarationInContainer(
return declarationInPlace return declarationInPlace
} }
internal fun KtNamedDeclaration.getReturnTypeReference(): KtTypeReference? { internal fun KtNamedDeclaration.getReturnTypeReference() = getReturnTypeReferences().singleOrNull()
internal fun KtNamedDeclaration.getReturnTypeReferences(): List<KtTypeReference> {
return when (this) { return when (this) {
is KtCallableDeclaration -> typeReference is KtCallableDeclaration -> listOfNotNull(typeReference)
is KtClassOrObject -> superTypeListEntries.firstOrNull()?.typeReference is KtClassOrObject -> superTypeListEntries.mapNotNull { it.typeReference }
else -> throw AssertionError("Unexpected declaration kind: $text") else -> throw AssertionError("Unexpected declaration kind: $text")
} }
} }
@@ -117,7 +117,7 @@ abstract class TypeInfo(val variance: Variance) {
return Collections.singletonList(callableBuilder.currentFileModule.builtIns.anyType) return Collections.singletonList(callableBuilder.currentFileModule.builtIns.anyType)
} }
val scope = getScopeForTypeApproximation(callableBuilder.config, callableBuilder.placement) val scope = getScopeForTypeApproximation(callableBuilder.config, callableBuilder.placement)
val approximations = getResolvableApproximations(scope, false) val approximations = getResolvableApproximations(scope, false, true)
return when (variance) { return when (variance) {
Variance.IN_VARIANCE -> approximations.toList() Variance.IN_VARIANCE -> approximations.toList()
else -> listOf(approximations.firstOrNull() ?: this) else -> listOf(approximations.firstOrNull() ?: this)
@@ -104,14 +104,17 @@ internal class ParameterNameExpression(
internal abstract class TypeExpression(val typeCandidates: List<TypeCandidate>) : Expression() { internal abstract class TypeExpression(val typeCandidates: List<TypeCandidate>) : Expression() {
class ForTypeReference(typeCandidates: List<TypeCandidate>) : TypeExpression(typeCandidates) { class ForTypeReference(typeCandidates: List<TypeCandidate>) : TypeExpression(typeCandidates) {
override val cachedLookupElements: Array<LookupElement> = override val cachedLookupElements: Array<LookupElement> =
typeCandidates.map { LookupElementBuilder.create(it, it.renderedType!!) }.toTypedArray() typeCandidates.map { LookupElementBuilder.create(it, it.renderedTypes.first()) }.toTypedArray()
} }
class ForDelegationSpecifier(typeCandidates: List<TypeCandidate>) : TypeExpression(typeCandidates) { class ForDelegationSpecifier(typeCandidates: List<TypeCandidate>) : TypeExpression(typeCandidates) {
override val cachedLookupElements: Array<LookupElement> = override val cachedLookupElements: Array<LookupElement> =
typeCandidates.map { typeCandidates.map {
val descriptor = it.theType.constructor.declarationDescriptor as ClassDescriptor val types = it.theType.decomposeIntersection()
val text = it.renderedType!! + if (descriptor.kind == ClassKind.INTERFACE) "" else "()" 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) LookupElementBuilder.create(it, text)
}.toTypedArray() }.toTypedArray()
} }
@@ -50,7 +50,15 @@ internal operator fun KotlinType.contains(descriptor: ClassifierDescriptor): Boo
return constructor.declarationDescriptor == descriptor || arguments.any { descriptor in it.type } return constructor.declarationDescriptor == descriptor || arguments.any { descriptor in it.type }
} }
private fun KotlinType.render(typeParameterNameMap: Map<TypeParameterDescriptor, String>, fq: Boolean): String { internal fun KotlinType.decomposeIntersection(): List<KotlinType> {
(constructor as? IntersectionTypeConstructor)?.let {
return it.supertypes.flatMap { it.decomposeIntersection() }
}
return listOf(this)
}
private fun KotlinType.renderSingle(typeParameterNameMap: Map<TypeParameterDescriptor, String>, fq: Boolean): String {
val substitution = typeParameterNameMap val substitution = typeParameterNameMap
.mapValues { .mapValues {
val name = Name.identifier(it.value) val name = Name.identifier(it.value)
@@ -77,6 +85,10 @@ private fun KotlinType.render(typeParameterNameMap: Map<TypeParameterDescriptor,
return renderer.renderType(typeToRender) return renderer.renderType(typeToRender)
} }
private fun KotlinType.render(typeParameterNameMap: Map<TypeParameterDescriptor, String>, fq: Boolean): List<String> {
return decomposeIntersection().map { it.renderSingle(typeParameterNameMap, fq) }
}
internal fun KotlinType.renderShort(typeParameterNameMap: Map<TypeParameterDescriptor, String>) = render(typeParameterNameMap, false) internal fun KotlinType.renderShort(typeParameterNameMap: Map<TypeParameterDescriptor, String>) = render(typeParameterNameMap, false)
internal fun KotlinType.renderLong(typeParameterNameMap: Map<TypeParameterDescriptor, String>) = render(typeParameterNameMap, true) internal fun KotlinType.renderLong(typeParameterNameMap: Map<TypeParameterDescriptor, String>) = render(typeParameterNameMap, true)
@@ -23,6 +23,9 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeIn
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.resolve.BindingContext 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 org.jetbrains.kotlin.types.Variance
import java.util.* import java.util.*
@@ -58,6 +61,17 @@ object CreateClassFromTypeReferenceActionFactory : CreateClassFromUsageFactory<K
} }
} }
private fun getExpectedUpperBound(element: KtUserType, context: BindingContext): KotlinType? {
val projection = (element.parent as? KtTypeReference)?.parent as? KtTypeProjection ?: return null
val argumentList = projection.parent as? KtTypeArgumentList ?: return null
val index = argumentList.arguments.indexOf(projection)
val callElement = argumentList.parent as? KtCallElement ?: return null
val resolvedCall = callElement.getResolvedCall(context) ?: return null
val typeParameterDescriptor = resolvedCall.candidateDescriptor.typeParameters.getOrNull(index) ?: return null
if (typeParameterDescriptor.upperBounds.isEmpty()) return null
return TypeIntersector.getUpperBoundsAsType(typeParameterDescriptor)
}
override fun extractFixData(element: KtUserType, diagnostic: Diagnostic): ClassInfo? { override fun extractFixData(element: KtUserType, diagnostic: Diagnostic): ClassInfo? {
val name = element.referenceExpression?.getReferencedName() ?: return null val name = element.referenceExpression?.getReferencedName() ?: return null
if (element.parent.parent is KtConstructorCalleeExpression) return null if (element.parent.parent is KtConstructorCalleeExpression) return null
@@ -69,13 +83,14 @@ object CreateClassFromTypeReferenceActionFactory : CreateClassFromUsageFactory<K
val qualifierDescriptor = qualifier?.let { context[BindingContext.REFERENCE_TARGET, it] } val qualifierDescriptor = qualifier?.let { context[BindingContext.REFERENCE_TARGET, it] }
val targetParent = getTargetParentByQualifier(file, qualifier != null, qualifierDescriptor) ?: return null val targetParent = getTargetParentByQualifier(file, qualifier != null, qualifierDescriptor) ?: return null
val expectedUpperBound = getExpectedUpperBound(element, context)
val anyType = module.builtIns.anyType val anyType = module.builtIns.anyType
return ClassInfo( return ClassInfo(
name = name, name = name,
targetParent = targetParent, targetParent = targetParent,
expectedTypeInfo = TypeInfo.Empty, expectedTypeInfo = expectedUpperBound?.let { TypeInfo.ByType(it, Variance.INVARIANT) } ?: TypeInfo.Empty,
typeArguments = element.typeArgumentsAsTypes.map { typeArguments = element.typeArgumentsAsTypes.map {
if (it != null) TypeInfo(it, Variance.INVARIANT) else TypeInfo(anyType, Variance.INVARIANT) if (it != null) TypeInfo(it, Variance.INVARIANT) else TypeInfo(anyType, Variance.INVARIANT)
} }
@@ -0,0 +1,8 @@
// "Create class 'Foo'" "true"
interface I
fun <T : I> foo() {}
fun x() {
foo<<caret>Foo>()
}
@@ -0,0 +1,12 @@
// "Create class 'Foo'" "true"
interface I
fun <T : I> foo() {}
fun x() {
foo<Foo>()
}
class Foo : I {
}
@@ -0,0 +1,9 @@
// "Create class 'Foo'" "true"
open class A
interface I
fun <T : I> foo() where T : A {}
fun x() {
foo<<caret>Foo>()
}
@@ -0,0 +1,13 @@
// "Create class 'Foo'" "true"
open class A
interface I
fun <T : I> foo() where T : A {}
fun x() {
foo<Foo>()
}
class Foo : I, A() {
}
@@ -2279,6 +2279,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/traitNotQualifierNoTypeArgs.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/traitNotQualifierNoTypeArgs.kt");
doTest(fileName); 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);
}
} }
} }