Create from Usage: Implement "Create type parameter" quickfix

#KT-11525 Fixed
This commit is contained in:
Alexey Sedunov
2016-08-24 11:49:05 +03:00
parent 6a2edabbd4
commit 63092f6985
43 changed files with 693 additions and 49 deletions
+6
View File
@@ -101,6 +101,12 @@ These artifacts include extensions for the types available in the latter JDKs, s
- [`KT-8672`](https://youtrack.jetbrains.com/issue/KT-8672) Rename: Optimize search of parameter references in calls with named arguments
- [`KT-9285`](https://youtrack.jetbrains.com/issue/KT-9285) Rename: Optimize search of private class members
#### Intention actions, inspections and quickfixes
##### New features
- [`KT-11525`](https://youtrack.jetbrains.com/issue/KT-11525) Implement "Create type parameter" quickfix
#### Refactorings
- [`KT-13535`](https://youtrack.jetbrains.com/issue/KT-13535) Pull Up: Remove visibility modifiers on adding 'override'
@@ -18,18 +18,21 @@ package org.jetbrains.kotlin.psi
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.lexer.KtToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.psiUtil.siblings
object EditCommaSeparatedListHelper {
fun <TItem: KtElement> addItem(list: KtElement, allItems: List<TItem>, item: TItem): TItem {
return addItemBefore(list, allItems, item, null)
@JvmOverloads
fun <TItem: KtElement> addItem(list: KtElement, allItems: List<TItem>, item: TItem, prefix: KtToken = KtTokens.LPAR): TItem {
return addItemBefore(list, allItems, item, null, prefix)
}
fun <TItem: KtElement> addItemAfter(list: KtElement, allItems: List<TItem>, item: TItem, anchor: TItem?): TItem {
@JvmOverloads
fun <TItem: KtElement> addItemAfter(list: KtElement, allItems: List<TItem>, item: TItem, anchor: TItem?, prefix: KtToken = KtTokens.LPAR): TItem {
assert(anchor == null || anchor.parent == list)
if (allItems.isEmpty()) {
if (list.firstChild.node.elementType == KtTokens.LPAR) {
if (list.firstChild.node.elementType == prefix) {
return list.addAfter(item, list.firstChild) as TItem
}
else {
@@ -49,7 +52,8 @@ object EditCommaSeparatedListHelper {
}
}
fun <TItem: KtElement> addItemBefore(list: KtElement, allItems: List<TItem>, item: TItem, anchor: TItem?): TItem {
@JvmOverloads
fun <TItem: KtElement> addItemBefore(list: KtElement, allItems: List<TItem>, item: TItem, anchor: TItem?, prefix: KtToken = KtTokens.LPAR): TItem {
val anchorAfter: TItem?
if (allItems.isEmpty()) {
assert(anchor == null)
@@ -65,7 +69,7 @@ object EditCommaSeparatedListHelper {
anchorAfter = allItems.get(allItems.size - 1)
}
}
return addItemAfter(list, allItems, item, anchorAfter)
return addItemAfter(list, allItems, item, anchorAfter, prefix)
}
fun <TItem: KtElement> removeItem(item: TItem) {
@@ -90,6 +90,8 @@ class KtPsiFactory(private val project: Project) {
return (property.getInitializer() as KtCallExpression).typeArgumentList!!
}
fun createTypeArgument(text: String) = createTypeArguments("<$text>").arguments.first()
fun createType(type: String): KtTypeReference {
val typeReference = createTypeIfPossible(type)
if (typeReference == null || typeReference.text != type) {
@@ -316,6 +318,10 @@ class KtPsiFactory(private val project: Project) {
return createFunction("fun foo$text{}").getValueParameterList()!!
}
fun createTypeParameterList(text: String) = createClass("class Foo$text").typeParameterList!!
fun createTypeParameter(text: String) = createTypeParameterList("<$text>").parameters.first()!!
fun createFunctionLiteralParameterList(text: String): KtParameterList {
return (createExpression("{ $text -> 0}") as KtLambdaExpression).functionLiteral.valueParameterList!!
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.psi;
import com.intellij.lang.ASTNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub;
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes;
@@ -43,7 +44,7 @@ public class KtTypeArgumentList extends KtElementImplStub<KotlinPlaceHolderStub<
}
@NotNull
public KtTypeProjection addArgument(@NotNull KtTypeProjection argument) {
return EditCommaSeparatedListHelper.INSTANCE.addItem(this, getArguments(), argument);
public KtTypeProjection addArgument(@NotNull KtTypeProjection typeArgument) {
return EditCommaSeparatedListHelper.INSTANCE.addItem(this, getArguments(), typeArgument, KtTokens.LT);
}
}
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.psi;
import com.intellij.lang.ASTNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub;
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes;
@@ -37,6 +38,11 @@ public class KtTypeParameterList extends KtElementImplStub<KotlinPlaceHolderStub
return getStubOrPsiChildrenAsList(KtStubElementTypes.TYPE_PARAMETER);
}
@NotNull
public KtTypeParameter addParameter(@NotNull KtTypeParameter typeParameter) {
return EditCommaSeparatedListHelper.INSTANCE.addItem(this, getParameters(), typeParameter, KtTokens.LT);
}
@Override
public <R, D> R accept(@NotNull KtVisitor<R, D> visitor, D data) {
return visitor.visitTypeParameterList(this, data);
@@ -271,4 +271,18 @@ fun dropEnclosingParenthesesIfPossible(expression: KtExpression): KtExpression {
val parent = expression.parent as? KtParenthesizedExpression ?: return expression
if (!KtPsiUtil.areParenthesesUseless(parent)) return expression
return parent.replaced(expression)
}
fun KtTypeParameterListOwner.addTypeParameter(typeParameter: KtTypeParameter): KtTypeParameter? {
typeParameterList?.let { return it.addParameter(typeParameter) }
val list = KtPsiFactory(this).createTypeParameterList("<X>")
list.parameters[0].replace(typeParameter)
val leftAnchor = when (this) {
is KtClass -> nameIdentifier ?: getClassOrInterfaceKeyword()
is KtNamedFunction -> funKeyword
is KtProperty -> valOrVarKeyword
else -> null
} ?: return null
return (addAfter(list, leftAnchor) as KtTypeParameterList).parameters.first()
}
@@ -22,11 +22,13 @@ import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.psi.KtCallElement
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.ErrorUtils
class InsertExplicitTypeArgumentsIntention : SelfTargetingRangeIntention<KtCallExpression>(KtCallExpression::class.java, "Add explicit type arguments"), LowPriorityAction {
@@ -34,17 +36,10 @@ class InsertExplicitTypeArgumentsIntention : SelfTargetingRangeIntention<KtCallE
return if (isApplicableTo(element, element.analyze())) element.calleeExpression!!.textRange else null
}
override fun applyTo(element: KtCallExpression, editor: Editor?) {
val argumentList = createTypeArguments(element, element.analyze())!!
val callee = element.calleeExpression!!
val newArgumentList = element.addAfter(argumentList, callee) as KtTypeArgumentList
ShortenReferences.DEFAULT.process(newArgumentList)
}
override fun applyTo(element: KtCallExpression, editor: Editor?) = applyTo(element)
companion object {
fun isApplicableTo(element: KtCallExpression, bindingContext: BindingContext): Boolean {
fun isApplicableTo(element: KtCallElement, bindingContext: BindingContext = element.analyze(BodyResolveMode.PARTIAL)): Boolean {
if (!element.typeArguments.isEmpty()) return false
if (element.calleeExpression == null) return false
@@ -53,7 +48,18 @@ class InsertExplicitTypeArgumentsIntention : SelfTargetingRangeIntention<KtCallE
return typeArgs.isNotEmpty() && typeArgs.values.none { ErrorUtils.containsErrorType(it) }
}
fun createTypeArguments(element: KtCallExpression, bindingContext: BindingContext): KtTypeArgumentList? {
fun applyTo(element: KtCallElement, shortenReferences: Boolean = true) {
val argumentList = createTypeArguments(element, element.analyze())!!
val callee = element.calleeExpression!!
val newArgumentList = element.addAfter(argumentList, callee) as KtTypeArgumentList
if (shortenReferences) {
ShortenReferences.DEFAULT.process(newArgumentList)
}
}
fun createTypeArguments(element: KtCallElement, bindingContext: BindingContext): KtTypeArgumentList? {
val resolvedCall = element.getResolvedCall(bindingContext) ?: return null
val args = resolvedCall.typeArguments
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.CreateClas
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.CreateClassFromReferenceExpressionActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.CreateClassFromTypeReferenceActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeAlias.CreateTypeAliasFromTypeReferenceActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.CreateTypeParameterByRefActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateLocalVariableActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByNamedArgumentActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByRefActionFactory
@@ -413,5 +414,7 @@ class QuickFixRegistrar : QuickFixContributor {
REIFIED_TYPE_PARAMETER_NO_INLINE.registerFactory(AddInlineToFunctionWithReifiedFix)
VALUE_PARAMETER_WITH_NO_TYPE_ANNOTATION.registerFactory(AddTypeAnnotationToValueParameterFix)
UNRESOLVED_REFERENCE.registerFactory(CreateTypeParameterByRefActionFactory)
}
}
@@ -24,6 +24,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiPackage
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.quickfix.DelegatingIntentionAction
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo
@@ -31,17 +32,18 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.contai
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.guessTypes
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.noSubstitutions
import org.jetbrains.kotlin.idea.refactoring.canRefactor
import org.jetbrains.kotlin.psi.Call
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.Qualifier
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitution
import java.lang.AssertionError
import java.util.*
import org.jetbrains.kotlin.descriptors.ClassKind as ClassDescriptorKind
internal fun String.checkClassName(): Boolean = isNotEmpty() && Character.isUpperCase(first())
@@ -130,3 +132,38 @@ internal fun KtSimpleNameExpression.getCreatePackageFixIfApplicable(targetParent
override fun getText(): String = "Create package '$fullName'"
}
}
data class UnsubstitutedTypeConstraintInfo(
val typeParameter: TypeParameterDescriptor,
private val originalSubstitution: Map<TypeConstructor, TypeProjection>,
val upperBound: KotlinType
) {
fun performSubstitution(vararg substitution: Pair<TypeConstructor, TypeProjection>): TypeConstraintInfo? {
val currentSubstitution = LinkedHashMap<TypeConstructor, TypeProjection>().apply {
this.putAll(originalSubstitution)
this.putAll(substitution)
}
val substitutedUpperBound = TypeSubstitutor.create(currentSubstitution).substitute(upperBound, Variance.INVARIANT) ?: return null
return TypeConstraintInfo(typeParameter, substitutedUpperBound)
}
}
data class TypeConstraintInfo(
val typeParameter: TypeParameterDescriptor,
val upperBound: KotlinType
)
fun getUnsubstitutedTypeConstraintInfo(element: KtTypeElement): UnsubstitutedTypeConstraintInfo? {
val context = element.analyze(BodyResolveMode.PARTIAL)
val containingTypeArg = (element.parent as? KtTypeReference)?.parent as? KtTypeProjection ?: return null
val argumentList = containingTypeArg.parent as? KtTypeArgumentList ?: return null
val containingTypeRef = (argumentList.parent as? KtTypeElement)?.parent as? KtTypeReference ?: return null
val containingType = containingTypeRef.getAbbreviatedTypeOrType(context) ?: return null
val baseType = containingType.constructor.declarationDescriptor?.defaultType ?: return null
val typeParameter = containingType.constructor.parameters.getOrNull(argumentList.arguments.indexOf(containingTypeArg))
val upperBound = typeParameter?.upperBounds?.singleOrNull() ?: return null
val substitution = getTypeSubstitution(baseType, containingType) ?: return null
return UnsubstitutedTypeConstraintInfo(typeParameter, substitution, upperBound)
}
fun getTypeConstraintInfo(element: KtTypeElement) = getUnsubstitutedTypeConstraintInfo(element)?.performSubstitution()
@@ -16,45 +16,21 @@
package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeAlias
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
import org.jetbrains.kotlin.idea.quickfix.IntentionActionPriority
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactoryWithDelegate
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.CreateClassFromTypeReferenceActionFactory
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.getTypeConstraintInfo
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitution
import org.jetbrains.kotlin.types.typeUtil.containsError
object CreateTypeAliasFromTypeReferenceActionFactory : KotlinSingleIntentionActionFactoryWithDelegate<KtUserType, TypeAliasInfo>(IntentionActionPriority.LOW) {
override fun getElementOfInterest(diagnostic: Diagnostic) = CreateClassFromTypeReferenceActionFactory.getElementOfInterest(diagnostic)
data class TypeConstraintInfo(val typeParameter: TypeParameterDescriptor, val upperBound: KotlinType)
private fun getTypeConstraintInfo(element: KtUserType): TypeConstraintInfo? {
val context = element.analyze(BodyResolveMode.PARTIAL)
val containingTypeArg = (element.parent as? KtTypeReference)?.parent as? KtTypeProjection ?: return null
val argumentList = containingTypeArg.parent as? KtTypeArgumentList ?: return null
val containingTypeRef = (argumentList.parent as? KtTypeElement)?.parent as? KtTypeReference ?: return null
val containingType = containingTypeRef.getAbbreviatedTypeOrType(context) ?: return null
val baseType = containingType.constructor.declarationDescriptor?.defaultType ?: return null
val typeParameter = containingType.constructor.parameters.getOrNull(argumentList.arguments.indexOf(containingTypeArg))
val upperBound = typeParameter?.upperBounds?.singleOrNull() ?: return null
val substitution = getTypeSubstitution(baseType, containingType) ?: return null
val substitutedUpperBound = TypeSubstitutor.create(substitution).substitute(upperBound, Variance.INVARIANT) ?: return null
if (substitutedUpperBound.containsError()) return null
return TypeConstraintInfo(typeParameter, substitutedUpperBound)
}
override fun extractFixData(element: KtUserType, diagnostic: Diagnostic): TypeAliasInfo? {
if (element.getParentOfTypeAndBranch<KtUserType>(true) { qualifier } != null) return null
@@ -0,0 +1,105 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter
import com.intellij.psi.PsiElement
import com.intellij.psi.SmartPsiElementPointer
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionFactoryWithDelegate
import org.jetbrains.kotlin.idea.quickfix.QuickFixWithDelegateFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.getUnsubstitutedTypeConstraintInfo
import org.jetbrains.kotlin.idea.refactoring.introduce.isObjectOrNonInnerClass
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeProjectionImpl
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.containsError
data class CreateTypeParameterData(
val name: String,
val declaration: KtTypeParameterListOwner,
val upperBoundType: KotlinType?,
val fakeTypeParameter: TypeParameterDescriptor
)
object CreateTypeParameterByRefActionFactory : KotlinIntentionActionFactoryWithDelegate<KtUserType, CreateTypeParameterData>() {
override fun getElementOfInterest(diagnostic: Diagnostic): KtUserType? {
val ktUserType = diagnostic.psiElement.getParentOfTypeAndBranch<KtUserType> { referenceExpression } ?: return null
if (ktUserType.qualifier != null) return null
if (ktUserType.getParentOfTypeAndBranch<KtUserType>(true) { qualifier } != null) return null
if (ktUserType.typeArgumentList != null) return null
return ktUserType
}
override fun extractFixData(element: KtUserType, diagnostic: Diagnostic): CreateTypeParameterData? {
val name = element.referencedName ?: return null
val declaration = element.parents.firstOrNull {
it is KtProperty || it is KtNamedFunction || it is KtClass
} as? KtTypeParameterListOwner ?: return null
val containingDescriptor = declaration.resolveToDescriptor()
val fakeTypeParameter = createFakeTypeParameterDescriptor(containingDescriptor, name)
val upperBoundType = getUnsubstitutedTypeConstraintInfo(element)?.let {
it.performSubstitution(it.typeParameter.typeConstructor to TypeProjectionImpl(fakeTypeParameter.defaultType))?.upperBound
}
if (upperBoundType != null && upperBoundType.containsError()) return null
return CreateTypeParameterData(name, declaration, upperBoundType, fakeTypeParameter)
}
override fun createFixes(
originalElementPointer: SmartPsiElementPointer<KtUserType>,
diagnostic: Diagnostic,
quickFixDataFactory: () -> CreateTypeParameterData?
): List<QuickFixWithDelegateFactory> {
val ktUserType = originalElementPointer.element ?: return emptyList()
val name = ktUserType.referencedName ?: return emptyList()
return getPossibleTypeParameterContainers(ktUserType)
.filter { it.typeParameters.all { it.name != name } }
.map {
QuickFixWithDelegateFactory factory@ {
val originalElement = originalElementPointer.element ?: return@factory null
val data = quickFixDataFactory()?.copy(declaration = it) ?: return@factory null
CreateTypeParameterFromUsageFix(originalElement, data)
}
}
}
}
fun createFakeTypeParameterDescriptor(containingDescriptor: DeclarationDescriptor, name: String): TypeParameterDescriptor {
return TypeParameterDescriptorImpl
.createWithDefaultBound(containingDescriptor, Annotations.EMPTY, false, Variance.INVARIANT, Name.identifier(name), -1)
}
fun getPossibleTypeParameterContainers(startFrom: PsiElement): List<KtTypeParameterListOwner> {
val stopAt = startFrom.parents.firstOrNull(::isObjectOrNonInnerClass)?.parent
return (if (stopAt != null) startFrom.parents.takeWhile { it != stopAt } else startFrom.parents)
.filterIsInstance<KtTypeParameterListOwner>()
.filter {
((it is KtClass && !it.isInterface() && it !is KtEnumEntry) ||
it is KtNamedFunction ||
(it is KtProperty && !it.isLocal) ||
it is KtTypeAlias) && it.nameIdentifier != null
}
.toList()
}
@@ -0,0 +1,137 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.ElementDescriptionUtil
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.usageView.UsageViewTypeLocation
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.addTypeParameter
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageFixBase
import org.jetbrains.kotlin.idea.refactoring.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.TypeProjectionImpl
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.isNullableAny
import org.jetbrains.kotlin.utils.SmartList
class CreateTypeParameterFromUsageFix(
originalElement: KtUserType,
private val data: CreateTypeParameterData
) : CreateFromUsageFixBase<KtUserType>(originalElement) {
override fun getText() = "Create type parameter '${data.name}' in " +
ElementDescriptionUtil.getElementDescription(data.declaration, UsageViewTypeLocation.INSTANCE) + " '${data.declaration.name}'"
override fun startInWriteAction() = false
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val declaration = data.declaration
val usages = project.runSynchronouslyWithProgress("Searching ${declaration.name}...", true) {
runReadAction {
ReferencesSearch
.search(declaration)
.mapNotNull {
it.element.getParentOfTypeAndBranch<KtUserType> { referenceExpression } ?:
it.element.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
}
.toSet()
}
} ?: return
runWriteAction {
val psiFactory = KtPsiFactory(project)
val elementsToShorten = SmartList<KtElement>()
val upperBoundType = data.upperBoundType
val upperBoundText = if (upperBoundType != null && !upperBoundType.isNullableAny()) {
IdeDescriptorRenderers.SOURCE_CODE.renderType(upperBoundType)
}
else null
val upperBound = upperBoundText?.let { psiFactory.createType(it) }
val newTypeParameterText = if (upperBound != null) "${data.name} : ${upperBound.text}" else data.name
elementsToShorten += declaration.addTypeParameter(psiFactory.createTypeParameter(newTypeParameterText))!!
val anonymizedTypeParameter = createFakeTypeParameterDescriptor(data.fakeTypeParameter.containingDeclaration, "_")
val anonymizedUpperBoundText = upperBoundType?.let {
TypeSubstitutor
.create(mapOf(data.fakeTypeParameter.typeConstructor to TypeProjectionImpl(anonymizedTypeParameter.defaultType)))
.substitute(upperBoundType, Variance.INVARIANT)
}?.let {
IdeDescriptorRenderers.SOURCE_CODE.renderType(it)
}
val anonymizedUpperBoundAsTypeArg = psiFactory.createTypeArgument(anonymizedUpperBoundText ?: "kotlin.Any?")
val callsToExplicateArguments = SmartList<KtCallElement>()
usages.forEach {
when (it) {
is KtUserType -> {
val typeArgumentList = it.typeArgumentList
elementsToShorten += if (typeArgumentList != null) {
typeArgumentList.addArgument(anonymizedUpperBoundAsTypeArg)
}
else {
it.addAfter(
psiFactory.createTypeArguments("<${anonymizedUpperBoundAsTypeArg.text}>"),
it.referenceExpression!!
) as KtTypeArgumentList
}
}
is KtCallElement -> {
if (it.analyze(BodyResolveMode.PARTIAL).diagnostics.forElement(it.calleeExpression!!).any {
it.factory in Errors.TYPE_INFERENCE_ERRORS
}) {
callsToExplicateArguments += it
}
}
}
}
callsToExplicateArguments.forEach {
val typeArgumentList = it.typeArgumentList
if (typeArgumentList == null) {
InsertExplicitTypeArgumentsIntention.applyTo(it, shortenReferences = false)
val newTypeArgument = it.typeArguments.lastOrNull()
if (anonymizedUpperBoundText != null && newTypeArgument != null && newTypeArgument.text == "kotlin.Any") {
newTypeArgument.replaced(anonymizedUpperBoundAsTypeArg)
}
elementsToShorten += it.typeArgumentList
}
else {
elementsToShorten += typeArgumentList.addArgument(anonymizedUpperBoundAsTypeArg)
}
}
ShortenReferences.DEFAULT.process(elementsToShorten)
}
}
}
@@ -216,3 +216,5 @@ fun KtExpression.mustBeParenthesizedInInitializerPosition(): Boolean {
if (left?.mustBeParenthesizedInInitializerPosition() ?: false) return true
return PsiChildRange(left, operationReference).any { (it is PsiWhiteSpace) && it.textContains('\n') }
}
fun isObjectOrNonInnerClass(e: PsiElement): Boolean = e is KtObjectDeclaration || (e is KtClass && !e.isInner())
@@ -1,6 +1,7 @@
// "Create class 'A'" "false"
// ACTION: Create interface 'A'
// ACTION: Create type alias 'A'
// ACTION: Create type parameter 'A' in class 'Foo'
// ACTION: Create test
// ERROR: Unresolved reference: A
package p
@@ -6,6 +6,7 @@
// ACTION: Create type alias 'A'
// ACTION: Convert to block body
// ACTION: Remove explicit type specification
// ACTION: Create type parameter 'A' in function 'foo'
// ERROR: Unresolved reference: A
package p
@@ -2,6 +2,7 @@
// ACTION: Create class 'NotExistent'
// ACTION: Create interface 'NotExistent'
// ACTION: Create type alias 'NotExistent'
// ACTION: Create type parameter 'NotExistent' in class 'TPB'
// ACTION: Create test
// ERROR: Unresolved reference: NotExistent
class TPB<X : <caret>NotExistent>
@@ -2,6 +2,7 @@
// ACTION: Create class 'NotExistent'
// ACTION: Create interface 'NotExistent'
// ACTION: Create type alias 'NotExistent'
// ACTION: Create type parameter 'NotExistent' in class 'TPB'
// ACTION: Create test
// ERROR: Unresolved reference: NotExistent
class TPB<X> where X : <caret>NotExistent
@@ -2,6 +2,7 @@
// ACTION: Create class 'NotExistent'
// ACTION: Create interface 'NotExistent'
// ACTION: Create type alias 'NotExistent'
// ACTION: Create type parameter 'NotExistent' in class 'TPB'
// ACTION: Create test
// ERROR: Unresolved reference: NotExistent
class TPB<X : <caret>NotExistent>
@@ -2,6 +2,7 @@
// ACTION: Create class 'NotExistent'
// ACTION: Create interface 'NotExistent'
// ACTION: Create type alias 'NotExistent'
// ACTION: Create type parameter 'NotExistent' in class 'TPB'
// ACTION: Create test
// ERROR: Unresolved reference: NotExistent
class TPB<X> where X : <caret>NotExistent
@@ -2,6 +2,7 @@
// ACTION: Create class 'NotExistent'
// ACTION: Create interface 'NotExistent'
// ACTION: Create type alias 'NotExistent'
// ACTION: Create type parameter 'NotExistent' in class 'TPB'
// ACTION: Create test
// ERROR: Unresolved reference: NotExistent
class TPB<X : <caret>NotExistent>
@@ -2,6 +2,7 @@
// ACTION: Create class 'NotExistent'
// ACTION: Create interface 'NotExistent'
// ACTION: Create type alias 'NotExistent'
// ACTION: Create type parameter 'NotExistent' in class 'TPB'
// ACTION: Create test
// ERROR: Unresolved reference: NotExistent
class TPB<X> where X : <caret>NotExistent
@@ -6,6 +6,7 @@
// ACTION: Create type alias 'A'
// ACTION: Convert to block body
// ACTION: Remove explicit type specification
// ACTION: Create type parameter 'A' in function 'foo'
// ERROR: Unresolved reference: A
package p
@@ -0,0 +1,13 @@
// "Create type parameter 'X' in class 'Foo'" "true"
open class Foo(x: <caret>X)
class Bar : Foo(1)
fun test() {
Foo(1)
Foo("2")
object : Foo("2") {
}
}
@@ -0,0 +1,13 @@
// "Create type parameter 'X' in class 'Foo'" "true"
open class Foo<X>(x: X)
class Bar : Foo<Any?>(1)
fun test() {
Foo(1)
Foo("2")
object : Foo<Any?>("2") {
}
}
@@ -0,0 +1,16 @@
// "Create type parameter 'X' in class 'Foo'" "true"
// ERROR: Type mismatch: inferred type is A<Int> but A<Any?> was expected
class A<T>
open class Foo(x: A<<caret>X>)
class Bar : Foo(A())
fun test() {
Foo(A())
Foo(A<Int>())
object : Foo(A<Int>()) {
}
}
@@ -0,0 +1,16 @@
// "Create type parameter 'X' in class 'Foo'" "true"
// ERROR: Type mismatch: inferred type is A<Int> but A<Any?> was expected
class A<T>
open class Foo<X>(x: A<X>)
class Bar : Foo<Any?>(A())
fun test() {
Foo<Any?>(A())
Foo(A<Int>())
object : Foo<Any?>(A<Int>()) {
}
}
@@ -0,0 +1,21 @@
// "Create type parameter 'X' in class 'Foo'" "true"
// ERROR: Unresolved reference: _
// ERROR: Unresolved reference: _
// ERROR: Unresolved reference: _
// ERROR: Type mismatch: inferred type is A<I> but A<List<[ERROR : _]>> was expected
class A<T : List<T>>
interface I : List<I>
open class Foo(x: A<<caret>X>)
class Bar : Foo(A())
fun test() {
Foo(A())
Foo(A<I>())
object : Foo(A<I>()) {
}
}
@@ -0,0 +1,21 @@
// "Create type parameter 'X' in class 'Foo'" "true"
// ERROR: Unresolved reference: _
// ERROR: Unresolved reference: _
// ERROR: Unresolved reference: _
// ERROR: Type mismatch: inferred type is A<I> but A<List<[ERROR : _]>> was expected
class A<T : List<T>>
interface I : List<I>
open class Foo<X : List<X>>(x: A<X>)
class Bar : Foo<List<_>>(A())
fun test() {
Foo<List<_>>(A())
Foo(A<I>())
object : Foo<List<_>>(A<I>()) {
}
}
@@ -0,0 +1,15 @@
// "Create type parameter 'X' in class 'Foo'" "true"
class A<T : List<Int>>
open class Foo(x: A<<caret>X>)
class Bar : Foo(A())
fun test() {
Foo(A())
Foo(A<List<Int>>())
object : Foo(A<List<Int>>()) {
}
}
@@ -0,0 +1,15 @@
// "Create type parameter 'X' in class 'Foo'" "true"
class A<T : List<Int>>
open class Foo<X : List<Int>>(x: A<X>)
class Bar : Foo<List<Int>>(A())
fun test() {
Foo<List<Int>>(A())
Foo(A<List<Int>>())
object : Foo<List<Int>>(A<List<Int>>()) {
}
}
@@ -0,0 +1,9 @@
// "Create type parameter 'X' in function 'foo'" "true"
fun foo(x: <caret>X) {
}
fun test() {
foo(1)
foo("2")
}
@@ -0,0 +1,9 @@
// "Create type parameter 'X' in function 'foo'" "true"
fun <X> foo(x: X) {
}
fun test() {
foo(1)
foo("2")
}
@@ -0,0 +1,11 @@
// "Create type parameter 'X' in function 'foo'" "true"
class A<T>
fun foo(x: A<<caret>X>) {
}
fun test() {
foo(A())
foo(A<Int>())
}
@@ -0,0 +1,11 @@
// "Create type parameter 'X' in function 'foo'" "true"
class A<T>
fun <X> foo(x: A<X>) {
}
fun test() {
foo<Any?>(A())
foo(A<Int>())
}
@@ -0,0 +1,14 @@
// "Create type parameter 'X' in function 'foo'" "true"
// ERROR: Unresolved reference: _
class A<T : List<T>>
interface I : List<I>
fun foo(x: A<<caret>X>) {
}
fun test() {
foo(A())
foo(A<I>())
}
@@ -0,0 +1,14 @@
// "Create type parameter 'X' in function 'foo'" "true"
// ERROR: Unresolved reference: _
class A<T : List<T>>
interface I : List<I>
fun <X : List<X>> foo(x: A<X>) {
}
fun test() {
foo<List<_>>(A())
foo(A<I>())
}
@@ -0,0 +1,11 @@
// "Create type parameter 'X' in function 'foo'" "true"
class A<T : List<Int>>
fun foo(x: A<<caret>X>) {
}
fun test() {
foo(A())
foo(A<List<Int>>())
}
@@ -0,0 +1,11 @@
// "Create type parameter 'X' in function 'foo'" "true"
class A<T : List<Int>>
fun <X : List<Int>> foo(x: A<X>) {
}
fun test() {
foo<List<Int>>(A())
foo(A<List<Int>>())
}
@@ -0,0 +1,13 @@
// "Create type parameter 'X'" "false"
// ACTION: Create annotation 'X'
// ACTION: Create class 'X'
// ACTION: Create enum 'X'
// ACTION: Create interface 'X'
// ACTION: Create type alias 'X'
// ERROR: Unresolved reference: X
class A
fun foo(x: A.<caret>X) {
}
@@ -0,0 +1,12 @@
// "Create type parameter 'X'" "false"
// ACTION: Create class 'X'
// ACTION: Create enum 'X'
// ACTION: Create interface 'X'
// ACTION: Create object 'X'
// ERROR: Unresolved reference: X
class A
fun foo(x: <caret>X.Y) {
}
@@ -0,0 +1,11 @@
// "Create type parameter 'X'" "false"
// ACTION: Create class 'X'
// ACTION: Create interface 'X'
// ACTION: Create type alias 'X'
// ERROR: Unresolved reference: X
class A
fun foo(x: <caret>X<Int>) {
}
@@ -5,6 +5,7 @@
// ACTION: Create interface 'NoSuchType'
// ACTION: Create type alias 'NoSuchType'
// ACTION: Remove explicit lambda parameter types (may break code)
// ACTION: Create type parameter 'NoSuchType' in function 'foo'
// ERROR: Type mismatch: inferred type is ([ERROR : NoSuchType]) -> Int but Int was expected
// ERROR: Unresolved reference: NoSuchType
@@ -3050,6 +3050,81 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class CreateTypeParameter extends AbstractQuickFixTest {
public void testAllFilesPresentInCreateTypeParameter() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createTypeParameter"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("classNoExplication.kt")
public void testClassNoExplication() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/classNoExplication.kt");
doTest(fileName);
}
@TestMetadata("classWithExplication.kt")
public void testClassWithExplication() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/classWithExplication.kt");
doTest(fileName);
}
@TestMetadata("classWithExplicationAndRecursiveUpperBound.kt")
public void testClassWithExplicationAndRecursiveUpperBound() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/classWithExplicationAndRecursiveUpperBound.kt");
doTest(fileName);
}
@TestMetadata("classWithExplicationAndUpperBound.kt")
public void testClassWithExplicationAndUpperBound() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/classWithExplicationAndUpperBound.kt");
doTest(fileName);
}
@TestMetadata("functionNoExplication.kt")
public void testFunctionNoExplication() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/functionNoExplication.kt");
doTest(fileName);
}
@TestMetadata("functionWithExplication.kt")
public void testFunctionWithExplication() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/functionWithExplication.kt");
doTest(fileName);
}
@TestMetadata("functionWithExplicationAndRecursiveUpperBound.kt")
public void testFunctionWithExplicationAndRecursiveUpperBound() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/functionWithExplicationAndRecursiveUpperBound.kt");
doTest(fileName);
}
@TestMetadata("functionWithExplicationAndUpperBound.kt")
public void testFunctionWithExplicationAndUpperBound() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/functionWithExplicationAndUpperBound.kt");
doTest(fileName);
}
@TestMetadata("qualifiedType.kt")
public void testQualifiedType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/qualifiedType.kt");
doTest(fileName);
}
@TestMetadata("typeQualifier.kt")
public void testTypeQualifier() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/typeQualifier.kt");
doTest(fileName);
}
@TestMetadata("withTypeArguments.kt")
public void testWithTypeArguments() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createTypeParameter/withTypeArguments.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createVariable")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)