Show warning for mod from built-ins since API=1.1

This commit is contained in:
Mikhail Zarechenskiy
2017-03-14 14:09:28 +03:00
parent e4188f889e
commit 1a4b9cb228
6 changed files with 151 additions and 24 deletions
@@ -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.
@@ -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()))
}
@@ -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<Deprecation>()
@@ -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
}
@@ -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 {
<!DEPRECATED_BINARY_MOD!>operator<!> fun mod(other: WithMod) = this
fun test() {
val a = this <!DEPRECATED_BINARY_MOD_AS_REM!>%<!> this
var b = this.mod(this)
b <!DEPRECATED_BINARY_MOD_AS_REM!>%=<!> 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
}
@@ -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
}
@@ -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");