Add "Round using roundToInt/roundToLong" quick fix

#KT-30389 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-04-08 05:41:44 +03:00
committed by Mikhail Glukhikh
parent 1455451601
commit 607325c6e5
17 changed files with 205 additions and 0 deletions
@@ -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)) {
@@ -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<KtExpression>(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()
}
@@ -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
@@ -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<caret>)
}
fun foo(x: Float) {}
@@ -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<caret>)
}
fun foo(x: Int) {}
@@ -0,0 +1,7 @@
// "Round using roundToInt()" "true"
// WITH_RUNTIME
fun test(d: Double) {
foo(d<caret>)
}
fun foo(x: Int) {}
@@ -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) {}
@@ -0,0 +1,7 @@
// "Round using roundToLong()" "true"
// WITH_RUNTIME
fun test(d: Double) {
bar(d<caret>)
}
fun bar(x: Long) {}
@@ -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) {}
@@ -0,0 +1,7 @@
// "Round using roundToInt()" "true"
// WITH_RUNTIME
fun test(f: Float) {
foo(f<caret>)
}
fun foo(x: Int) {}
@@ -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) {}
@@ -0,0 +1,7 @@
// "Round using roundToLong()" "true"
// WITH_RUNTIME
fun test(f: Float) {
bar(f<caret>)
}
fun bar(x: Long) {}
@@ -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) {}
@@ -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<caret>
@@ -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<caret>
@@ -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)
@@ -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)