Initial implementation of KT-7688 Intention action to iterate over iterable value
#KT-7688 Fixed
This commit is contained in:
+2
-2
@@ -392,9 +392,9 @@ class SmartCompletion(
|
||||
}
|
||||
|
||||
val scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, expressionWithType)
|
||||
val iterableDetector = IterableTypesDetector(project, moduleDescriptor, scope, loopVarType)
|
||||
val iterableDetector = IterableTypesDetector(project, moduleDescriptor, scope)
|
||||
|
||||
return buildResultByTypeFilter(expressionWithType, receiver, Tail.RPARENTH) { iterableDetector.isIterable(it) }
|
||||
return buildResultByTypeFilter(expressionWithType, receiver, Tail.RPARENTH) { iterableDetector.isIterable(it, loopVarType) }
|
||||
}
|
||||
|
||||
private fun buildForInOperatorArgument(expressionWithType: JetExpression, receiver: JetExpression?): Result? {
|
||||
|
||||
@@ -34,12 +34,11 @@ import java.util.HashMap
|
||||
public class IterableTypesDetector(
|
||||
private val project: Project,
|
||||
private val moduleDescriptor: ModuleDescriptor,
|
||||
private val scope: JetScope,
|
||||
private val loopVarType: JetType? = null
|
||||
private val scope: JetScope
|
||||
) {
|
||||
|
||||
private val container = createContainerForMacros(project, moduleDescriptor)
|
||||
private val cache = HashMap<FuzzyType, Boolean>()
|
||||
private val cache = HashMap<FuzzyType, FuzzyType?>()
|
||||
private val iteratorName = Name.identifier("iterator")
|
||||
|
||||
private val typesWithExtensionIterator: Collection<JetType> = scope.getFunctions(iteratorName)
|
||||
@@ -47,13 +46,24 @@ public class IterableTypesDetector(
|
||||
.filterNotNull()
|
||||
.map { it.getType() }
|
||||
|
||||
public fun isIterable(type: FuzzyType): Boolean {
|
||||
return cache.getOrPut(type, { isIterableNoCache(type) })
|
||||
public fun isIterable(type: FuzzyType, loopVarType: JetType? = null): Boolean {
|
||||
val elementType = elementType(type) ?: return false
|
||||
return loopVarType == null || elementType.checkIsSubtypeOf(loopVarType) != null
|
||||
}
|
||||
|
||||
private fun isIterableNoCache(type: FuzzyType): Boolean {
|
||||
public fun isIterable(type: JetType, loopVarType: JetType? = null): Boolean
|
||||
= isIterable(FuzzyType(type, emptyList()), loopVarType)
|
||||
|
||||
public fun elementType(type: FuzzyType): FuzzyType? {
|
||||
return cache.getOrPut(type, { elementTypeNoCache(type) })
|
||||
}
|
||||
|
||||
public fun elementType(type: JetType): FuzzyType?
|
||||
= elementType(FuzzyType(type, emptyList()))
|
||||
|
||||
private fun elementTypeNoCache(type: FuzzyType): FuzzyType? {
|
||||
// optimization
|
||||
if (!canBeIterable(type)) return false
|
||||
if (!canBeIterable(type)) return null
|
||||
|
||||
val expression = JetPsiFactory(project).createExpression("fake")
|
||||
val expressionReceiver = ExpressionReceiver(expression, type.type)
|
||||
@@ -61,8 +71,7 @@ public class IterableTypesDetector(
|
||||
val context = ExpressionTypingContext.newContext(expressionTypingComponents.getAdditionalCheckerProvider(),
|
||||
BindingTraceContext(), scope, DataFlowInfo.EMPTY, TypeUtils.NO_EXPECTED_TYPE)
|
||||
val elementType = expressionTypingComponents.getForLoopConventionsChecker().checkIterableConvention(expressionReceiver, context)
|
||||
if (elementType == null) return false
|
||||
return loopVarType == null || FuzzyType(elementType, type.freeParameters).checkIsSubtypeOf(loopVarType) != null
|
||||
return elementType?.let { FuzzyType(it, type.freeParameters) }
|
||||
}
|
||||
|
||||
private fun canBeIterable(type: FuzzyType): Boolean {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
for (i in <spot>listOf(1, 2, 3)</spot>) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<spot>listOf(1, 2, 3)</spot>
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
Uses for-loop to iterate over sequence of elements.
|
||||
</body>
|
||||
</html>
|
||||
@@ -933,6 +933,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.IterateExpressionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
|
||||
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
|
||||
groupName="Kotlin"
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil
|
||||
|
||||
//TODO: move it somewhere else and reuse
|
||||
public abstract class ChooseValueExpression<T : Any>(
|
||||
lookupItems: List<T>,
|
||||
protected val defaultItem: T,
|
||||
@@ -62,3 +63,12 @@ public abstract class ChooseValueExpression<T : Any>(
|
||||
|
||||
override fun getAdvertisingText() = advertisementText
|
||||
}
|
||||
|
||||
public class ChooseStringExpression(
|
||||
suggestions: List<String>,
|
||||
default: String = suggestions[0],
|
||||
advertisementText: String? = null
|
||||
) : ChooseValueExpression<String>(suggestions, default, advertisementText) {
|
||||
override fun getLookupString(element: String) = element
|
||||
override fun getResult(element: String) = element
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.intentions
|
||||
|
||||
import com.intellij.codeInsight.intention.HighPriorityAction
|
||||
import com.intellij.codeInsight.template.TemplateBuilderImpl
|
||||
import com.intellij.codeInsight.template.impl.ConstantNode
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.IterableTypesDetector
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.EmptyValidator
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
|
||||
public class IterateExpressionIntention : JetSelfTargetingIntention<JetExpression>(javaClass(), "Iterate over collection"), HighPriorityAction {
|
||||
override fun isApplicableTo(element: JetExpression, caretOffset: Int): Boolean {
|
||||
if (element.getParent() !is JetBlockExpression) return false
|
||||
val range = element.getTextRange()
|
||||
if (caretOffset != range.getStartOffset() && caretOffset != range.getEndOffset()) return false
|
||||
val data = data(element) ?: return false
|
||||
setText("Iterate over '${IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(data.collectionType)}'")
|
||||
return true
|
||||
}
|
||||
|
||||
private data class Data(val collectionType: JetType, val elementType: JetType)
|
||||
|
||||
private fun data(expression: JetExpression): Data? {
|
||||
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
|
||||
val type = bindingContext.getType(expression) ?: return null
|
||||
val moduleDescriptor = expression.getResolutionFacade().findModuleDescriptor(expression)
|
||||
val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, expression]
|
||||
val elementType = IterableTypesDetector(expression.getProject(), moduleDescriptor, scope).elementType(type)?.type ?: return null
|
||||
return Data(type, elementType)
|
||||
}
|
||||
|
||||
override fun applyTo(element: JetExpression, editor: Editor) {
|
||||
//TODO: multi-declaration (when?)
|
||||
|
||||
val elementType = data(element)!!.elementType
|
||||
//TODO: base on expression too
|
||||
//TODO: name validation
|
||||
val names = JetNameSuggester.suggestNames(elementType, EmptyValidator, "e")
|
||||
|
||||
var forExpression = JetPsiFactory(element).createExpressionByPattern("for($0 in $1) {\nx\n}", names[0], element) as JetForExpression
|
||||
forExpression = element.replaced(forExpression)
|
||||
|
||||
PsiDocumentManager.getInstance(forExpression.getProject()).doPostponedOperationsAndUnblockDocument(editor.getDocument())
|
||||
|
||||
val bodyPlaceholder = (forExpression.getBody() as JetBlockExpression).getStatements().single()
|
||||
|
||||
val templateBuilder = TemplateBuilderImpl(forExpression)
|
||||
templateBuilder.replaceElement(forExpression.getLoopParameter()!!, ChooseStringExpression(names.asList()))
|
||||
templateBuilder.replaceElement(bodyPlaceholder, ConstantNode(""), false)
|
||||
templateBuilder.setEndVariableAfter(bodyPlaceholder)
|
||||
|
||||
templateBuilder.run(editor, true)
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -26,8 +26,8 @@ import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import org.apache.commons.lang.StringEscapeUtils.escapeJava
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.intentions.ChooseStringExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingRangeIntention
|
||||
import org.jetbrains.kotlin.idea.intentions.ChooseValueExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfNotNullExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfNullExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.introduceValueForCondition
|
||||
@@ -68,13 +68,7 @@ public class DoubleBangToIfThenIntention : JetSelfTargetingRangeIntention<JetPos
|
||||
val nullPtrExceptionText = "NullPointerException(\"$message\")"
|
||||
val kotlinNullPtrExceptionText = "KotlinNullPointerException()"
|
||||
|
||||
val exceptionLookupExpression =
|
||||
object: ChooseValueExpression<String>(listOf(nullPtrExceptionText, kotlinNullPtrExceptionText),
|
||||
nullPtrExceptionText) {
|
||||
override fun getLookupString(element: String) = element
|
||||
override fun getResult(element: String) = element
|
||||
}
|
||||
|
||||
val exceptionLookupExpression = ChooseStringExpression(listOf(nullPtrExceptionText, kotlinNullPtrExceptionText))
|
||||
val project = element.getProject()
|
||||
val builder = TemplateBuilderImpl(thrownExpression)
|
||||
builder.replaceElement(thrownExpression, exceptionLookupExpression);
|
||||
|
||||
@@ -71,7 +71,7 @@ public abstract class BaseJetVariableMacro extends Macro {
|
||||
return null;
|
||||
}
|
||||
|
||||
IterableTypesDetector iterableTypesDetector = new IterableTypesDetector(project, analysisResult.getModuleDescriptor(), scope, null);
|
||||
IterableTypesDetector iterableTypesDetector = new IterableTypesDetector(project, analysisResult.getModuleDescriptor(), scope);
|
||||
|
||||
DataFlowInfo dataFlowInfo = getDataFlowInfo(bindingContext, contextExpression);
|
||||
|
||||
|
||||
+1
-5
@@ -18,14 +18,10 @@ package org.jetbrains.kotlin.idea.liveTemplates.macro;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor;
|
||||
import org.jetbrains.kotlin.idea.JetBundle;
|
||||
import org.jetbrains.kotlin.idea.util.FuzzyType;
|
||||
import org.jetbrains.kotlin.idea.core.IterableTypesDetector;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class JetIterableVariableMacro extends BaseJetVariableMacro {
|
||||
|
||||
@Override
|
||||
@@ -45,6 +41,6 @@ public class JetIterableVariableMacro extends BaseJetVariableMacro {
|
||||
@NotNull IterableTypesDetector iterableTypesDetector
|
||||
) {
|
||||
//TODO: smart-casts
|
||||
return iterableTypesDetector.isIterable(new FuzzyType(variableDescriptor.getType(), Collections.<TypeParameterDescriptor>emptyList()));
|
||||
return iterableTypesDetector.isIterable(variableDescriptor.getType(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.intentions.IterateExpressionIntention
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
f()<caret>
|
||||
}
|
||||
|
||||
fun f(): List<Int> = emptyList()
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
for (i in f()) {
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
|
||||
fun f(): List<Int> = emptyList()
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(list: List<String>) {
|
||||
list<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(list: List<String>) {
|
||||
for (s in list) {
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
@@ -5221,6 +5221,27 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/iterateExpression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class IterateExpression extends AbstractIntentionTest {
|
||||
public void testAllFilesPresentInIterateExpression() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/iterateExpression"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("functionCall.kt")
|
||||
public void testFunctionCall() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/iterateExpression/functionCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/iterateExpression/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/moveLambdaInsideParentheses")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user