From 1a4b9cb228393ae3809479e571381a1072373a91 Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Tue, 14 Mar 2017 14:09:28 +0300 Subject: [PATCH] Show warning for `mod` from built-ins since API=1.1 --- .../kotlin/resolve/OperatorModifierChecker.kt | 2 +- .../calls/checkers/OperatorCallChecker.kt | 50 ++++++++++++++++--- .../kotlin/resolve/deprecationUtil.kt | 24 +++------ .../noWarningForModFromBuiltinsWhenApi1_0.kt | 46 +++++++++++++++++ .../noWarningForModFromBuiltinsWhenApi1_0.txt | 47 +++++++++++++++++ .../checkers/DiagnosticsTestGenerated.java | 6 +++ 6 files changed, 151 insertions(+), 24 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.kt create mode 100644 compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt index 171b9270add..420a2f375be 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OperatorModifierChecker.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. + * Copyright 2010-2017 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. 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 bdb1f32bffb..fd5645fa9f7 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 @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. + * Copyright 2010-2017 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. @@ -17,10 +17,14 @@ package org.jetbrains.kotlin.resolve.calls.checkers import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.name.isSubpackageOf import org.jetbrains.kotlin.psi.Call import org.jetbrains.kotlin.psi.KtArrayAccessExpression import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry @@ -30,6 +34,7 @@ import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isConventionCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.expressions.OperatorConventions @@ -62,11 +67,7 @@ class OperatorCallChecker : CallChecker { val isConventionOperator = element is KtOperationReferenceExpression && element.isConventionOperator() if (isConventionOperator) { - val shouldUseOperatorRem = context.languageVersionSettings.supportsFeature(LanguageFeature.OperatorRem) - if (functionDescriptor.name in OperatorConventions.REM_TO_MOD_OPERATION_NAMES.values && shouldUseOperatorRem) { - val newNameConvention = OperatorConventions.REM_TO_MOD_OPERATION_NAMES.inverse()[functionDescriptor.name] - context.trace.report(Errors.DEPRECATED_BINARY_MOD_AS_REM.on(reportOn, functionDescriptor, newNameConvention!!.asString())) - } + checkModConvention(functionDescriptor, context.languageVersionSettings, context.trace, reportOn) } if (isConventionOperator || element is KtArrayAccessExpression) { @@ -99,3 +100,40 @@ class OperatorCallChecker : CallChecker { } } } + +fun FunctionDescriptor.isOperatorMod(): Boolean { + return this.isOperator && name in OperatorConventions.REM_TO_MOD_OPERATION_NAMES.values +} + +// This is an alternate function to KotlinBuiltIns.isBuiltIn +// It is safer as it produces stable results independent of target platform (Java/JS/Native) +fun FunctionDescriptor.hasSubpackageOfKotlin(): Boolean { + val descriptorFqName = fqNameOrNull() ?: return false + return descriptorFqName.isSubpackageOf(KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME) +} + +fun shouldWarnAboutDeprecatedModFromBuiltIns(languageVersionSettings: LanguageVersionSettings): Boolean { + return languageVersionSettings.supportsFeature(LanguageFeature.OperatorRem) && languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_1 +} + +private fun checkModConvention(descriptor: FunctionDescriptor, languageVersionSettings: LanguageVersionSettings, + diagnosticHolder: DiagnosticSink, modifier: PsiElement) { + if (!descriptor.isOperatorMod()) return + + if (descriptor.hasSubpackageOfKotlin()) { + if (shouldWarnAboutDeprecatedModFromBuiltIns(languageVersionSettings)) { + addWarningAboutDeprecatedMod(descriptor, diagnosticHolder, modifier) + } + } + else { + if (languageVersionSettings.supportsFeature(LanguageFeature.OperatorRem)) { + addWarningAboutDeprecatedMod(descriptor, diagnosticHolder, modifier) + } + } +} + +private fun addWarningAboutDeprecatedMod(descriptor: FunctionDescriptor, diagnosticHolder: DiagnosticSink, reportOn: PsiElement) { + val newNameConvention = OperatorConventions.REM_TO_MOD_OPERATION_NAMES.inverse()[descriptor.name] + diagnosticHolder.report(Errors.DEPRECATED_BINARY_MOD_AS_REM.on(reportOn, descriptor, newNameConvention!!.asString())) +} + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecationUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecationUtil.kt index 46ffeb50e11..952907185a3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecationUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/deprecationUtil.kt @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.resolve import com.intellij.psi.PsiElement import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.config.ApiVersion -import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor @@ -29,15 +28,15 @@ import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.isSubpackageOf import org.jetbrains.kotlin.resolve.DeprecationLevelValue.* import org.jetbrains.kotlin.resolve.annotations.argumentValue -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull +import org.jetbrains.kotlin.resolve.calls.checkers.hasSubpackageOfKotlin +import org.jetbrains.kotlin.resolve.calls.checkers.isOperatorMod +import org.jetbrains.kotlin.resolve.calls.checkers.shouldWarnAboutDeprecatedModFromBuiltIns import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor import org.jetbrains.kotlin.serialization.deserialization.descriptors.SinceKotlinInfo -import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.SmartList private val JAVA_DEPRECATED = FqName("java.lang.Deprecated") @@ -186,8 +185,10 @@ private fun DeclarationDescriptor.getOwnDeprecations(languageVersionSettings: La } // The problem is that declaration `mod` in built-ins has @Deprecated annotation but actually it was deprecated only in version 1.1 - if (!languageVersionSettings.supportsFeature(LanguageFeature.OperatorRem) && isOperatorModFromBuiltIns(this)) { - return emptyList() + if (this is FunctionDescriptor && this.isOperatorMod() && this.hasSubpackageOfKotlin()) { + if (!shouldWarnAboutDeprecatedModFromBuiltIns(languageVersionSettings)) { + return emptyList() + } } val result = SmartList() @@ -281,14 +282,3 @@ fun DeclarationDescriptor.isHiddenInResolution(languageVersionSettings: Language return isDeprecatedHidden(languageVersionSettings) } - -private fun isOperatorModFromBuiltIns(descriptor: DeclarationDescriptor): Boolean { - if (descriptor is FunctionDescriptor && descriptor.isOperator) { - val isOperatorMod = descriptor.name == OperatorNameConventions.MOD - - val descriptorFqName = descriptor.fqNameOrNull() ?: return false - return isOperatorMod && descriptorFqName.isSubpackageOf(KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME) - } - - return false -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.kt b/compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.kt new file mode 100644 index 00000000000..1542cf69f83 --- /dev/null +++ b/compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.kt @@ -0,0 +1,46 @@ +// !API_VERSION: 1.0 +// !DIAGNOSTICS: -UNUSED_PARAMETER, -UNUSED_VARIABLE, -EXTENSION_SHADOWED_BY_MEMBER + +class Foo { + operator fun rem(x: Int): Foo = Foo() +} + +class Bar { + operator fun remAssign(x: Int) {} +} + +class Baz { + companion object { + operator fun rem(x: Int) {} + operator fun Int.rem(x: Int) {} + } +} + +operator fun Baz.rem(x: Int) {} + +fun local() { + operator fun Int.rem(x: Int) {} + operator fun String.remAssign(x: Int) {} +} + +class WithMod { + operator fun mod(other: WithMod) = this + + fun test() { + val a = this % this + var b = this.mod(this) + b %= this + } +} + +fun noOverflow() { + (-1).mod(5) +} + +fun builtIns(b: Byte, s: Short) { + var a = 1 % 2 + a %= 3 + 1.mod(2) + b % s + 1.0 % 2.0 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.txt b/compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.txt new file mode 100644 index 00000000000..8e9c74eeea6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.txt @@ -0,0 +1,47 @@ +package + +public fun builtIns(/*0*/ b: kotlin.Byte, /*1*/ s: kotlin.Short): kotlin.Unit +public fun local(): kotlin.Unit +public fun noOverflow(): kotlin.Unit +public operator fun Baz.rem(/*0*/ x: kotlin.Int): kotlin.Unit + +public final class Bar { + public constructor Bar() + 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 remAssign(/*0*/ x: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Baz { + public constructor Baz() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public companion object Companion { + private constructor Companion() + 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 rem(/*0*/ x: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public final operator fun kotlin.Int.rem(/*0*/ x: kotlin.Int): kotlin.Unit + } +} + +public final class Foo { + public constructor Foo() + 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 rem(/*0*/ x: kotlin.Int): Foo + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class WithMod { + public constructor WithMod() + 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*/ other: WithMod): WithMod + public final fun test(): 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 4f038bf3b0f..7774442c3b4 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -13795,6 +13795,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("noWarningForModFromBuiltinsWhenApi1_0.kt") + public void testNoWarningForModFromBuiltinsWhenApi1_0() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/operatorRem/noWarningForModFromBuiltinsWhenApi1_0.kt"); + doTest(fileName); + } + @TestMetadata("numberRemConversions.kt") public void testNumberRemConversions() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/operatorRem/numberRemConversions.kt");