diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt index 10b77285390..538f57cc947 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt @@ -23,13 +23,24 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassifierDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.JetBundle +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtPostfixExpression import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.typeUtil.makeNotNullable +import org.jetbrains.kotlin.util.OperatorChecks +import org.jetbrains.kotlin.util.OperatorNameConventions public abstract class ExclExclCallFix : IntentionAction { override fun getFamilyName(): String = getText() @@ -99,3 +110,37 @@ public class AddExclExclCallFix(val psiElement: PsiElement) : ExclExclCallFix() = AddExclExclCallFix(diagnostic.getPsiElement()) } } + +object MissingIteratorExclExclFixFactory : JetSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val element = diagnostic.psiElement + if (element !is KtExpression) return null + + val analyze = element.analyze(BodyResolveMode.PARTIAL) + val type = analyze.getType(element) + if (type == null || !TypeUtils.isNullableType(type)) return null + + val descriptor = type.constructor.declarationDescriptor + + fun hasIteratorFunction(descriptor: ClassifierDescriptor?) : Boolean { + if (descriptor !is ClassDescriptor) return false + + val memberScope = descriptor.unsubstitutedMemberScope + val functions = memberScope.getFunctions(OperatorNameConventions.ITERATOR, NoLookupLocation.FROM_IDE) + + return functions.any { it.isOperator() && OperatorChecks.canBeOperator(it) } + } + + when (descriptor) { + is TypeParameterDescriptor -> { + if (descriptor.upperBounds.none { hasIteratorFunction(it.constructor.declarationDescriptor) }) return null + } + is ClassifierDescriptor -> { + if (!hasIteratorFunction(descriptor)) return null + } + else -> return null + } + + return AddExclExclCallFix(element) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 7a3d4941f66..4c75222cff9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -291,6 +291,7 @@ public class QuickFixRegistrar : QuickFixContributor { NEXT_MISSING.registerFactory(CreateNextFunctionActionFactory) NEXT_NONE_APPLICABLE.registerFactory(CreateNextFunctionActionFactory) ITERATOR_MISSING.registerFactory(CreateIteratorFunctionActionFactory) + ITERATOR_MISSING.registerFactory(MissingIteratorExclExclFixFactory) COMPONENT_FUNCTION_MISSING.registerFactory(CreateComponentFunctionActionFactory) DELEGATE_SPECIAL_FUNCTION_MISSING.registerFactory(CreatePropertyDelegateAccessorsActionFactory) diff --git a/idea/testData/quickfix/expressions/fixNullableIterableGenericWithExclExcl.kt b/idea/testData/quickfix/expressions/fixNullableIterableGenericWithExclExcl.kt new file mode 100644 index 00000000000..f2fdadec041 --- /dev/null +++ b/idea/testData/quickfix/expressions/fixNullableIterableGenericWithExclExcl.kt @@ -0,0 +1,4 @@ +// "Add non-null asserted (!!) call" "true" +fun ?> foo(c: T) { + for (i in c) { } +} diff --git a/idea/testData/quickfix/expressions/fixNullableIterableGenericWithExclExcl.kt.after b/idea/testData/quickfix/expressions/fixNullableIterableGenericWithExclExcl.kt.after new file mode 100644 index 00000000000..055b350994d --- /dev/null +++ b/idea/testData/quickfix/expressions/fixNullableIterableGenericWithExclExcl.kt.after @@ -0,0 +1,4 @@ +// "Add non-null asserted (!!) call" "true" +fun ?> foo(c: T) { + for (i in c!!) { } +} diff --git a/idea/testData/quickfix/expressions/fixNullableIterableWithExclExcl.kt b/idea/testData/quickfix/expressions/fixNullableIterableWithExclExcl.kt new file mode 100644 index 00000000000..81300ec1baf --- /dev/null +++ b/idea/testData/quickfix/expressions/fixNullableIterableWithExclExcl.kt @@ -0,0 +1,5 @@ +// "Add non-null asserted (!!) call" "true" +fun foo() { + val test : Collection? = null!! + for (i in test) { } +} diff --git a/idea/testData/quickfix/expressions/fixNullableIterableWithExclExcl.kt.after b/idea/testData/quickfix/expressions/fixNullableIterableWithExclExcl.kt.after new file mode 100644 index 00000000000..8f04def8148 --- /dev/null +++ b/idea/testData/quickfix/expressions/fixNullableIterableWithExclExcl.kt.after @@ -0,0 +1,5 @@ +// "Add non-null asserted (!!) call" "true" +fun foo() { + val test : Collection? = null!! + for (i in test!!) { } +} diff --git a/idea/testData/quickfix/expressions/fixNullableWithExclExclAbsentWithBadIterator.kt b/idea/testData/quickfix/expressions/fixNullableWithExclExclAbsentWithBadIterator.kt new file mode 100644 index 00000000000..0c01e962396 --- /dev/null +++ b/idea/testData/quickfix/expressions/fixNullableWithExclExclAbsentWithBadIterator.kt @@ -0,0 +1,12 @@ +// "Add non-null asserted (!!) call" "false" +// ACTION: Replace with a 'forEach' function call +// ERROR: For-loop range must have an iterator() method + +class Some { + fun iterator(): Iterator = null!! +} + +fun foo() { + val test: Some? = Some() + for (i in test) { } +} diff --git a/idea/testData/quickfix/expressions/fixNullableWithIteratorWithExclExcl.kt b/idea/testData/quickfix/expressions/fixNullableWithIteratorWithExclExcl.kt new file mode 100644 index 00000000000..217cce1350a --- /dev/null +++ b/idea/testData/quickfix/expressions/fixNullableWithIteratorWithExclExcl.kt @@ -0,0 +1,9 @@ +// "Add non-null asserted (!!) call" "true" +class Some { + operator fun iterator(): Iterator = null!! +} + +fun foo() { + val test: Some? = Some() + for (i in test) { } +} diff --git a/idea/testData/quickfix/expressions/fixNullableWithIteratorWithExclExcl.kt.after b/idea/testData/quickfix/expressions/fixNullableWithIteratorWithExclExcl.kt.after new file mode 100644 index 00000000000..68e9b442fa3 --- /dev/null +++ b/idea/testData/quickfix/expressions/fixNullableWithIteratorWithExclExcl.kt.after @@ -0,0 +1,9 @@ +// "Add non-null asserted (!!) call" "true" +class Some { + operator fun iterator(): Iterator = null!! +} + +fun foo() { + val test: Some? = Some() + for (i in test!!) { } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java index b65f77d77e2..f964b8c01f2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java @@ -109,10 +109,14 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase { catch (FileComparisonFailure e) { throw e; } + catch (AssertionError e) { + throw e; + } catch (Throwable e) { e.printStackTrace(); fail(testName); - } finally { + } + finally { ConfigLibraryUtil.unconfigureLibrariesByDirective(getModule(), fileText); } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index f34266cf877..6e22f35cd55 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -4070,6 +4070,30 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/expressions"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); } + @TestMetadata("fixNullableIterableGenericWithExclExcl.kt") + public void testFixNullableIterableGenericWithExclExcl() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/expressions/fixNullableIterableGenericWithExclExcl.kt"); + doTest(fileName); + } + + @TestMetadata("fixNullableIterableWithExclExcl.kt") + public void testFixNullableIterableWithExclExcl() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/expressions/fixNullableIterableWithExclExcl.kt"); + doTest(fileName); + } + + @TestMetadata("fixNullableWithExclExclAbsentWithBadIterator.kt") + public void testFixNullableWithExclExclAbsentWithBadIterator() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/expressions/fixNullableWithExclExclAbsentWithBadIterator.kt"); + doTest(fileName); + } + + @TestMetadata("fixNullableWithIteratorWithExclExcl.kt") + public void testFixNullableWithIteratorWithExclExcl() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/expressions/fixNullableWithIteratorWithExclExcl.kt"); + doTest(fileName); + } + @TestMetadata("removeUselessCast.kt") public void testRemoveUselessCast() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/expressions/removeUselessCast.kt");