Create Class from Usage: Use type parameter bounds as supertypes
#KT-15242 Fixed
This commit is contained in:
@@ -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<KotlinType> {
|
||||
fun KotlinType.getResolvableApproximations(
|
||||
scope: LexicalScope?,
|
||||
checkTypeParameters: Boolean,
|
||||
allowIntersections: Boolean = false
|
||||
): Sequence<KotlinType> {
|
||||
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
|
||||
|
||||
+26
-23
@@ -85,13 +85,13 @@ import java.util.*
|
||||
*/
|
||||
class TypeCandidate(val theType: KotlinType, scope: HierarchicalScope? = null) {
|
||||
val typeParameters: Array<TypeParameterDescriptor>
|
||||
var renderedType: String? = null
|
||||
var renderedTypes: List<String> = emptyList()
|
||||
private set
|
||||
var renderedTypeParameters: List<RenderedTypeParameter>? = null
|
||||
private set
|
||||
|
||||
fun render(typeParameterNameMap: Map<TypeParameterDescriptor, String>, 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<TypeCandidate>.getTypeByRenderedType(renderedType: String): KotlinType? =
|
||||
firstOrNull { it.renderedType == renderedType }?.theType
|
||||
fun List<TypeCandidate>.getTypeByRenderedType(renderedTypes: List<String>): KotlinType? =
|
||||
firstOrNull { it.renderedTypes == renderedTypes }?.theType
|
||||
|
||||
class CallableBuilderConfiguration(
|
||||
val callableInfos: List<CallableInfo>,
|
||||
@@ -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<KtElement>()
|
||||
|
||||
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<String, Array<String>>()
|
||||
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<KtTypeReference>, 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 <D : KtNamedDeclaration> placeDeclarationInContainer(
|
||||
return declarationInPlace
|
||||
}
|
||||
|
||||
internal fun KtNamedDeclaration.getReturnTypeReference(): KtTypeReference? {
|
||||
internal fun KtNamedDeclaration.getReturnTypeReference() = getReturnTypeReferences().singleOrNull()
|
||||
|
||||
internal fun KtNamedDeclaration.getReturnTypeReferences(): List<KtTypeReference> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+6
-3
@@ -104,14 +104,17 @@ internal class ParameterNameExpression(
|
||||
internal abstract class TypeExpression(val typeCandidates: List<TypeCandidate>) : Expression() {
|
||||
class ForTypeReference(typeCandidates: List<TypeCandidate>) : TypeExpression(typeCandidates) {
|
||||
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) {
|
||||
override val cachedLookupElements: Array<LookupElement> =
|
||||
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()
|
||||
}
|
||||
|
||||
+13
-1
@@ -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<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
|
||||
.mapValues {
|
||||
val name = Name.identifier(it.value)
|
||||
@@ -77,6 +85,10 @@ private fun KotlinType.render(typeParameterNameMap: Map<TypeParameterDescriptor,
|
||||
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.renderLong(typeParameterNameMap: Map<TypeParameterDescriptor, String>) = render(typeParameterNameMap, true)
|
||||
|
||||
|
||||
+16
-1
@@ -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<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? {
|
||||
val name = element.referenceExpression?.getReferencedName() ?: 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 targetParent = getTargetParentByQualifier(file, qualifier != null, qualifierDescriptor) ?: return null
|
||||
val expectedUpperBound = getExpectedUpperBound(element, context)
|
||||
|
||||
val anyType = module.builtIns.anyType
|
||||
|
||||
return ClassInfo(
|
||||
name = name,
|
||||
targetParent = targetParent,
|
||||
expectedTypeInfo = TypeInfo.Empty,
|
||||
expectedTypeInfo = expectedUpperBound?.let { TypeInfo.ByType(it, Variance.INVARIANT) } ?: TypeInfo.Empty,
|
||||
typeArguments = element.typeArgumentsAsTypes.map {
|
||||
if (it != null) TypeInfo(it, Variance.INVARIANT) else TypeInfo(anyType, Variance.INVARIANT)
|
||||
}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Create class 'Foo'" "true"
|
||||
interface I
|
||||
|
||||
fun <T : I> foo() {}
|
||||
|
||||
fun x() {
|
||||
foo<<caret>Foo>()
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// "Create class 'Foo'" "true"
|
||||
interface I
|
||||
|
||||
fun <T : I> foo() {}
|
||||
|
||||
fun x() {
|
||||
foo<Foo>()
|
||||
}
|
||||
|
||||
class Foo : I {
|
||||
|
||||
}
|
||||
Vendored
+9
@@ -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>()
|
||||
}
|
||||
+13
@@ -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");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user