diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt index 555e01691a8..50828922beb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt @@ -126,6 +126,7 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() { actions.add(wrongPrimitiveLiteralFix) } actions.add(NumberConversionFix(diagnosticElement, expectedType, wrongPrimitiveLiteralFix)) + actions.add(RoundNumberFix(diagnosticElement, expectedType, wrongPrimitiveLiteralFix)) } if (KotlinBuiltIns.isCharSequenceOrNullableCharSequence(expectedType) || KotlinBuiltIns.isStringOrNullableString(expectedType)) { diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/RoundNumberFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/RoundNumberFix.kt new file mode 100644 index 00000000000..76571d6480d --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/RoundNumberFix.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.idea.quickfix + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.codeInsight.intention.LowPriorityAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.ImportInsertHelper +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.createExpressionByPattern +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.isDouble +import org.jetbrains.kotlin.types.typeUtil.isFloat +import org.jetbrains.kotlin.types.typeUtil.isInt +import org.jetbrains.kotlin.types.typeUtil.isLong + +class RoundNumberFix( + element: KtExpression, + type: KotlinType, + private val disableIfAvailable: IntentionAction? = null +) : KotlinQuickFixAction(element), LowPriorityAction { + + private val isTarget = type.isLongOrInt() && element.analyze(BodyResolveMode.PARTIAL).getType(element)?.isDoubleOrFloat() == true + + private val roundFunction = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.renderType(type).let { "roundTo$it" } + + override fun isAvailable(project: Project, editor: Editor?, file: KtFile) = + disableIfAvailable?.isAvailable(project, editor, file) != true && isTarget + + override fun getText() = "Round using $roundFunction()" + + override fun getFamilyName() = text + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val element = element ?: return + val replaced = element.replaced(KtPsiFactory(file).createExpressionByPattern("$0.$roundFunction()", element)) + file.resolveImportReference(FqName("kotlin.math.$roundFunction")).firstOrNull()?.also { + ImportInsertHelper.getInstance(project).importDescriptor(file, it) + } + editor?.caretModel?.moveToOffset(replaced.endOffset) + } + + private fun KotlinType.isLongOrInt() = this.isLong() || this.isInt() + + private fun KotlinType.isDoubleOrFloat() = this.isDouble() || this.isFloat() +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/notExactArgument.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/notExactArgument.kt index bec3f3d6ec4..7b714e2855c 100644 --- a/idea/testData/quickfix/createFromUsage/createFunction/call/notExactArgument.kt +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/notExactArgument.kt @@ -1,5 +1,6 @@ // "Create function 'synchronized'" "false" // ACTION: Convert expression to 'Int' +// ACTION: Round using roundToInt() // ERROR: Type mismatch: inferred type is Float but Int was expected // WITH_RUNTIME diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/notApplicable.kt b/idea/testData/quickfix/typeMismatch/roundNumber/notApplicable.kt new file mode 100644 index 00000000000..3d23d09bf55 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/notApplicable.kt @@ -0,0 +1,11 @@ +// "Round using roundToInt()" "false" +// DISABLE-ERRORS +// ACTION: Change parameter 'x' type of function 'foo' to 'Double' +// ACTION: Convert expression to 'Float' +// ACTION: Create function 'foo' +// WITH_RUNTIME +fun test(d: Double) { + foo(d) +} + +fun foo(x: Float) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/notApplicable2.kt b/idea/testData/quickfix/typeMismatch/roundNumber/notApplicable2.kt new file mode 100644 index 00000000000..f9de45cb603 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/notApplicable2.kt @@ -0,0 +1,11 @@ +// "Round using roundToInt()" "false" +// DISABLE-ERRORS +// ACTION: Change parameter 'x' type of function 'foo' to 'Long' +// ACTION: Convert expression to 'Int' +// ACTION: Create function 'foo' +// WITH_RUNTIME +fun test(l: Long) { + foo(l) +} + +fun foo(x: Int) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToInt.kt b/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToInt.kt new file mode 100644 index 00000000000..0d05b0c53c3 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToInt.kt @@ -0,0 +1,7 @@ +// "Round using roundToInt()" "true" +// WITH_RUNTIME +fun test(d: Double) { + foo(d) +} + +fun foo(x: Int) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToInt.kt.after b/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToInt.kt.after new file mode 100644 index 00000000000..f7aad3c3e1b --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToInt.kt.after @@ -0,0 +1,9 @@ +import kotlin.math.roundToInt + +// "Round using roundToInt()" "true" +// WITH_RUNTIME +fun test(d: Double) { + foo(d.roundToInt()) +} + +fun foo(x: Int) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToLong.kt b/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToLong.kt new file mode 100644 index 00000000000..f872cafaee2 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToLong.kt @@ -0,0 +1,7 @@ +// "Round using roundToLong()" "true" +// WITH_RUNTIME +fun test(d: Double) { + bar(d) +} + +fun bar(x: Long) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToLong.kt.after b/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToLong.kt.after new file mode 100644 index 00000000000..e490a6a1705 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToLong.kt.after @@ -0,0 +1,9 @@ +import kotlin.math.roundToLong + +// "Round using roundToLong()" "true" +// WITH_RUNTIME +fun test(d: Double) { + bar(d.roundToLong()) +} + +fun bar(x: Long) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToInt.kt b/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToInt.kt new file mode 100644 index 00000000000..a23c4ff0b6d --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToInt.kt @@ -0,0 +1,7 @@ +// "Round using roundToInt()" "true" +// WITH_RUNTIME +fun test(f: Float) { + foo(f) +} + +fun foo(x: Int) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToInt.kt.after b/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToInt.kt.after new file mode 100644 index 00000000000..2ce7a023153 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToInt.kt.after @@ -0,0 +1,9 @@ +import kotlin.math.roundToInt + +// "Round using roundToInt()" "true" +// WITH_RUNTIME +fun test(f: Float) { + foo(f.roundToInt()) +} + +fun foo(x: Int) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToLong.kt b/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToLong.kt new file mode 100644 index 00000000000..58471a279a4 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToLong.kt @@ -0,0 +1,7 @@ +// "Round using roundToLong()" "true" +// WITH_RUNTIME +fun test(f: Float) { + bar(f) +} + +fun bar(x: Long) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToLong.kt.after b/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToLong.kt.after new file mode 100644 index 00000000000..2244832bf2c --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToLong.kt.after @@ -0,0 +1,9 @@ +import kotlin.math.roundToLong + +// "Round using roundToLong()" "true" +// WITH_RUNTIME +fun test(f: Float) { + bar(f.roundToLong()) +} + +fun bar(x: Long) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/wrongPrimitive/doubleToIntDecimalPlaces.kt b/idea/testData/quickfix/typeMismatch/wrongPrimitive/doubleToIntDecimalPlaces.kt index 0158dca1136..c8b5b6cd66a 100644 --- a/idea/testData/quickfix/typeMismatch/wrongPrimitive/doubleToIntDecimalPlaces.kt +++ b/idea/testData/quickfix/typeMismatch/wrongPrimitive/doubleToIntDecimalPlaces.kt @@ -3,6 +3,7 @@ // ACTION: Change type of 'a' to 'Double' // ACTION: Convert expression to 'Int' // ACTION: Convert property initializer to getter +// ACTION: Round using roundToInt() // ERROR: The floating-point literal does not conform to the expected type Int val a : Int = 1.12 \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/wrongPrimitive/doubleToLongNotInRange.kt b/idea/testData/quickfix/typeMismatch/wrongPrimitive/doubleToLongNotInRange.kt index 616a1904d6e..78ee723ba7c 100644 --- a/idea/testData/quickfix/typeMismatch/wrongPrimitive/doubleToLongNotInRange.kt +++ b/idea/testData/quickfix/typeMismatch/wrongPrimitive/doubleToLongNotInRange.kt @@ -4,6 +4,7 @@ // ACTION: Convert expression to 'Long' // ACTION: Convert property initializer to getter // ACTION: Add underscores +// ACTION: Round using roundToLong() // ERROR: The floating-point literal does not conform to the expected type Long val a : Long = 10000000000000000000.0 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index e03262a866a..1a352d25ff0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -4424,6 +4424,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes } } + @TestMetadata("idea/testData/quickfix/typeMismatch/roundNumber") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RoundNumber extends AbstractQuickFixMultiFileTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInRoundNumber() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/roundNumber"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true); + } + } + @TestMetadata("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 3c6fc975882..444d1b25469 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -13294,6 +13294,49 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/typeMismatch/roundNumber") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RoundNumber extends AbstractQuickFixTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInRoundNumber() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/roundNumber"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("notApplicable.kt") + public void testNotApplicable() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/roundNumber/notApplicable.kt"); + } + + @TestMetadata("notApplicable2.kt") + public void testNotApplicable2() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/roundNumber/notApplicable2.kt"); + } + + @TestMetadata("roundDoubleToInt.kt") + public void testRoundDoubleToInt() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToInt.kt"); + } + + @TestMetadata("roundDoubleToLong.kt") + public void testRoundDoubleToLong() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/roundNumber/roundDoubleToLong.kt"); + } + + @TestMetadata("roundFloatToInt.kt") + public void testRoundFloatToInt() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToInt.kt"); + } + + @TestMetadata("roundFloatToLong.kt") + public void testRoundFloatToLong() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/roundNumber/roundFloatToLong.kt"); + } + } + @TestMetadata("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)