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)
} != 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
@@ -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")
}
}
@@ -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)
@@ -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()
}
@@ -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)
@@ -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)
}
@@ -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");
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);
}
}
}