[FIR] Add RedundantCallOfConversionMethod checker

This commit is contained in:
vldf
2020-07-29 11:50:34 +03:00
committed by Mikhail Glukhikh
parent 39d4b79324
commit cfc09048c6
44 changed files with 378 additions and 2 deletions
@@ -0,0 +1,3 @@
interface IC {
fun toString(x: String): String = "IC$x"
}
@@ -0,0 +1,7 @@
FILE: StringTemplate.kt
public abstract interface IC : R|kotlin/Any| {
public open fun toString(x: R|kotlin/String|): R|kotlin/String| {
^toString <strcat>(String(IC), R|<local>/x|.R|kotlin/Any.toString|())
}
}
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
fun Boolean.toInt() = if (this) 1 else 0
fun test(x: Int, y: Int): Int {
return (x > y).toInt()
}
@@ -0,0 +1,15 @@
FILE: booleanToInt.kt
public final fun R|kotlin/Boolean|.toInt(): R|kotlin/Int| {
^toInt when () {
this@R|/toInt| -> {
Int(1)
}
else -> {
Int(0)
}
}
}
public final fun test(x: R|kotlin/Int|, y: R|kotlin/Int|): R|kotlin/Int| {
^test CMP(>, R|<local>/x|.R|kotlin/Int.compareTo|(R|<local>/y|)).R|/toInt|()
}
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val foo = Byte.MAX_VALUE.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toByte()<!>
@@ -0,0 +1,3 @@
FILE: byte.kt
public final val foo: R|kotlin/Byte| = Q|kotlin/Byte|.R|kotlin/Byte.Companion.MAX_VALUE|.R|kotlin/Byte.toByte|()
public get(): R|kotlin/Byte|
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val foo = 'a'.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toChar()<!>
@@ -0,0 +1,3 @@
FILE: char.kt
public final val foo: R|kotlin/Char| = Char(a).R|kotlin/Char.toChar|()
public get(): R|kotlin/Char|
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val foo = 1.1.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toDouble()<!>
@@ -0,0 +1,3 @@
FILE: double.kt
public final val foo: R|kotlin/Double| = Double(1.1).R|kotlin/Double.toDouble|()
public get(): R|kotlin/Double|
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val foo = 1.1f.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toFloat()<!>
@@ -0,0 +1,3 @@
FILE: float.kt
public final val foo: R|kotlin/Float| = Float(1.1).R|kotlin/Float.toFloat|()
public get(): R|kotlin/Float|
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val foo = 1.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toInt()<!>
@@ -0,0 +1,3 @@
FILE: int.kt
public final val foo: R|kotlin/Int| = Int(1).R|kotlin/Int.toInt|()
public get(): R|kotlin/Int|
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val foo = Long.MAX_VALUE.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toLong()<!>
@@ -0,0 +1,3 @@
FILE: long.kt
public final val foo: R|kotlin/Long| = Q|kotlin/Long|.R|kotlin/Long.Companion.MAX_VALUE|.R|kotlin/Long.toLong|()
public get(): R|kotlin/Long|
@@ -0,0 +1,5 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun foo(s: String?) {
val t: String = s.toString()
}
@@ -0,0 +1,4 @@
FILE: nullable.kt
public final fun foo(s: R|kotlin/String?|): R|kotlin/Unit| {
lval t: R|kotlin/String| = R|<local>/s|.R|kotlin/toString|()
}
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
data class Foo(val name: String)
fun nullable2(foo: Foo?) {
val s: String = foo?.name.toString()
}
@@ -0,0 +1,17 @@
FILE: nullable2.kt
public final data class Foo : R|kotlin/Any| {
public constructor(name: R|kotlin/String|): R|Foo| {
super<R|kotlin/Any|>()
}
public final val name: R|kotlin/String| = R|<local>/name|
public get(): R|kotlin/String|
public final fun component1(): R|kotlin/String|
public final fun copy(name: R|kotlin/String| = this@R|/Foo|.R|/Foo.name|): R|Foo|
}
public final fun nullable2(foo: R|Foo?|): R|kotlin/Unit| {
lval s: R|kotlin/String| = R|<local>/foo|?.{ $subj$.R|/Foo.name| }.R|kotlin/toString|()
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test() {
val foo: String? = null
foo?.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toString()<!>
}
@@ -0,0 +1,5 @@
FILE: safeString.kt
public final fun test(): R|kotlin/Unit| {
lval foo: R|kotlin/String?| = Null(null)
R|<local>/foo|?.{ $subj$.R|kotlin/Any.toString|() }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
data class Foo(val name: String)
fun test(foo: Foo?) {
val s: String? = foo?.name?.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toString()<!>
}
@@ -0,0 +1,17 @@
FILE: safeString2.kt
public final data class Foo : R|kotlin/Any| {
public constructor(name: R|kotlin/String|): R|Foo| {
super<R|kotlin/Any|>()
}
public final val name: R|kotlin/String| = R|<local>/name|
public get(): R|kotlin/String|
public final fun component1(): R|kotlin/String|
public final fun copy(name: R|kotlin/String| = this@R|/Foo|.R|/Foo.name|): R|Foo|
}
public final fun test(foo: R|Foo?|): R|kotlin/Unit| {
lval s: R|kotlin/String?| = R|<local>/foo|?.{ $subj$.R|/Foo.name| }?.{ $subj$.R|kotlin/Any.toString|() }
}
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val foo = Short.MAX_VALUE.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toShort()<!>
@@ -0,0 +1,3 @@
FILE: short.kt
public final val foo: R|kotlin/Short| = Q|kotlin/Short|.R|kotlin/Short.Companion.MAX_VALUE|.R|kotlin/Short.toShort|()
public get(): R|kotlin/Short|
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val foo = "".<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toString()<!>
@@ -0,0 +1,3 @@
FILE: string.kt
public final val foo: R|kotlin/String| = String().R|kotlin/Any.toString|()
public get(): R|kotlin/String|
@@ -0,0 +1,3 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
val foo = 1.toLong()
@@ -0,0 +1,3 @@
FILE: toOtherType.kt
public final val foo: R|kotlin/Long| = Int(1).R|kotlin/Int.toLong|()
public get(): R|kotlin/Long|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(i: UByte) {
val foo = i.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toUByte()<!>
}
@@ -0,0 +1,4 @@
FILE: uByte.kt
public final fun test(i: R|kotlin/UByte|): R|kotlin/Unit| {
lval foo: R|kotlin/UByte| = R|<local>/i|.R|kotlin/UByte.toUByte|()
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(i: UInt) {
val foo = i.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toUInt()<!>
}
@@ -0,0 +1,4 @@
FILE: uInt.kt
public final fun test(i: R|kotlin/UInt|): R|kotlin/Unit| {
lval foo: R|kotlin/UInt| = R|<local>/i|.R|kotlin/UInt.toUInt|()
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(i: ULong) {
val foo = i.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toULong()<!>
}
@@ -0,0 +1,4 @@
FILE: uLong.kt
public final fun test(i: R|kotlin/ULong|): R|kotlin/Unit| {
lval foo: R|kotlin/ULong| = R|<local>/i|.R|kotlin/ULong.toULong|()
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(i: UShort) {
val foo = i.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toUShort()<!>
}
@@ -0,0 +1,4 @@
FILE: uShort.kt
public final fun test(i: R|kotlin/UShort|): R|kotlin/Unit| {
lval foo: R|kotlin/UShort| = R|<local>/i|.R|kotlin/UShort.toUShort|()
}
@@ -0,0 +1,3 @@
// WITH_RUNTIME
val foo = ""
val bar = foo.<!REDUNDANT_CALL_OF_CONVERSION_METHOD!>toString()<!>
@@ -0,0 +1,5 @@
FILE: variable.kt
public final val foo: R|kotlin/String| = String()
public get(): R|kotlin/String|
public final val bar: R|kotlin/String| = R|/foo|.R|kotlin/Any.toString|()
public get(): R|kotlin/String|
@@ -166,6 +166,119 @@ public class ExtendedFirDiagnosticsTestGenerated extends AbstractExtendedFirDiag
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class RedundantCallOfConversionMethod extends AbstractExtendedFirDiagnosticsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInRedundantCallOfConversionMethod() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("booleanToInt.kt")
public void testBooleanToInt() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/booleanToInt.kt");
}
@TestMetadata("byte.kt")
public void testByte() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/byte.kt");
}
@TestMetadata("char.kt")
public void testChar() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/char.kt");
}
@TestMetadata("double.kt")
public void testDouble() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/double.kt");
}
@TestMetadata("float.kt")
public void testFloat() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/float.kt");
}
@TestMetadata("int.kt")
public void testInt() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/int.kt");
}
@TestMetadata("long.kt")
public void testLong() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/long.kt");
}
@TestMetadata("nullable.kt")
public void testNullable() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/nullable.kt");
}
@TestMetadata("nullable2.kt")
public void testNullable2() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/nullable2.kt");
}
@TestMetadata("safeString.kt")
public void testSafeString() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/safeString.kt");
}
@TestMetadata("safeString2.kt")
public void testSafeString2() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/safeString2.kt");
}
@TestMetadata("short.kt")
public void testShort() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/short.kt");
}
@TestMetadata("string.kt")
public void testString() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/string.kt");
}
@TestMetadata("StringTemplate.kt")
public void testStringTemplate() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/StringTemplate.kt");
}
@TestMetadata("toOtherType.kt")
public void testToOtherType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/toOtherType.kt");
}
@TestMetadata("uByte.kt")
public void testUByte() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/uByte.kt");
}
@TestMetadata("uInt.kt")
public void testUInt() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/uInt.kt");
}
@TestMetadata("uLong.kt")
public void testULong() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/uLong.kt");
}
@TestMetadata("uShort.kt")
public void testUShort() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/uShort.kt");
}
@TestMetadata("variable.kt")
public void testVariable() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/variable.kt");
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.fir.analysis.checkers.extended.RedundantCallOfConversionMethod
object CommonExpressionCheckers : ExpressionCheckers() {
override val expressionCheckers: List<FirBasicExpresionChecker> = listOf()
override val qualifiedAccessCheckers: List<FirQualifiedAccessChecker> = listOf(
@@ -16,6 +18,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirProjectionsOnNonClassTypeArgumentChecker,
FirUpperBoundViolatedChecker,
FirTypeArgumentsNotAllowedExpressionChecker,
RedundantCallOfConversionMethod
)
override val functionCallCheckers: List<FirFunctionCallChecker> = listOf()
}
@@ -0,0 +1,76 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.extended
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirQualifiedAccessChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
import org.jetbrains.kotlin.fir.types.ConeFlexibleType
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isMarkedNullable
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression
import org.jetbrains.kotlin.types.expressions.OperatorConventions
object RedundantCallOfConversionMethod : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
if (functionCall !is FirFunctionCall) return
if (functionCall.source?.kind == FirFakeSourceElementKind.GeneratedToStringCallOnTemplateEntry) return
val functionName = functionCall.calleeReference.name.asString()
val qualifiedType = targetClassMap[functionName] ?: return
if (functionCall.explicitReceiver?.isRedundant(qualifiedType) == true) {
reporter.report(functionCall.source, FirErrors.REDUNDANT_CALL_OF_CONVERSION_METHOD)
}
}
private val targetClassMap = mapOf(
"toString" to StandardClassIds.String,
"toDouble" to StandardClassIds.Double,
"toFloat" to StandardClassIds.Float,
"toLong" to StandardClassIds.Long,
"toInt" to StandardClassIds.Int,
"toChar" to StandardClassIds.Char,
"toShort" to StandardClassIds.Short,
"toByte" to StandardClassIds.Byte,
"toULong" to StandardClassIds.ULong,
"toUInt" to StandardClassIds.UInt,
"toUShort" to StandardClassIds.UShort,
"toUByte" to StandardClassIds.UByte
)
private fun FirExpression.isRedundant(qualifiedClassId: ClassId): Boolean {
val thisType = if (this is FirConstExpression<*>) {
this.typeRef.coneType.classId
} else {
val binaryExpression = psi as? KtBinaryExpression
val operator = binaryExpression?.operationToken
if (operator in OperatorConventions.COMPARISON_OPERATIONS) {
// Special case here because compareTo returns Int
StandardClassIds.Boolean
} else {
when {
typeRef.coneType is ConeFlexibleType -> null
psi?.parent !is KtSafeQualifiedExpression
&& (psi is KtSafeQualifiedExpression || typeRef.coneType.isMarkedNullable) -> null
this.typeRef.coneType.isMarkedNullable -> null
else -> this.typeRef.coneType.classId
}
}
}
return thisType == qualifiedClassId
}
}
@@ -104,8 +104,7 @@ object FirErrors {
val REDUNDANT_SINGLE_EXPRESSION_STRING_TEMPLATE by warning0<FirSourceElement, PsiElement>()
val CAN_BE_VAL by warning0<FirSourceElement, PsiElement>()
val CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT by warning0<FirSourceElement, PsiElement>()
val REDUNDANT_CALL_OF_CONVERSION_METHOD by warning0<FirSourceElement, PsiElement>()
val ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS by warning0<FirSourceElement, PsiElement>()
val EMPTY_RANGE by warning0<FirSourceElement, PsiElement>()
}