diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/OperatorModifierInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/OperatorModifierInspection.kt index 97b5285796e..1923f609a86 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/OperatorModifierInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/OperatorModifierInspection.kt @@ -24,19 +24,20 @@ import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.project.Project import com.intellij.psi.PsiElementVisitor import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters2 -import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.quickfix.AddModifierFix import org.jetbrains.kotlin.idea.quickfix.CleanupFix import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.resolve.OverrideResolver import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.types.expressions.OperatorConventions @@ -48,12 +49,13 @@ public class OperatorModifierInspection : AbstractKotlinInspection() { if (nameIdentifier != null && function.isMemberOrExtension() && function.isOperator() && - !function.hasModifier(JetTokens.OPERATOR_KEYWORD)) { + !function.isAnnotatedAsOperator()) { holder.registerProblem(nameIdentifier, "Function defines an operator but isn't annotated as such", AddModifierLocalQuickFix()) } } + } } @@ -86,15 +88,25 @@ public class OperatorModifierInspection : AbstractKotlinInspection() { return false } + private fun JetNamedFunction.isAnnotatedAsOperator(): Boolean { + if (hasModifier(JetTokens.OPERATOR_KEYWORD)) return true + val descriptor = descriptor as? CallableMemberDescriptor ?: return false + return OverrideResolver.getOverriddenDeclarations(descriptor).any { + (it as? FunctionDescriptor)?.isOperator == true + } + } + private fun JetNamedFunction.isMemberOrExtension(): Boolean = receiverTypeReference != null || getStrictParentOfType() != null private fun JetNamedFunction.isIntReturnType(): Boolean { - return KotlinBuiltIns.isInt(analyze(BodyResolveMode.PARTIAL).getType(this) ?: return false) + val returnType = (descriptor as? FunctionDescriptor)?.returnType ?: return false + return KotlinBuiltIns.isInt(returnType) } private fun JetNamedFunction.isBooleanReturnType(): Boolean { - return KotlinBuiltIns.isBoolean(analyze(BodyResolveMode.PARTIAL).getType(this) ?: return false) + val returnType = (descriptor as? FunctionDescriptor)?.returnType ?: return false + return KotlinBuiltIns.isBoolean(returnType) } } diff --git a/idea/testData/quickfix/migration/operatorModifier/containsBool.kt b/idea/testData/quickfix/migration/operatorModifier/containsBool.kt new file mode 100644 index 00000000000..7fec9e0ca74 --- /dev/null +++ b/idea/testData/quickfix/migration/operatorModifier/containsBool.kt @@ -0,0 +1,4 @@ +// "Add 'operator' modifier" "true" +class A { + fun contains(other: A): Boolean = true +} diff --git a/idea/testData/quickfix/migration/operatorModifier/containsBool.kt.after b/idea/testData/quickfix/migration/operatorModifier/containsBool.kt.after new file mode 100644 index 00000000000..323afcd19aa --- /dev/null +++ b/idea/testData/quickfix/migration/operatorModifier/containsBool.kt.after @@ -0,0 +1,4 @@ +// "Add 'operator' modifier" "true" +class A { + operator fun contains(other: A): Boolean = true +} diff --git a/idea/testData/quickfix/migration/operatorModifier/overridden.kt b/idea/testData/quickfix/migration/operatorModifier/overridden.kt new file mode 100644 index 00000000000..5f8b905defb --- /dev/null +++ b/idea/testData/quickfix/migration/operatorModifier/overridden.kt @@ -0,0 +1,10 @@ +// "Add 'operator' modifier" "false" +// ACTION: Convert to block body +// ACTION: Specify return type explicitly +open class A { + open operator fun plus(a: A) = A() +} + +class B : A() { + override fun plus(a: A) = A() +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 96062450999..19267394f7c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -4195,6 +4195,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/operatorModifier"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); } + @TestMetadata("containsBool.kt") + public void testContainsBool() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/operatorModifier/containsBool.kt"); + doTest(fileName); + } + @TestMetadata("containsInt.kt") public void testContainsInt() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/operatorModifier/containsInt.kt"); @@ -4207,6 +4213,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("overridden.kt") + public void testOverridden() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/operatorModifier/overridden.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/operatorModifier/simple.kt");