Support '.toType()' and literal fixes related to unsigned type mismatch

#KT-26836 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-12-26 15:05:31 +03:00
parent 73b6148407
commit 8420fceb8c
14 changed files with 102 additions and 19 deletions
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.types.typeUtil package org.jetbrains.kotlin.types.typeUtil
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.UnsignedTypes
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.resolve.DescriptorUtils 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.isNullableAny(): Boolean = KotlinBuiltIns.isNullableAny(this)
fun KotlinType.isBoolean(): Boolean = KotlinBuiltIns.isBoolean(this) fun KotlinType.isBoolean(): Boolean = KotlinBuiltIns.isBoolean(this)
fun KotlinType.isPrimitiveNumberType(): Boolean = KotlinBuiltIns.isPrimitiveType(this) && !isBoolean() 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.isBooleanOrNullableBoolean(): Boolean = KotlinBuiltIns.isBooleanOrNullableBoolean(this)
fun KotlinType.isNotNullThrowable(): Boolean = KotlinBuiltIns.isThrowableOrNullableThrowable(this) && !isMarkedNullable 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.psi.psiUtil.endOffset
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType import org.jetbrains.kotlin.types.typeUtil.isSignedOrUnsignedNumberType
class NumberConversionFix( class NumberConversionFix(
element: KtExpression, element: KtExpression,
type: KotlinType, type: KotlinType,
private val disableIfAvailable: IntentionAction? = null private val disableIfAvailable: IntentionAction? = null
) : KotlinQuickFixAction<KtExpression>(element) { ) : KotlinQuickFixAction<KtExpression>(element) {
private val isConversionAvailable: Boolean = run { private val isConversionAvailable: Boolean = run {
val expressionType = element.analyze(BodyResolveMode.PARTIAL).getType(element) 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) private val typePresentation = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.renderType(type)
override fun isAvailable(project: Project, editor: Editor?, file: KtFile) override fun isAvailable(project: Project, editor: Editor?, file: KtFile) =
= disableIfAvailable?.isAvailable(project, editor, file) != true disableIfAvailable?.isAvailable(project, editor, file) != true && isConversionAvailable
&& isConversionAvailable
override fun getFamilyName() = "Insert number conversion" override fun getFamilyName() = "Insert number conversion"
override fun getText() = "Convert expression to '$typePresentation'" override fun getText() = "Convert expression to '$typePresentation'"
@@ -117,7 +117,7 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() {
return emptyList() return emptyList()
} }
if (expressionType.isPrimitiveNumberType() && expectedType.isPrimitiveNumberType()) { if (expressionType.isSignedOrUnsignedNumberType() && expectedType.isSignedOrUnsignedNumberType()) {
var wrongPrimitiveLiteralFix: WrongPrimitiveLiteralFix? = null var wrongPrimitiveLiteralFix: WrongPrimitiveLiteralFix? = null
if (diagnosticElement is KtConstantExpression && !KotlinBuiltIns.isChar(expectedType)) { if (diagnosticElement is KtConstantExpression && !KotlinBuiltIns.isChar(expectedType)) {
wrongPrimitiveLiteralFix = WrongPrimitiveLiteralFix(diagnosticElement, 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.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isUnsignedNumberType
private val valueRanges = mapOf( private val valueRanges = mapOf(
KotlinBuiltIns.FQ_NAMES._byte to Byte.MIN_VALUE.toLong()..Byte.MAX_VALUE.toLong(), 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 typeName = DescriptorUtils.getFqName(type.constructor.declarationDescriptor!!)
private val expectedTypeIsFloat = KotlinBuiltIns.isFloat(type) private val expectedTypeIsFloat = KotlinBuiltIns.isFloat(type)
private val expectedTypeIsDouble = KotlinBuiltIns.isDouble(type) private val expectedTypeIsDouble = KotlinBuiltIns.isDouble(type)
private val expectedTypeIsUnsigned = type.isUnsignedNumberType()
private val constValue = run { private val constValue = run {
val shouldInlineCosntVals = element.languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals) val shouldInlineConstVals = element.languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals)
ExpressionCodegen.getPrimitiveOrStringCompileTimeConstant( ExpressionCodegen.getPrimitiveOrStringCompileTimeConstant(
element, element.analyze(BodyResolveMode.PARTIAL), shouldInlineCosntVals)?.value as? Number element, element.analyze(BodyResolveMode.PARTIAL), shouldInlineConstVals
)?.value as? Number
} }
private val fixedExpression = buildString { private val fixedExpression = buildString {
@@ -57,17 +60,17 @@ class WrongPrimitiveLiteralFix(element: KtConstantExpression, type: KotlinType)
append(constValue) append(constValue)
if (expectedTypeIsFloat) { if (expectedTypeIsFloat) {
append('F') append('F')
} } else if ('.' !in this) {
else if ('.' !in this) {
append(".0") append(".0")
} }
} } else if (expectedTypeIsUnsigned) {
else { append(constValue)
append('u')
} else {
if (constValue is Float || constValue is Double) { if (constValue is Float || constValue is Double) {
append(constValue.toLong()) append(constValue.toLong())
} } else {
else { append(element.text.trimEnd('l', 'L', 'u'))
append(element.text.trimEnd('l', 'L'))
} }
if (KotlinBuiltIns.isLong(type)) { if (KotlinBuiltIns.isLong(type)) {
@@ -78,7 +81,7 @@ class WrongPrimitiveLiteralFix(element: KtConstantExpression, type: KotlinType)
override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean {
if (constValue == null) return false if (constValue == null) return false
if (expectedTypeIsFloat || expectedTypeIsDouble) return true if (expectedTypeIsFloat || expectedTypeIsDouble || expectedTypeIsUnsigned) return true
if (constValue is Float || constValue is Double) { if (constValue is Float || constValue is Double) {
val value = constValue.toDouble() val value = constValue.toDouble()
+1
View File
@@ -3,6 +3,7 @@
// ERROR: Conversion of signed constants to unsigned ones is prohibited // ERROR: Conversion of signed constants to unsigned ones is prohibited
// ACTION: Change parameter 'u' type of function 'takeUInt' to 'Int' // ACTION: Change parameter 'u' type of function 'takeUInt' to 'Int'
// ACTION: Convert property initializer to getter // ACTION: Convert property initializer to getter
// ACTION: Change to '1u'
// ACTION: Do not show hints for current method // ACTION: Do not show hints for current method
// ACTION: Add 'u =' to argument // 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)
}
@@ -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 { public void testConvertExpression() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/numberConversion/convertExpression.kt"); 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") @TestMetadata("idea/testData/quickfix/typeMismatch/parameterTypeMismatch")
@@ -12771,6 +12781,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/intToFloat.kt"); 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") @TestMetadata("longToDouble.kt")
public void testLongToDouble() throws Exception { public void testLongToDouble() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/longToDouble.kt"); runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/longToDouble.kt");
@@ -12785,6 +12800,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
public void testLongToIntBinary() throws Exception { public void testLongToIntBinary() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/longToIntBinary.kt"); runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/longToIntBinary.kt");
} }
@TestMetadata("unsignedToInt.kt")
public void testUnsignedToInt() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrongPrimitive/unsignedToInt.kt");
}
} }
} }