Create Class from Usage: Support multiple containers

This commit is contained in:
Alexey Sedunov
2017-07-20 20:27:07 +03:00
parent 78117b3e7b
commit d30461afc6
8 changed files with 112 additions and 77 deletions
@@ -490,7 +490,8 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
val safeName = name.quoteIfNeeded()
when (kind) {
ClassKind.ENUM_ENTRY -> {
if (!(targetParent is KtClass && targetParent.isEnum())) throw AssertionError("Enum class expected: ${targetParent.text}")
val targetParent = targetParents.singleOrNull()
if (!(targetParent is KtClass && targetParent.isEnum())) throw AssertionError("Enum class expected: ${targetParent?.text}")
val hasParameters = targetParent.primaryConstructorParameters.isNotEmpty()
psiFactory.createEnumEntry("$safeName${if (hasParameters) "()" else " "}")
}
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.utils.ifEmpty
import java.util.*
object CreateClassFromCallWithConstructorCalleeActionFactory : CreateClassFromUsageFactory<KtCallElement>() {
@@ -61,7 +62,7 @@ object CreateClassFromCallWithConstructorCalleeActionFactory : CreateClassFromUs
val qualifier = userType.qualifier?.referenceExpression
val qualifierDescriptor = qualifier?.let { context[BindingContext.REFERENCE_TARGET, it] }
val targetParent = getTargetParentByQualifier(file, qualifier != null, qualifierDescriptor) ?: return null
val targetParents = getTargetParentsByQualifier(file, qualifier != null, qualifierDescriptor).ifEmpty { return null }
val anyType = module.builtIns.nullableAnyType
val valueArguments = element.valueArguments
@@ -80,7 +81,7 @@ object CreateClassFromCallWithConstructorCalleeActionFactory : CreateClassFromUs
return ClassInfo(
name = calleeRef.getReferencedName(),
targetParent = targetParent,
targetParents = targetParents,
expectedTypeInfo = TypeInfo.Empty,
parameterInfos = parameterInfos,
open = !isAnnotation,
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.utils.ifEmpty
import java.util.*
object CreateClassFromConstructorCallActionFactory: CreateClassFromUsageFactory<KtCallExpression>() {
@@ -43,11 +44,12 @@ object CreateClassFromConstructorCallActionFactory: CreateClassFromUsageFactory<
val (context, moduleDescriptor) = element.analyzeFullyAndGetResult()
val file = element.containingFile as? KtFile ?: return emptyList()
val call = element.getCall(context) ?: return emptyList()
val targetParent = getTargetParentByCall(call, file, context) ?: return emptyList()
val targetParents = getTargetParentsByCall(call, file, context).ifEmpty { return emptyList() }
val classKind = if (inAnnotationEntry) ClassKind.ANNOTATION_CLASS else ClassKind.PLAIN_CLASS
val fullCallExpr = element.getQualifiedExpressionForSelectorOrThis()
if (!fullCallExpr.getInheritableTypeInfo(context, moduleDescriptor, targetParent).second(classKind)) return emptyList()
val expectedType = fullCallExpr.guessTypeForClass(context, moduleDescriptor)
if (expectedType != null && !targetParents.any { getClassKindFilter(expectedType, it)(classKind) }) return emptyList()
return listOf(classKind)
}
@@ -75,7 +77,7 @@ object CreateClassFromConstructorCallActionFactory: CreateClassFromUsageFactory<
val (context, moduleDescriptor) = callExpr.analyzeFullyAndGetResult()
val call = callExpr.getCall(context) ?: return null
val targetParent = getTargetParentByCall(call, file, context) ?: return null
val targetParents = getTargetParentsByCall(call, file, context).ifEmpty { return null }
val inner = isInnerClassExpected(call)
val valueArguments = callExpr.valueArguments
@@ -90,14 +92,17 @@ object CreateClassFromConstructorCallActionFactory: CreateClassFromUsageFactory<
val classKind = if (inAnnotationEntry) ClassKind.ANNOTATION_CLASS else ClassKind.PLAIN_CLASS
val (expectedTypeInfo, filter) = fullCallExpr.getInheritableTypeInfo(context, moduleDescriptor, targetParent)
if (!filter(classKind)) return null
val expectedType = fullCallExpr.guessTypeForClass(context, moduleDescriptor)
val expectedTypeInfo = expectedType?.toClassTypeInfo() ?: TypeInfo.Empty
val filteredParents = if (expectedType != null) {
targetParents.filter { getClassKindFilter(expectedType, it)(classKind) }.ifEmpty { return null }
} else targetParents
val typeArgumentInfos = if (inAnnotationEntry) Collections.emptyList() else callExpr.getTypeInfoForTypeArguments()
return ClassInfo(
name = name,
targetParent = targetParent,
targetParents = filteredParents,
expectedTypeInfo = expectedTypeInfo,
inner = inner,
typeArguments = typeArgumentInfos,
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.utils.ifEmpty
import java.util.Arrays
import java.util.Collections
@@ -69,11 +70,15 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
val receiverSelector = (fullCallExpr as? KtQualifiedExpression)?.receiverExpression?.getQualifiedElementSelector() as? KtReferenceExpression
val qualifierDescriptor = receiverSelector?.let { context[BindingContext.REFERENCE_TARGET, it] }
val targetParent =
getTargetParentByQualifier(element.containingKtFile, receiverSelector != null, qualifierDescriptor)
?: return Collections.emptyList()
val targetParents = getTargetParentsByQualifier(
element.containingKtFile,
receiverSelector != null,
qualifierDescriptor
).ifEmpty { return emptyList() }
element.getCreatePackageFixIfApplicable(targetParent)?.let { return emptyList() }
targetParents.forEach {
if (element.getCreatePackageFixIfApplicable(it) != null) return emptyList()
}
if (!name.checkClassName()) return emptyList()
@@ -82,7 +87,7 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
.filter {
when (it) {
ClassKind.ANNOTATION_CLASS -> inImport
ClassKind.ENUM_ENTRY -> inImport && isEnum(targetParent)
ClassKind.ENUM_ENTRY -> inImport && targetParents.any { isEnum(it) }
else -> true
}
}
@@ -95,19 +100,22 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
if (fullCallExpr.getAssignmentByLHS() != null) return Collections.emptyList()
val call = element.getCall(context) ?: return Collections.emptyList()
val targetParent = getTargetParentByCall(call, file, context) ?: return Collections.emptyList()
val targetParents = getTargetParentsByCall(call, file, context).ifEmpty { return emptyList() }
if (isInnerClassExpected(call)) return Collections.emptyList()
val filter = fullCallExpr.getInheritableTypeInfo(context, moduleDescriptor, targetParent).second
val allKinds = Arrays.asList(ClassKind.OBJECT, ClassKind.ENUM_ENTRY)
return Arrays.asList(ClassKind.OBJECT, ClassKind.ENUM_ENTRY)
.filter {
filter(it) && when (it) {
ClassKind.OBJECT -> true
ClassKind.ENUM_ENTRY -> isEnum(targetParent)
else -> false
}
val expectedType = fullCallExpr.guessTypeForClass(context, moduleDescriptor)
return allKinds.filter { classKind ->
targetParents.any { targetParent ->
(expectedType == null || getClassKindFilter(expectedType, targetParent)(classKind)) && when (classKind) {
ClassKind.OBJECT -> true
ClassKind.ENUM_ENTRY -> isEnum(targetParent)
else -> false
}
}
}
}
override fun extractFixData(element: KtSimpleNameExpression, diagnostic: Diagnostic): ClassInfo? {
@@ -123,25 +131,27 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
val receiverSelector = (fullCallExpr as? KtQualifiedExpression)?.receiverExpression?.getQualifiedElementSelector() as? KtReferenceExpression
val qualifierDescriptor = receiverSelector?.let { context[BindingContext.REFERENCE_TARGET, it] }
val targetParent =
getTargetParentByQualifier(element.containingKtFile, receiverSelector != null, qualifierDescriptor)
?: return null
val targetParents = getTargetParentsByQualifier(
element.containingKtFile,
receiverSelector != null,
qualifierDescriptor
).ifEmpty { return null }
return ClassInfo(
name = name,
targetParent = targetParent,
targetParents = targetParents,
expectedTypeInfo = TypeInfo.Empty
)
}
val call = element.getCall(context) ?: return null
val targetParent = getTargetParentByCall(call, file, context) ?: return null
val targetParents = getTargetParentsByCall(call, file, context).ifEmpty { return null }
val expectedTypeInfo = fullCallExpr.getInheritableTypeInfo(context, moduleDescriptor, targetParent).first
val expectedTypeInfo = fullCallExpr.guessTypeForClass(context, moduleDescriptor)?.toClassTypeInfo() ?: TypeInfo.Empty
return ClassInfo(
name = name,
targetParent = targetParent,
targetParents = targetParents,
expectedTypeInfo = expectedTypeInfo
)
}
@@ -27,6 +27,7 @@ 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.utils.ifEmpty
import java.util.*
object CreateClassFromTypeReferenceActionFactory : CreateClassFromUsageFactory<KtUserType>() {
@@ -82,14 +83,14 @@ object CreateClassFromTypeReferenceActionFactory : CreateClassFromUsageFactory<K
val qualifier = element.qualifier?.referenceExpression
val qualifierDescriptor = qualifier?.let { context[BindingContext.REFERENCE_TARGET, it] }
val targetParent = getTargetParentByQualifier(file, qualifier != null, qualifierDescriptor) ?: return null
val targetParents = getTargetParentsByQualifier(file, qualifier != null, qualifierDescriptor).ifEmpty { return null }
val expectedUpperBound = getExpectedUpperBound(element, context)
val anyType = module.builtIns.anyType
return ClassInfo(
name = name,
targetParent = targetParent,
targetParents = targetParents,
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)
@@ -34,8 +34,10 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageFixBase
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.*
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.ClassKind.*
import org.jetbrains.kotlin.idea.refactoring.canRefactor
import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import java.util.*
@@ -56,7 +58,7 @@ val ClassKind.actionPriority: IntentionActionPriority
data class ClassInfo(
val kind: ClassKind = ClassKind.DEFAULT,
val name: String,
val targetParent: PsiElement,
val targetParents: List<PsiElement>,
val expectedTypeInfo: TypeInfo,
val inner: Boolean = false,
val open: Boolean = false,
@@ -74,14 +76,19 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
if (!super.isAvailable(project, editor, file)) return false
with(classInfo) {
if (kind == DEFAULT) return false
if (targetParent is PsiClass) {
if (kind == OBJECT || kind == ENUM_ENTRY) return false
if (targetParent.isInterface && inner) return false
targetParents.forEach {
if (it is PsiClass) {
if (kind == OBJECT || kind == ENUM_ENTRY) return false
if (it.isInterface && inner) return false
}
}
}
return true
}
override fun startInWriteAction() = false
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
fun createFileByPackage(psiPackage: PsiPackage): KtFile? {
val directories = psiPackage.directories.filter { it.canRefactor() }
@@ -114,20 +121,30 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
return targetFile
}
with (classInfo) {
val targetParent =
when (targetParent) {
is KtElement, is PsiClass -> targetParent
is PsiPackage -> createFileByPackage(targetParent)
else -> throw AssertionError("Unexpected element: " + targetParent.text)
} ?: return
if (editor == null) return
val constructorInfo = PrimaryConstructorInfo(classInfo, expectedTypeInfo)
val builder = CallableBuilderConfiguration(
Collections.singletonList(constructorInfo), element as KtElement, file, editor, false, kind == PLAIN_CLASS || kind == INTERFACE
).createBuilder()
builder.placement = CallablePlacement.NoReceiver(targetParent)
project.executeCommand(text) { builder.build() }
with (classInfo) {
chooseContainerElementIfNecessary(targetParents, editor, "Choose class container", true, { it }) {
runWriteAction {
val targetParent =
when (it) {
is KtElement, is PsiClass -> it
is PsiPackage -> createFileByPackage(it)
else -> throw AssertionError("Unexpected element: " + it.text)
} ?: return@runWriteAction
val constructorInfo = PrimaryConstructorInfo(classInfo, expectedTypeInfo)
val builder = CallableBuilderConfiguration(
Collections.singletonList(constructorInfo),
element as KtElement,
file,
editor,
false,
kind == PLAIN_CLASS || kind == INTERFACE
).createBuilder()
builder.placement = CallablePlacement.NoReceiver(targetParent)
project.executeCommand(text) { builder.build() }
}
}
}
}
@@ -50,56 +50,57 @@ internal fun String.checkClassName(): Boolean = isNotEmpty() && Character.isUppe
private fun String.checkPackageName(): Boolean = isNotEmpty() && Character.isLowerCase(first())
internal fun getTargetParentByQualifier(
internal fun getTargetParentsByQualifier(
file: KtFile,
isQualified: Boolean,
qualifierDescriptor: DeclarationDescriptor?): PsiElement? {
qualifierDescriptor: DeclarationDescriptor?
): List<PsiElement> {
val project = file.project
val targetParent: PsiElement? = when {
val targetParents: List<PsiElement> = when {
!isQualified ->
file
listOf(file)
qualifierDescriptor is ClassDescriptor ->
DescriptorToSourceUtilsIde.getAnyDeclaration(project, qualifierDescriptor)
listOfNotNull(DescriptorToSourceUtilsIde.getAnyDeclaration(project, qualifierDescriptor))
qualifierDescriptor is PackageViewDescriptor ->
if (qualifierDescriptor.fqName != file.packageFqName) {
JavaPsiFacade.getInstance(project).findPackage(qualifierDescriptor.fqName.asString())
listOfNotNull(JavaPsiFacade.getInstance(project).findPackage(qualifierDescriptor.fqName.asString()))
}
else file
else listOf(file)
else ->
null
emptyList()
}
return if (targetParent?.canRefactor() ?: false) return targetParent else null
return targetParents.filter { it.canRefactor() }
}
internal fun getTargetParentByCall(call: Call, file: KtFile, context: BindingContext): PsiElement? {
internal fun getTargetParentsByCall(call: Call, file: KtFile, context: BindingContext): List<PsiElement> {
val receiver = call.explicitReceiver
return when (receiver) {
null -> getTargetParentByQualifier(file, false, null)
is Qualifier -> getTargetParentByQualifier(file, true, context[BindingContext.REFERENCE_TARGET, receiver.referenceExpression])
is ReceiverValue -> getTargetParentByQualifier(file, true, receiver.type.constructor.declarationDescriptor)
null -> getTargetParentsByQualifier(file, false, null)
is Qualifier -> getTargetParentsByQualifier(file, true, context[BindingContext.REFERENCE_TARGET, receiver.referenceExpression])
is ReceiverValue -> getTargetParentsByQualifier(file, true, receiver.type.constructor.declarationDescriptor)
else -> throw AssertionError("Unexpected receiver: $receiver")
}
}
internal fun isInnerClassExpected(call: Call) = call.explicitReceiver is ReceiverValue
internal fun KtExpression.getInheritableTypeInfo(
context: BindingContext,
moduleDescriptor: ModuleDescriptor,
containingDeclaration: PsiElement): Pair<TypeInfo, (ClassKind) -> Boolean> {
val types = guessTypes(context, moduleDescriptor, coerceUnusedToUnit = false)
if (types.size != 1) return TypeInfo.Empty to { _ -> true }
internal fun KtExpression.guessTypeForClass(context: BindingContext, moduleDescriptor: ModuleDescriptor) =
guessTypes(context, moduleDescriptor, coerceUnusedToUnit = false).singleOrNull()
val type = types.first()
val descriptor = type.constructor.declarationDescriptor ?: return TypeInfo.Empty to { _ -> false }
internal fun KotlinType.toClassTypeInfo(): TypeInfo {
return TypeInfo.ByType(this, Variance.OUT_VARIANCE).noSubstitutions()
}
val canHaveSubtypes = !(type.constructor.isFinal || type.containsStarProjections())
internal fun getClassKindFilter(expectedType: KotlinType, containingDeclaration: PsiElement): (ClassKind) -> Boolean {
val descriptor = expectedType.constructor.declarationDescriptor ?: return { _ -> false }
val canHaveSubtypes = !(expectedType.constructor.isFinal || expectedType.containsStarProjections())
val isEnum = DescriptorUtils.isEnumClass(descriptor)
if (!(canHaveSubtypes || isEnum)
|| descriptor is TypeParameterDescriptor) return TypeInfo.Empty to { _ -> false }
|| descriptor is TypeParameterDescriptor) return { _ -> false }
return TypeInfo.ByType(type, Variance.OUT_VARIANCE).noSubstitutions() to { classKind ->
return { classKind ->
when (classKind) {
ClassKind.ENUM_ENTRY -> isEnum && containingDeclaration == DescriptorToSourceUtils.descriptorToDeclaration(descriptor)
ClassKind.INTERFACE -> containingDeclaration !is PsiClass
@@ -40,17 +40,16 @@ object CreateTypeAliasFromTypeReferenceActionFactory : KotlinSingleIntentionActi
if (!element.languageVersionSettings.supportsFeature(LanguageFeature.TypeAliases)) return null
val classInfo = CreateClassFromTypeReferenceActionFactory.extractFixData(element, diagnostic) ?: return null
if (classInfo.targetParent is KtDeclaration) return null
if (classInfo.targetParent is PsiPackage) return null
val targetParent = classInfo.targetParents.singleOrNull { it !is KtDeclaration && it !is PsiPackage } ?: return null
val expectedType = getTypeConstraintInfo(element)?.upperBound
if (expectedType != null && expectedType.containsError()) return null
val validator = CollectingNameValidator(
filter = NewDeclarationNameValidator(classInfo.targetParent, null, NewDeclarationNameValidator.Target.FUNCTIONS_AND_CLASSES)
filter = NewDeclarationNameValidator(targetParent, null, NewDeclarationNameValidator.Target.FUNCTIONS_AND_CLASSES)
)
val typeParameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(classInfo.typeArguments.size, validator)
return TypeAliasInfo(classInfo.name, classInfo.targetParent, typeParameterNames, expectedType)
return TypeAliasInfo(classInfo.name, targetParent, typeParameterNames, expectedType)
}
override fun createFix(originalElement: KtUserType, data: TypeAliasInfo) = CreateTypeAliasFromUsageFix(originalElement, data)