diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 62ea7c7bc11..b0bddd25e4e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -747,7 +747,9 @@ public interface Errors { DiagnosticFactory2 FORBIDDEN_IDENTITY_EQUALS = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 DEPRECATED_BINARY_MOD = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 FORBIDDEN_BINARY_MOD = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 DEPRECATED_BINARY_MOD_AS_REM = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 FORBIDDEN_BINARY_MOD_AS_REM = DiagnosticFactory2.create(ERROR); DiagnosticFactory0 NO_GET_METHOD = DiagnosticFactory0.create(ERROR, ARRAY_ACCESS); DiagnosticFactory0 NO_SET_METHOD = DiagnosticFactory0.create(ERROR, ARRAY_ACCESS); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 580f8ba8580..959d766e540 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -396,7 +396,9 @@ public class DefaultErrorMessages { MAP.put(FORBIDDEN_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} is forbidden", RENDER_TYPE, RENDER_TYPE); MAP.put(DEPRECATED_BINARY_MOD, "Deprecated convention for ''{0}''. Use ''{1}''", NAME, STRING); + MAP.put(FORBIDDEN_BINARY_MOD, "Convention for ''{0}'' is forbidden. Use ''{1}''", NAME, STRING); MAP.put(DEPRECATED_BINARY_MOD_AS_REM, "''%'' is resolved to deprecated ''{0}'' operator. Replace with ''.{0}'' or add operator ''{1}''", NAME, STRING); + MAP.put(FORBIDDEN_BINARY_MOD_AS_REM, "''%'' is resolved to forbidden ''{0}'' operator. Replace with ''.{0}'' or add operator ''{1}''", NAME, STRING); MAP.put(NO_GET_METHOD, "No get method providing array access"); MAP.put(NO_SET_METHOD, "No set method providing array access"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt index 531db90bd83..be9f638df24 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.resolve import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.DeclarationDescriptor @@ -50,10 +51,18 @@ object OperatorModifierChecker { checkSupportsFeature(LanguageFeature.OperatorProvideDelegate, languageVersionSettings, diagnosticHolder, modifier) } - if (functionDescriptor.name in REM_TO_MOD_OPERATION_NAMES.values - && languageVersionSettings.supportsFeature(LanguageFeature.OperatorRem)) { + if (functionDescriptor.name in REM_TO_MOD_OPERATION_NAMES.values && + languageVersionSettings.supportsFeature(LanguageFeature.OperatorRem) + ) { + val diagnosticFactory = if (!KotlinBuiltIns.isUnderKotlinPackage(descriptor) && + languageVersionSettings.supportsFeature(LanguageFeature.ProhibitOperatorMod) + ) + Errors.FORBIDDEN_BINARY_MOD + else + Errors.DEPRECATED_BINARY_MOD + val newNameConvention = REM_TO_MOD_OPERATION_NAMES.inverse()[functionDescriptor.name] - diagnosticHolder.report(Errors.DEPRECATED_BINARY_MOD.on(modifier, functionDescriptor, newNameConvention!!.asString())) + diagnosticHolder.report(diagnosticFactory.on(modifier, functionDescriptor, newNameConvention!!.asString())) } return diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/OperatorCallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/OperatorCallChecker.kt index 97ab01b4b19..ec1eeb6f4c9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/OperatorCallChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/OperatorCallChecker.kt @@ -117,16 +117,26 @@ private fun checkModConvention( if (KotlinBuiltIns.isUnderKotlinPackage(descriptor)) { if (shouldWarnAboutDeprecatedModFromBuiltIns(languageVersionSettings)) { - addWarningAboutDeprecatedMod(descriptor, diagnosticHolder, modifier) + warnAboutDeprecatedOrForbiddenMod(descriptor, diagnosticHolder, modifier, languageVersionSettings) } } else { if (languageVersionSettings.supportsFeature(LanguageFeature.OperatorRem)) { - addWarningAboutDeprecatedMod(descriptor, diagnosticHolder, modifier) + warnAboutDeprecatedOrForbiddenMod(descriptor, diagnosticHolder, modifier, languageVersionSettings) } } } -private fun addWarningAboutDeprecatedMod(descriptor: FunctionDescriptor, diagnosticHolder: DiagnosticSink, reportOn: PsiElement) { +private fun warnAboutDeprecatedOrForbiddenMod( + descriptor: FunctionDescriptor, + diagnosticHolder: DiagnosticSink, + reportOn: PsiElement, + languageVersionSettings: LanguageVersionSettings +) { + val diagnosticFactory = if (languageVersionSettings.supportsFeature(LanguageFeature.ProhibitOperatorMod)) + Errors.FORBIDDEN_BINARY_MOD_AS_REM + else + Errors.DEPRECATED_BINARY_MOD_AS_REM + val newNameConvention = OperatorConventions.REM_TO_MOD_OPERATION_NAMES.inverse()[descriptor.name] - diagnosticHolder.report(Errors.DEPRECATED_BINARY_MOD_AS_REM.on(reportOn, descriptor, newNameConvention!!.asString())) + diagnosticHolder.report(diagnosticFactory.on(reportOn, descriptor, newNameConvention!!.asString())) } diff --git a/compiler/testData/diagnostics/tests/operatorRem/forbiddenModOperatorConvention.kt b/compiler/testData/diagnostics/tests/operatorRem/forbiddenModOperatorConvention.kt new file mode 100644 index 00000000000..47f10cc11f8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/operatorRem/forbiddenModOperatorConvention.kt @@ -0,0 +1,32 @@ +// !LANGUAGE: +ProhibitOperatorMod +// !DIAGNOSTICS: -UNUSED_PARAMETER + +object ModAndRem { + operator fun mod(x: Int) {} + operator fun rem(x: Int) {} + + operator fun modAssign(x: Int) {} + operator fun remAssign(x: Int) {} +} + +object JustMod { + operator fun mod(x: Int) {} + operator fun modAssign(x: Int) {} +} + +fun foo() { + ModAndRem % 1 + ModAndRem.mod(1) + ModAndRem.rem(1) + + JustMod % 1 + JustMod.mod(1) + + val r = ModAndRem + r %= 1 + r.remAssign(1) + + val m = JustMod + m %= 1 + m.modAssign(1) +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/operatorRem/forbiddenModOperatorConvention.txt b/compiler/testData/diagnostics/tests/operatorRem/forbiddenModOperatorConvention.txt new file mode 100644 index 00000000000..524b7991003 --- /dev/null +++ b/compiler/testData/diagnostics/tests/operatorRem/forbiddenModOperatorConvention.txt @@ -0,0 +1,23 @@ +package + +public fun foo(): kotlin.Unit + +public object JustMod { + private constructor JustMod() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final operator fun mod(/*0*/ x: kotlin.Int): kotlin.Unit + public final operator fun modAssign(/*0*/ x: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public object ModAndRem { + private constructor ModAndRem() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final operator fun mod(/*0*/ x: kotlin.Int): kotlin.Unit + public final operator fun modAssign(/*0*/ x: kotlin.Int): kotlin.Unit + public final operator fun rem(/*0*/ x: kotlin.Int): kotlin.Unit + public final operator fun remAssign(/*0*/ x: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 4d1289b9fd6..ba6e2fc7667 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -13813,6 +13813,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { runTest("compiler/testData/diagnostics/tests/operatorRem/doNotResolveToInapplicableRem.kt"); } + @TestMetadata("forbiddenModOperatorConvention.kt") + public void testForbiddenModOperatorConvention() throws Exception { + runTest("compiler/testData/diagnostics/tests/operatorRem/forbiddenModOperatorConvention.kt"); + } + @TestMetadata("modWithRemAssign.kt") public void testModWithRemAssign() throws Exception { runTest("compiler/testData/diagnostics/tests/operatorRem/modWithRemAssign.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index 3be6cf94dbb..eb7e3f3219f 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -13813,6 +13813,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing runTest("compiler/testData/diagnostics/tests/operatorRem/doNotResolveToInapplicableRem.kt"); } + @TestMetadata("forbiddenModOperatorConvention.kt") + public void testForbiddenModOperatorConvention() throws Exception { + runTest("compiler/testData/diagnostics/tests/operatorRem/forbiddenModOperatorConvention.kt"); + } + @TestMetadata("modWithRemAssign.kt") public void testModWithRemAssign() throws Exception { runTest("compiler/testData/diagnostics/tests/operatorRem/modWithRemAssign.kt"); diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 74a3bd20352..40f8f93f258 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -77,6 +77,7 @@ enum class LanguageFeature( AllowContractsForCustomFunctions(KOTLIN_1_3, kind = UNSTABLE_FEATURE), ProhibitLocalAnnotations(KOTLIN_1_3, kind = BUG_FIX), ProhibitSmartcastsOnLocalDelegatedProperty(KOTLIN_1_3, kind = BUG_FIX), + ProhibitOperatorMod(KOTLIN_1_3, kind = BUG_FIX), StrictJavaNullabilityAssertions(sinceVersion = null, defaultState = State.DISABLED), ProperIeee754Comparisons(sinceVersion = null, defaultState = State.DISABLED), diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 7d62b450eb2..1a29249ccf2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -148,6 +148,8 @@ class QuickFixRegistrar : QuickFixContributor { TYPE_CANT_BE_USED_FOR_CONST_VAL.registerFactory(removeModifierFactory) DEPRECATED_BINARY_MOD.registerFactory(removeModifierFactory) DEPRECATED_BINARY_MOD.registerFactory(RenameModToRemFix.Factory) + FORBIDDEN_BINARY_MOD.registerFactory(removeModifierFactory) + FORBIDDEN_BINARY_MOD.registerFactory(RenameModToRemFix.Factory) UNRESOLVED_REFERENCE.registerFactory(ImportFix) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/RenameModToRemFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/RenameModToRemFix.kt index 00d69eedce5..7def3f0cc43 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/RenameModToRemFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/RenameModToRemFix.kt @@ -41,7 +41,7 @@ class RenameModToRemFix(element: KtNamedFunction, val newName: Name) : KotlinQui companion object Factory : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { - if (diagnostic.factory != Errors.DEPRECATED_BINARY_MOD) return null + if (diagnostic.factory != Errors.DEPRECATED_BINARY_MOD && diagnostic.factory != Errors.FORBIDDEN_BINARY_MOD) return null val operatorMod = diagnostic.psiElement.getNonStrictParentOfType() ?: return null val newName = REM_TO_MOD_OPERATION_NAMES.inverse()[operatorMod.nameAsName] ?: return null diff --git a/idea/testData/quickfix/modifiers/removeModifierFromForbiddenOperatorMod.kt b/idea/testData/quickfix/modifiers/removeModifierFromForbiddenOperatorMod.kt new file mode 100644 index 00000000000..390f34ddd64 --- /dev/null +++ b/idea/testData/quickfix/modifiers/removeModifierFromForbiddenOperatorMod.kt @@ -0,0 +1,6 @@ +// "Remove 'operator' modifier" "true" +// COMPILER_ARGUMENTS: -XXLanguage:+ProhibitOperatorMod + +object A { + operator fun mod(x: Int) {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/modifiers/removeModifierFromForbiddenOperatorMod.kt.after b/idea/testData/quickfix/modifiers/removeModifierFromForbiddenOperatorMod.kt.after new file mode 100644 index 00000000000..67c6e556e7e --- /dev/null +++ b/idea/testData/quickfix/modifiers/removeModifierFromForbiddenOperatorMod.kt.after @@ -0,0 +1,6 @@ +// "Remove 'operator' modifier" "true" +// COMPILER_ARGUMENTS: -XXLanguage:+ProhibitOperatorMod + +object A { + fun mod(x: Int) {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/renameToRem/forbiddenModAsMember.kt b/idea/testData/quickfix/renameToRem/forbiddenModAsMember.kt new file mode 100644 index 00000000000..e75d5f37c7a --- /dev/null +++ b/idea/testData/quickfix/renameToRem/forbiddenModAsMember.kt @@ -0,0 +1,14 @@ +// "Rename to 'rem'" "true" +// DISABLE-ERRORS +// COMPILER_ARGUMENTS: -XXLanguage:+ProhibitOperatorMod + +object Rem { + operator fun mod(x: Int) {} + operator fun modAssign(x: Int) {} +} + +fun test() { + Rem % 1 + Rem.mod(1) + Rem.modAssign(1) +} \ No newline at end of file diff --git a/idea/testData/quickfix/renameToRem/forbiddenModAsMember.kt.after b/idea/testData/quickfix/renameToRem/forbiddenModAsMember.kt.after new file mode 100644 index 00000000000..bcc74ccf870 --- /dev/null +++ b/idea/testData/quickfix/renameToRem/forbiddenModAsMember.kt.after @@ -0,0 +1,14 @@ +// "Rename to 'rem'" "true" +// DISABLE-ERRORS +// COMPILER_ARGUMENTS: -XXLanguage:+ProhibitOperatorMod + +object Rem { + operator fun rem(x: Int) {} + operator fun modAssign(x: Int) {} +} + +fun test() { + Rem % 1 + Rem.rem(1) + Rem.modAssign(1) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 5a7686c9f8a..3e19c8ea68d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -7699,6 +7699,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/modifiers/removeInnerForClassInTrait.kt"); } + @TestMetadata("removeModifierFromForbiddenOperatorMod.kt") + public void testRemoveModifierFromForbiddenOperatorMod() throws Exception { + runTest("idea/testData/quickfix/modifiers/removeModifierFromForbiddenOperatorMod.kt"); + } + @TestMetadata("removeModifierFromOperatorMod.kt") public void testRemoveModifierFromOperatorMod() throws Exception { runTest("idea/testData/quickfix/modifiers/removeModifierFromOperatorMod.kt"); @@ -9287,6 +9292,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/renameToRem"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("forbiddenModAsMember.kt") + public void testForbiddenModAsMember() throws Exception { + runTest("idea/testData/quickfix/renameToRem/forbiddenModAsMember.kt"); + } + @TestMetadata("modAsExtension.kt") public void testModAsExtension() throws Exception { runTest("idea/testData/quickfix/renameToRem/modAsExtension.kt");