Introduce Type Parameter

#KT-13155 Fixed
This commit is contained in:
Alexey Sedunov
2016-08-26 20:14:12 +03:00
parent cd717467f0
commit 2f9a911624
23 changed files with 367 additions and 14 deletions
+4
View File
@@ -118,6 +118,10 @@ These artifacts include extensions for the types available in the latter JDKs, s
- [`KT-13216`](https://youtrack.jetbrains.com/issue/KT-13216) Move: Report separate conflicts for each property accessor
- [`KT-13216`](https://youtrack.jetbrains.com/issue/KT-13216) Move: Forbid moving of enum entries
##### New features
- [`KT-13155`](https://youtrack.jetbrains.com/issue/KT-13155) Implement "Introduce Type Parameter" refactoring
## 1.0.4
### Compiler
@@ -32,7 +32,9 @@ import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.takeSnapshot
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.noTypeInfo
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
operator fun <K, V: Any> BindingContext.get(slice: ReadOnlySlice<K, V>, key: K): V? = get(slice, key)
@@ -98,4 +100,16 @@ fun KtExpression.getReferenceTargets(context: BindingContext): Collection<Declar
}
fun KtTypeReference.getAbbreviatedTypeOrType(context: BindingContext) =
context[BindingContext.ABBREVIATED_TYPE, this] ?: context[BindingContext.TYPE, this]
context[BindingContext.ABBREVIATED_TYPE, this] ?: context[BindingContext.TYPE, this]
fun KtTypeElement.getAbbreviatedTypeOrType(context: BindingContext): KotlinType? {
val parent = parent
return when (parent) {
is KtTypeReference -> parent.getAbbreviatedTypeOrType(context)
is KtNullableType -> {
val outerType = parent.getAbbreviatedTypeOrType(context)
if (this is KtNullableType) outerType else outerType?.makeNotNullable()
}
else -> null
}
}
@@ -782,6 +782,7 @@ fun main(args: Array<String>) {
model("refactoring/introduceParameter", pattern = KT_OR_KTS, testMethod = "doIntroduceSimpleParameterTest")
model("refactoring/introduceLambdaParameter", pattern = KT_OR_KTS, testMethod = "doIntroduceLambdaParameterTest")
model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest")
model("refactoring/introduceTypeParameter", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeParameterTest")
model("refactoring/introduceTypeAlias", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeAliasTest")
}
+7 -1
View File
@@ -162,10 +162,16 @@
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunction"/>
</action>
<action id="IntroduceTypeParameter" class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeParameter.IntroduceTypeParameterAction"
text="T_ype Parameter...">
<keyboard-shortcut keymap="$default" first-keystroke="control alt shift Y"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunctionToScope"/>
</action>
<action id="IntroduceTypeAlias" class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.IntroduceTypeAliasAction"
text="Type _Alias...">
<keyboard-shortcut keymap="$default" first-keystroke="control alt shift A"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractFunctionToScope"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceTypeParameter"/>
</action>
<!-- Kotlin Console REPL-->
@@ -53,18 +53,22 @@ object CreateTypeParameterByRefActionFactory : KotlinIntentionActionFactoryWithD
return ktUserType
}
override fun extractFixData(element: KtUserType, diagnostic: Diagnostic): CreateTypeParameterData? {
val name = element.referencedName ?: return null
fun extractFixData(element: KtTypeElement, newName: String): CreateTypeParameterData? {
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 fakeTypeParameter = createFakeTypeParameterDescriptor(containingDescriptor, newName)
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)
return CreateTypeParameterData(newName, declaration, upperBoundType, fakeTypeParameter)
}
override fun extractFixData(element: KtUserType, diagnostic: Diagnostic): CreateTypeParameterData? {
val name = element.referencedName ?: return null
return extractFixData(element, name)
}
override fun createFixes(
@@ -42,16 +42,21 @@ import org.jetbrains.kotlin.types.typeUtil.isNullableAny
import org.jetbrains.kotlin.utils.SmartList
class CreateTypeParameterFromUsageFix(
originalElement: KtUserType,
originalElement: KtTypeElement,
private val data: CreateTypeParameterData
) : CreateFromUsageFixBase<KtUserType>(originalElement) {
) : CreateFromUsageFixBase<KtTypeElement>(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) {
doInvoke()
}
fun doInvoke(): KtTypeParameter? {
val declaration = data.declaration
val project = declaration.project
val usages = project.runSynchronouslyWithProgress("Searching ${declaration.name}...", true) {
runReadAction {
ReferencesSearch
@@ -62,9 +67,9 @@ class CreateTypeParameterFromUsageFix(
}
.toSet()
}
} ?: return
} ?: return null
runWriteAction {
return runWriteAction {
val psiFactory = KtPsiFactory(project)
val elementsToShorten = SmartList<KtElement>()
@@ -76,7 +81,8 @@ class CreateTypeParameterFromUsageFix(
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 newTypeParameter = declaration.addTypeParameter(psiFactory.createTypeParameter(newTypeParameterText))!!
elementsToShorten += newTypeParameter
val anonymizedTypeParameter = createFakeTypeParameterDescriptor(data.fakeTypeParameter.containingDeclaration, "_")
val anonymizedUpperBoundText = upperBoundType?.let {
@@ -132,6 +138,8 @@ class CreateTypeParameterFromUsageFix(
}
ShortenReferences.DEFAULT.process(elementsToShorten)
newTypeParameter
}
}
}
@@ -25,7 +25,7 @@ cannot.refactor.synthesized.function=Cannot refactor synthesized function ''{0}'
error.types.in.generated.function=Cannot generate function with erroneous return type
cannot.introduce.parameter.of.0.type=Cannot introduce parameter of type ''{0}''
0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.declaration={0} has detected {1} code {1,choice,1#fragment|2#fragments} in this file that can be replaced with a call to extracted declaration. Would you like to review and replace {1,choice,1#it|2#them}?
0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.declaration={0} has detected {1} code {1,choice,1#fragment|2#fragments} in this file that can be replaced with a usage of extracted declaration. Would you like to review and replace {1,choice,1#it|2#them}?
error.wrong.caret.position.function.or.constructor.name=The caret should be positioned at the name of the function or constructor to be refactored.
error.cant.refactor.vararg.functions=Can't refactor the function with variable arguments
@@ -176,8 +176,6 @@ fun IntroduceParameterDescriptor.performRefactoring() {
}
}
private fun isObjectOrNonInnerClass(e: PsiElement): Boolean = e is KtObjectDeclaration || (e is KtClass && !e.isInner())
fun selectNewParameterContext(
editor: Editor,
file: KtFile,
@@ -0,0 +1,144 @@
/*
* 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.refactoring.introduce.introduceTypeParameter
import com.intellij.lang.refactoring.RefactoringSupportProvider
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.RefactoringActionHandler
import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.CreateTypeParameterByRefActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.CreateTypeParameterFromUsageFix
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.getPossibleTypeParameterContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.processDuplicates
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetParent
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtTypeElement
import org.jetbrains.kotlin.psi.KtTypeParameterListOwner
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.utils.keysToMap
import java.lang.AssertionError
object KotlinIntroduceTypeParameterHandler : RefactoringActionHandler {
@JvmField
val REFACTORING_NAME = "Introduce Type Parameter"
fun selectElements(editor: Editor, file: KtFile, continuation: (elements: List<PsiElement>, targetParent: PsiElement) -> Unit) {
selectElementsWithTargetParent(
REFACTORING_NAME,
editor,
file,
"Introduce type parameter to declaration",
listOf(CodeInsightUtils.ElementKind.TYPE_ELEMENT),
{ elements, parent -> getPossibleTypeParameterContainers(parent) },
continuation
)
}
fun doInvoke(project: Project, editor: Editor, elements: List<PsiElement>, targetParent: PsiElement) {
val targetOwner = targetParent as KtTypeParameterListOwner
val typeElementToExtract = elements.singleOrNull() as? KtTypeElement
?: return showErrorHint(project, editor, "No type to refactor", REFACTORING_NAME)
val scope = targetOwner.getResolutionScope()
val suggestedNames = KotlinNameSuggester.suggestNamesForTypeParameters(1) {
scope.findClassifier(Name.identifier(it), NoLookupLocation.FROM_IDE) == null
}
val defaultName = suggestedNames.single()
val context = typeElementToExtract.analyze(BodyResolveMode.PARTIAL)
val originalType = typeElementToExtract.getAbbreviatedTypeOrType(context)
val createTypeParameterData =
CreateTypeParameterByRefActionFactory.extractFixData(typeElementToExtract, defaultName)
?.copy(upperBoundType = originalType, declaration = targetOwner)
?: return showErrorHint(project, editor, "Refactoring is not applicable in the current context", REFACTORING_NAME)
val parameterRefElement = KtPsiFactory(project).createType(defaultName).typeElement!!
val duplicateRanges = typeElementToExtract
.toRange()
.match(targetParent, KotlinPsiUnifier.DEFAULT)
.filterNot {
val textRange = it.range.getTextRange()
typeElementToExtract.textRange.intersects(textRange) || targetOwner.typeParameterList?.textRange?.intersects(textRange) ?: false
}
.mapNotNull { it.range.elements.toRange() }
project.executeCommand(REFACTORING_NAME) {
runWriteAction {
typeElementToExtract.replace(parameterRefElement)
processDuplicates(
duplicateRanges.keysToMap {
{
it.elements.singleOrNull()?.replace(parameterRefElement)
Unit
}
},
project,
editor
)
}
val newTypeParameter = CreateTypeParameterFromUsageFix(typeElementToExtract, createTypeParameterData).doInvoke()
?: return@executeCommand
if (!ApplicationManager.getApplication().isUnitTestMode) {
val dataContext = SimpleDataContext.getSimpleContext(CommonDataKeys.PSI_ELEMENT.name, newTypeParameter,
(editor as? EditorEx)?.dataContext)
VariableInplaceRenameHandler().doRename(newTypeParameter, editor, dataContext)
}
}
}
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
if (file !is KtFile) return
selectElements(editor, file) { elements, targetParent -> doInvoke(project, editor, elements, targetParent) }
}
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
throw AssertionError("${KotlinIntroduceTypeAliasHandler.REFACTORING_NAME} can only be invoked from editor")
}
}
class IntroduceTypeParameterAction : AbstractIntroduceAction() {
override fun getRefactoringHandler(provider: RefactoringSupportProvider) = KotlinIntroduceTypeParameterHandler
}
@@ -0,0 +1,9 @@
class A
fun foo(x: List<A>, f: (<selection>A</selection>) -> Int) {
val a: A? = x.firstOrNull()
}
fun test() {
foo(listOf(A())) { 1 }
}
@@ -0,0 +1,9 @@
class A
fun <T : A> foo(x: List<T>, f: (T) -> Int) {
val a: T? = x.firstOrNull()
}
fun test() {
foo(listOf(A())) { 1 }
}
@@ -0,0 +1,9 @@
class A
fun foo(x: List<<selection>(A?) -> List<Int></selection>>) {
}
fun test() {
foo(listOf({ a -> listOf(1) }))
}
@@ -0,0 +1,9 @@
class A
fun <T : (A?) -> List<Int>> foo(x: List<T>) {
}
fun test() {
foo(listOf({ a -> listOf(1) }))
}
@@ -0,0 +1,11 @@
class A
open class X(x: List<A>, f: (<selection>A</selection>) -> Int) {
val a: A? = x.firstOrNull()
}
class Y : X(listOf(A()), { 1 })
fun test() {
X(listOf(A())) { 1 }
}
@@ -0,0 +1,11 @@
class A
open class X<T : A>(x: List<T>, f: (T) -> Int) {
val a: T? = x.firstOrNull()
}
class Y : X<A>(listOf(A()), { 1 })
fun test() {
X(listOf(A())) { 1 }
}
@@ -0,0 +1,11 @@
class A
val List<<selection>A</selection>>.foo: (A) -> Int
get() {
val a: A? = firstOrNull()
return { 0 }
}
fun test() {
val t = listOf(A()).foo
}
@@ -0,0 +1,11 @@
class A
val <T : A> List<T>.foo: (T) -> Int
get() {
val a: T? = firstOrNull()
return { 0 }
}
fun test() {
val t = listOf(A()).foo
}
@@ -0,0 +1,9 @@
class A
fun foo(x: List<<selection>A?</selection>>) {
}
fun test() {
foo(listOf(A()))
}
@@ -0,0 +1,9 @@
class A
fun <T : A?> foo(x: List<T>) {
}
fun test() {
foo(listOf(A()))
}
@@ -0,0 +1,9 @@
class A
fun foo(x: (<selection>List<A?></selection>) -> Int) {
}
fun test() {
foo { 1 }
}
@@ -0,0 +1,9 @@
class A
fun <T : List<A?>> foo(x: (T) -> Int) {
}
fun test() {
foo<List<A?>> { 1 }
}
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.*
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.INTRODUCE_PROPERTY
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeParameter.KotlinIntroduceTypeParameterHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
@@ -314,6 +315,18 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase(
}
}
protected fun doIntroduceTypeParameterTest(path: String) {
doTest(path) { file ->
file as KtFile
val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:")
val editor = fixture.editor
KotlinIntroduceTypeParameterHandler.selectElements(editor, file) { elements, previousSibling ->
KotlinIntroduceTypeParameterHandler.doInvoke(project, editor, elements, explicitPreviousSibling ?: previousSibling)
}
}
}
protected fun doIntroduceTypeAliasTest(path: String) {
doTest(path) { file ->
file as KtFile
@@ -3992,6 +3992,51 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
}
}
@TestMetadata("idea/testData/refactoring/introduceTypeParameter")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class IntroduceTypeParameter extends AbstractExtractionTest {
public void testAllFilesPresentInIntroduceTypeParameter() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceTypeParameter"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
}
@TestMetadata("duplicates.kt")
public void testDuplicates() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeParameter/duplicates.kt");
doIntroduceTypeParameterTest(fileName);
}
@TestMetadata("functionType.kt")
public void testFunctionType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeParameter/functionType.kt");
doIntroduceTypeParameterTest(fileName);
}
@TestMetadata("inClass.kt")
public void testInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeParameter/inClass.kt");
doIntroduceTypeParameterTest(fileName);
}
@TestMetadata("inProperty.kt")
public void testInProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeParameter/inProperty.kt");
doIntroduceTypeParameterTest(fileName);
}
@TestMetadata("nullableType.kt")
public void testNullableType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeParameter/nullableType.kt");
doIntroduceTypeParameterTest(fileName);
}
@TestMetadata("userType.kt")
public void testUserType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceTypeParameter/userType.kt");
doIntroduceTypeParameterTest(fileName);
}
}
@TestMetadata("idea/testData/refactoring/introduceTypeAlias")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)