Support '.toType()' and literal fixes related to unsigned type mismatch
#KT-26836 Fixed
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.types.typeUtil
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.UnsignedTypes
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
@@ -58,6 +59,8 @@ fun KotlinType.isAnyOrNullableAny(): Boolean = KotlinBuiltIns.isAnyOrNullableAny
|
||||
fun KotlinType.isNullableAny(): Boolean = KotlinBuiltIns.isNullableAny(this)
|
||||
fun KotlinType.isBoolean(): Boolean = KotlinBuiltIns.isBoolean(this)
|
||||
fun KotlinType.isPrimitiveNumberType(): Boolean = KotlinBuiltIns.isPrimitiveType(this) && !isBoolean()
|
||||
fun KotlinType.isUnsignedNumberType(): Boolean = UnsignedTypes.isUnsignedType(this)
|
||||
fun KotlinType.isSignedOrUnsignedNumberType(): Boolean = isPrimitiveNumberType() || isUnsignedNumberType()
|
||||
|
||||
fun KotlinType.isBooleanOrNullableBoolean(): Boolean = KotlinBuiltIns.isBooleanOrNullableBoolean(this)
|
||||
fun KotlinType.isNotNullThrowable(): Boolean = KotlinBuiltIns.isThrowableOrNullableThrowable(this) && !isMarkedNullable
|
||||
|
||||
@@ -29,22 +29,22 @@ 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.isPrimitiveNumberType
|
||||
import org.jetbrains.kotlin.types.typeUtil.isSignedOrUnsignedNumberType
|
||||
|
||||
class NumberConversionFix(
|
||||
element: KtExpression,
|
||||
type: KotlinType,
|
||||
private val disableIfAvailable: IntentionAction? = null
|
||||
element: KtExpression,
|
||||
type: KotlinType,
|
||||
private val disableIfAvailable: IntentionAction? = null
|
||||
) : KotlinQuickFixAction<KtExpression>(element) {
|
||||
private val isConversionAvailable: Boolean = run {
|
||||
val expressionType = element.analyze(BodyResolveMode.PARTIAL).getType(element)
|
||||
expressionType != null && expressionType != type && expressionType.isPrimitiveNumberType() && type.isPrimitiveNumberType()
|
||||
expressionType != null && expressionType != type &&
|
||||
expressionType.isSignedOrUnsignedNumberType() && type.isSignedOrUnsignedNumberType()
|
||||
}
|
||||
private val typePresentation = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.renderType(type)
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: KtFile)
|
||||
= disableIfAvailable?.isAvailable(project, editor, file) != true
|
||||
&& isConversionAvailable
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: KtFile) =
|
||||
disableIfAvailable?.isAvailable(project, editor, file) != true && isConversionAvailable
|
||||
|
||||
override fun getFamilyName() = "Insert number conversion"
|
||||
override fun getText() = "Convert expression to '$typePresentation'"
|
||||
|
||||
@@ -117,7 +117,7 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if (expressionType.isPrimitiveNumberType() && expectedType.isPrimitiveNumberType()) {
|
||||
if (expressionType.isSignedOrUnsignedNumberType() && expectedType.isSignedOrUnsignedNumberType()) {
|
||||
var wrongPrimitiveLiteralFix: WrongPrimitiveLiteralFix? = null
|
||||
if (diagnosticElement is KtConstantExpression && !KotlinBuiltIns.isChar(expectedType)) {
|
||||
wrongPrimitiveLiteralFix = WrongPrimitiveLiteralFix(diagnosticElement, expectedType)
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnsignedNumberType
|
||||
|
||||
private val valueRanges = mapOf(
|
||||
KotlinBuiltIns.FQ_NAMES._byte to Byte.MIN_VALUE.toLong()..Byte.MAX_VALUE.toLong(),
|
||||
@@ -45,11 +46,13 @@ class WrongPrimitiveLiteralFix(element: KtConstantExpression, type: KotlinType)
|
||||
private val typeName = DescriptorUtils.getFqName(type.constructor.declarationDescriptor!!)
|
||||
private val expectedTypeIsFloat = KotlinBuiltIns.isFloat(type)
|
||||
private val expectedTypeIsDouble = KotlinBuiltIns.isDouble(type)
|
||||
private val expectedTypeIsUnsigned = type.isUnsignedNumberType()
|
||||
|
||||
private val constValue = run {
|
||||
val shouldInlineCosntVals = element.languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals)
|
||||
val shouldInlineConstVals = element.languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals)
|
||||
ExpressionCodegen.getPrimitiveOrStringCompileTimeConstant(
|
||||
element, element.analyze(BodyResolveMode.PARTIAL), shouldInlineCosntVals)?.value as? Number
|
||||
element, element.analyze(BodyResolveMode.PARTIAL), shouldInlineConstVals
|
||||
)?.value as? Number
|
||||
}
|
||||
|
||||
private val fixedExpression = buildString {
|
||||
@@ -57,17 +60,17 @@ class WrongPrimitiveLiteralFix(element: KtConstantExpression, type: KotlinType)
|
||||
append(constValue)
|
||||
if (expectedTypeIsFloat) {
|
||||
append('F')
|
||||
}
|
||||
else if ('.' !in this) {
|
||||
} else if ('.' !in this) {
|
||||
append(".0")
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else if (expectedTypeIsUnsigned) {
|
||||
append(constValue)
|
||||
append('u')
|
||||
} else {
|
||||
if (constValue is Float || constValue is Double) {
|
||||
append(constValue.toLong())
|
||||
}
|
||||
else {
|
||||
append(element.text.trimEnd('l', 'L'))
|
||||
} else {
|
||||
append(element.text.trimEnd('l', 'L', 'u'))
|
||||
}
|
||||
|
||||
if (KotlinBuiltIns.isLong(type)) {
|
||||
@@ -78,7 +81,7 @@ class WrongPrimitiveLiteralFix(element: KtConstantExpression, type: KotlinType)
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean {
|
||||
if (constValue == null) return false
|
||||
if (expectedTypeIsFloat || expectedTypeIsDouble) return true
|
||||
if (expectedTypeIsFloat || expectedTypeIsDouble || expectedTypeIsUnsigned) return true
|
||||
|
||||
if (constValue is Float || constValue is Double) {
|
||||
val value = constValue.toDouble()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// ERROR: Conversion of signed constants to unsigned ones is prohibited
|
||||
// ACTION: Change parameter 'u' type of function 'takeUInt' to 'Int'
|
||||
// ACTION: Convert property initializer to getter
|
||||
// ACTION: Change to '1u'
|
||||
// ACTION: Do not show hints for current method
|
||||
// ACTION: Add 'u =' to argument
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Convert expression to 'Int'" "true"
|
||||
// WITH_RUNTIME
|
||||
fun foo(param: Int) {}
|
||||
|
||||
fun test(expr: UInt) {
|
||||
foo(<caret>expr)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Convert expression to 'Int'" "true"
|
||||
// WITH_RUNTIME
|
||||
fun foo(param: Int) {}
|
||||
|
||||
fun test(expr: UInt) {
|
||||
foo(expr.toInt())
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Convert expression to 'UInt'" "true"
|
||||
// WITH_RUNTIME
|
||||
fun foo(param: UInt) {}
|
||||
|
||||
fun test(expr: Int) {
|
||||
foo(<caret>expr)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Convert expression to 'UInt'" "true"
|
||||
// WITH_RUNTIME
|
||||
fun foo(param: UInt) {}
|
||||
|
||||
fun test(expr: Int) {
|
||||
foo(expr.toUInt())
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change to '1u'" "true"
|
||||
// WITH_RUNTIME
|
||||
fun foo(param: UInt) {}
|
||||
|
||||
fun test() {
|
||||
foo(<caret>1)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change to '1u'" "true"
|
||||
// WITH_RUNTIME
|
||||
fun foo(param: UInt) {}
|
||||
|
||||
fun test() {
|
||||
foo(1u)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change to '1'" "true"
|
||||
// WITH_RUNTIME
|
||||
fun foo(param: Int) {}
|
||||
|
||||
fun test() {
|
||||
foo(<caret>1u)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change to '1'" "true"
|
||||
// WITH_RUNTIME
|
||||
fun foo(param: Int) {}
|
||||
|
||||
fun test() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -12488,6 +12488,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
public void testConvertExpression() throws Exception {
|
||||
runTest("idea/testData/quickfix/typeMismatch/numberConversion/convertExpression.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("convertToSigned.kt")
|
||||
public void testConvertToSigned() throws Exception {
|
||||
runTest("idea/testData/quickfix/typeMismatch/numberConversion/convertToSigned.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("convertToUnsigned.kt")
|
||||
public void testConvertToUnsigned() throws Exception {
|
||||
runTest("idea/testData/quickfix/typeMismatch/numberConversion/convertToUnsigned.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/typeMismatch/parameterTypeMismatch")
|
||||
@@ -12771,6 +12781,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/intToFloat.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intToUnsigned.kt")
|
||||
public void testIntToUnsigned() throws Exception {
|
||||
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/intToUnsigned.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("longToDouble.kt")
|
||||
public void testLongToDouble() throws Exception {
|
||||
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/longToDouble.kt");
|
||||
@@ -12785,6 +12800,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
public void testLongToIntBinary() throws Exception {
|
||||
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/longToIntBinary.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unsignedToInt.kt")
|
||||
public void testUnsignedToInt() throws Exception {
|
||||
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/unsignedToInt.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user