diff --git a/idea/resources/inspectionDescriptions/AddVarianceModifier.html b/idea/resources/inspectionDescriptions/AddVarianceModifier.html
new file mode 100644
index 00000000000..3c420bcf670
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/AddVarianceModifier.html
@@ -0,0 +1,6 @@
+
+
+This inspection reports type parameters that can have 'in' or 'out' variance added.
+Usage of 'in' and 'out' variances leads to more precise type inference in Kotlin.
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 39e07135858..5049ee29676 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1423,6 +1423,13 @@
level="WEAK WARNING"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/AddVarianceModifierInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/AddVarianceModifierInspection.kt
new file mode 100644
index 00000000000..12c000cdc4f
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/AddVarianceModifierInspection.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.inspections
+
+import com.intellij.codeInspection.*
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElementVisitor
+import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
+import org.jetbrains.kotlin.diagnostics.DiagnosticSink
+import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtClassOrObject
+import org.jetbrains.kotlin.psi.KtTypeParameter
+import org.jetbrains.kotlin.psi.KtVisitorVoid
+import org.jetbrains.kotlin.psi.addRemoveModifier.addModifier
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.ManualVariance
+import org.jetbrains.kotlin.resolve.VarianceCheckerCore
+import org.jetbrains.kotlin.types.Variance
+
+class AddVarianceModifierInspection : AbstractKotlinInspection() {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+
+ private fun variancePossible(
+ klass: KtClassOrObject,
+ parameterDescriptor: TypeParameterDescriptor,
+ variance: Variance,
+ context: BindingContext
+ ) = VarianceCheckerCore(
+ context,
+ DiagnosticSink.DO_NOTHING,
+ ManualVariance(parameterDescriptor, variance)
+ ).checkClassOrObject(klass)
+
+ override fun visitClassOrObject(klass: KtClassOrObject) {
+ val context = klass.analyzeFully()
+ for (typeParameter in klass.typeParameters) {
+ if (typeParameter.variance != Variance.INVARIANT) continue
+ val parameterDescriptor =
+ context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, typeParameter) as? TypeParameterDescriptor ?: continue
+ val variances = listOf(Variance.IN_VARIANCE, Variance.OUT_VARIANCE).filter {
+ variancePossible(klass, parameterDescriptor, it, context)
+ }
+ if (variances.isNotEmpty()) {
+ val suggested = variances.joinToString(" or ") { "'$it'" }
+ val fixes = variances.map { AddVarianceFix(it) }
+ holder.registerProblem(
+ typeParameter,
+ "Type parameter can have $suggested variance",
+ ProblemHighlightType.WEAK_WARNING,
+ *fixes.toTypedArray()
+ )
+ }
+ }
+ }
+ }
+ }
+
+ class AddVarianceFix(val variance: Variance) : LocalQuickFix {
+ override fun getName() = "Add '$variance' variance"
+
+ override fun getFamilyName() = "Add variance"
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val typeParameter = descriptor.psiElement as? KtTypeParameter
+ ?: throw AssertionError("Add variance fix is used on ${descriptor.psiElement.text}")
+ addModifier(typeParameter, if (variance == Variance.IN_VARIANCE) KtTokens.IN_KEYWORD else KtTokens.OUT_KEYWORD)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/idea/testData/inspections/addVarianceModifier/inspectionData/expected.xml b/idea/testData/inspections/addVarianceModifier/inspectionData/expected.xml
new file mode 100644
index 00000000000..07cddc55bc5
--- /dev/null
+++ b/idea/testData/inspections/addVarianceModifier/inspectionData/expected.xml
@@ -0,0 +1,42 @@
+
+
+ test.kt
+ 1
+ light_idea_test_case
+
+ Type parameter can have 'in' or 'out' variance
+ Type parameter can have 'out' variance
+
+
+ test.kt
+ 5
+ light_idea_test_case
+
+ Type parameter can have 'in' or 'out' variance
+ Type parameter can have 'in' variance
+
+
+ test.kt
+ 17
+ light_idea_test_case
+
+ Type parameter can have 'in' or 'out' variance
+ Type parameter can have 'out' variance
+
+
+ test.kt
+ 21
+ light_idea_test_case
+
+ Type parameter can have 'in' or 'out' variance
+ Type parameter can have 'in' variance
+
+
+ test.kt
+ 24
+ light_idea_test_case
+
+ Type parameter can have 'in' or 'out' variance
+ Type parameter can have 'in' or 'out' variance
+
+
\ No newline at end of file
diff --git a/idea/testData/inspections/addVarianceModifier/inspectionData/inspections.test b/idea/testData/inspections/addVarianceModifier/inspectionData/inspections.test
new file mode 100644
index 00000000000..6fe0bce31c7
--- /dev/null
+++ b/idea/testData/inspections/addVarianceModifier/inspectionData/inspections.test
@@ -0,0 +1 @@
+// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.AddVarianceModifierInspection
\ No newline at end of file
diff --git a/idea/testData/inspections/addVarianceModifier/test.kt b/idea/testData/inspections/addVarianceModifier/test.kt
new file mode 100644
index 00000000000..7c34367a60b
--- /dev/null
+++ b/idea/testData/inspections/addVarianceModifier/test.kt
@@ -0,0 +1,24 @@
+interface EffectivelyOut {
+ fun foo(): T
+ val bar: T
+}
+interface EffectivelyIn {
+ fun foo(arg: T)
+}
+interface Invariant1 {
+ var bar: T
+}
+interface Invariant2 {
+ fun T.foo(): T
+}
+interface Invariant3> {
+ fun T.foo()
+}
+abstract class AbstractOut {
+ abstract val foo: T
+ private var bar = foo
+}
+abstract class AbstractIn(private val foo: T) {
+ fun bar(arg: T) = foo == arg
+}
+interface Empty
\ No newline at end of file
diff --git a/idea/testData/quickfix/addVarianceModifier/.inspection b/idea/testData/quickfix/addVarianceModifier/.inspection
new file mode 100644
index 00000000000..34b24d475b9
--- /dev/null
+++ b/idea/testData/quickfix/addVarianceModifier/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.AddVarianceModifierInspection
\ No newline at end of file
diff --git a/idea/testData/quickfix/addVarianceModifier/Empty.kt b/idea/testData/quickfix/addVarianceModifier/Empty.kt
new file mode 100644
index 00000000000..0987b32a730
--- /dev/null
+++ b/idea/testData/quickfix/addVarianceModifier/Empty.kt
@@ -0,0 +1,2 @@
+// "Add 'out' variance" "true"
+interface Empty<T>
\ No newline at end of file
diff --git a/idea/testData/quickfix/addVarianceModifier/abstractIn.kt b/idea/testData/quickfix/addVarianceModifier/abstractIn.kt
new file mode 100644
index 00000000000..e4d20856030
--- /dev/null
+++ b/idea/testData/quickfix/addVarianceModifier/abstractIn.kt
@@ -0,0 +1,4 @@
+// "Add 'in' variance" "true"
+abstract class AbstractIn<T>(private val foo: T) {
+ fun bar(arg: T) = foo == arg
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/addVarianceModifier/abstractIn.kt.after b/idea/testData/quickfix/addVarianceModifier/abstractIn.kt.after
new file mode 100644
index 00000000000..8b2ce6e9751
--- /dev/null
+++ b/idea/testData/quickfix/addVarianceModifier/abstractIn.kt.after
@@ -0,0 +1,4 @@
+// "Add 'in' variance" "true"
+abstract class AbstractIn(private val foo: T) {
+ fun bar(arg: T) = foo == arg
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/addVarianceModifier/abstractOut.kt b/idea/testData/quickfix/addVarianceModifier/abstractOut.kt
new file mode 100644
index 00000000000..3676829921a
--- /dev/null
+++ b/idea/testData/quickfix/addVarianceModifier/abstractOut.kt
@@ -0,0 +1,5 @@
+// "Add 'out' variance" "true"
+abstract class AbstractOut<T> {
+ abstract val foo: T
+ private var bar = foo
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/addVarianceModifier/abstractOut.kt.after b/idea/testData/quickfix/addVarianceModifier/abstractOut.kt.after
new file mode 100644
index 00000000000..6352b61c8e7
--- /dev/null
+++ b/idea/testData/quickfix/addVarianceModifier/abstractOut.kt.after
@@ -0,0 +1,5 @@
+// "Add 'out' variance" "true"
+abstract class AbstractOut {
+ abstract val foo: T
+ private var bar = foo
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/addVarianceModifier/empty.kt.after b/idea/testData/quickfix/addVarianceModifier/empty.kt.after
new file mode 100644
index 00000000000..face154ad24
--- /dev/null
+++ b/idea/testData/quickfix/addVarianceModifier/empty.kt.after
@@ -0,0 +1,2 @@
+// "Add 'out' variance" "true"
+interface Empty
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
index 51049ace3df..7bb03f0a16d 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
@@ -102,6 +102,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Inspections extends AbstractInspectionTest {
+ @TestMetadata("addVarianceModifier/inspectionData/inspections.test")
+ public void testAddVarianceModifier_inspectionData_Inspections_test() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/addVarianceModifier/inspectionData/inspections.test");
+ doTest(fileName);
+ }
+
public void testAllFilesPresentInInspections() throws Exception {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/inspections"), Pattern.compile("^(inspections\\.test)$"));
}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
index b4384fed0a7..cb32252fc9a 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
@@ -509,6 +509,33 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
+ @TestMetadata("idea/testData/quickfix/addVarianceModifier")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class AddVarianceModifier extends AbstractQuickFixTest {
+ @TestMetadata("abstractIn.kt")
+ public void testAbstractIn() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addVarianceModifier/abstractIn.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("abstractOut.kt")
+ public void testAbstractOut() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addVarianceModifier/abstractOut.kt");
+ doTest(fileName);
+ }
+
+ public void testAllFilesPresentInAddVarianceModifier() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addVarianceModifier"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
+ }
+
+ @TestMetadata("empty.kt")
+ public void testEmpty() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addVarianceModifier/empty.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/quickfix/asyncUnsupported")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -7368,8 +7395,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
@TestMetadata("letClassImplementInterfaceNullable.kt")
public void testLetClassImplementInterfaceNullable() throws Exception {
- String fileName =
- KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/letClassImplementInterfaceNullable.kt");
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/letClassImplementInterfaceNullable.kt");
doTest(fileName);
}