KT-6044 J2K: Convert numeric float literals to 0.2f instead of 0.2.toFloat() + more fixes related to literals and type transformations

#KT-6044 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-03-27 17:43:30 +03:00
parent 5454f7053f
commit 39f94d7d45
22 changed files with 291 additions and 135 deletions
@@ -16,39 +16,9 @@
package org.jetbrains.kotlin.j2k
import com.intellij.psi.PsiType
import com.intellij.psi.PsiCodeBlock
import com.intellij.psi.PsiStatement
import org.jetbrains.kotlin.j2k.ast.Block
import org.jetbrains.kotlin.j2k.ast.LBrace
import org.jetbrains.kotlin.j2k.ast.assignPrototype
import org.jetbrains.kotlin.j2k.ast.RBrace
import org.jetbrains.kotlin.j2k.ast.Statement
import com.intellij.psi.PsiExpression
import org.jetbrains.kotlin.j2k.ast.Expression
import com.intellij.psi.PsiLocalVariable
import org.jetbrains.kotlin.j2k.ast.LocalVariable
import com.intellij.psi.PsiModifier
import org.jetbrains.kotlin.j2k.ast.declarationIdentifier
import org.jetbrains.kotlin.j2k.ast.Identifier
import com.intellij.psi.PsiPrimitiveType
import com.intellij.psi.PsiClassType
import org.jetbrains.kotlin.j2k.ast.BangBangExpression
import org.jetbrains.kotlin.j2k.ast.assignNoPrototype
import org.jetbrains.kotlin.j2k.ast.LiteralExpression
import org.jetbrains.kotlin.j2k.ast.MethodCallExpression
import org.jetbrains.kotlin.j2k.ast.Type
import org.jetbrains.kotlin.j2k.ast.ErrorType
import org.jetbrains.kotlin.j2k.ast.Nullability
import com.intellij.psi.CommonClassNames.JAVA_LANG_BYTE
import com.intellij.psi.CommonClassNames.JAVA_LANG_SHORT
import com.intellij.psi.CommonClassNames.JAVA_LANG_INTEGER
import com.intellij.psi.CommonClassNames.JAVA_LANG_LONG
import com.intellij.psi.CommonClassNames.JAVA_LANG_FLOAT
import com.intellij.psi.CommonClassNames.JAVA_LANG_DOUBLE
import com.intellij.psi.CommonClassNames.JAVA_LANG_CHARACTER
import com.intellij.psi.PsiAnonymousClass
import org.jetbrains.kotlin.j2k.ast.AnonymousClassBody
import com.intellij.psi.*
import com.intellij.psi.CommonClassNames.*
import org.jetbrains.kotlin.j2k.ast.*
class CodeConverter(
public val converter: Converter,
@@ -113,18 +83,40 @@ class CodeConverter(
var convertedExpression = convertExpression(expression)
if (expectedType == null || expectedType == PsiType.VOID) return convertedExpression
val actualType = expression.getType()
if (actualType == null) return convertedExpression
val actualType = expression.getType() ?: return convertedExpression
if (convertedExpression.isNullable &&
(actualType is PsiPrimitiveType || actualType is PsiClassType && expectedType is PsiPrimitiveType)) {
convertedExpression = BangBangExpression(convertedExpression).assignNoPrototype()
if (actualType is PsiPrimitiveType || actualType is PsiClassType && expectedType is PsiPrimitiveType) {
convertedExpression = BangBangExpression.surroundIfNullable(convertedExpression)
}
if (needConversion(actualType, expectedType) && convertedExpression !is LiteralExpression) {
val conversion = PRIMITIVE_TYPE_CONVERSIONS[expectedType.getCanonicalText()]
if (conversion != null) {
convertedExpression = MethodCallExpression.buildNotNull(convertedExpression, conversion)
if (needConversion(actualType, expectedType)) {
val expectedTypeStr = expectedType.getCanonicalText()
if (expression is PsiLiteralExpression) {
if (expectedTypeStr == "float" || expectedTypeStr == "double") {
var text = convertedExpression.canonicalCode()
if (text.last() in setOf('f', 'L')) {
text = text.substring(0, text.length() - 1)
}
if (expectedTypeStr == "float") {
text += "f"
}
else {
if (text.indexOf('.') < 0) {
text += ".0"
}
}
convertedExpression = LiteralExpression(text)
}
}
else if (expression is PsiPrefixExpression && expression.isLiteralWithSign()) {
val operandConverted = convertExpression(expression.getOperand(), expectedType)
convertedExpression = PrefixExpression(expression.getOperationSign().getText(), operandConverted)
}
else {
val conversion = PRIMITIVE_TYPE_CONVERSIONS[expectedTypeStr]
if (conversion != null) {
convertedExpression = MethodCallExpression.buildNotNull(convertedExpression, conversion)
}
}
}
@@ -133,8 +125,7 @@ class CodeConverter(
public fun convertedExpressionType(expression: PsiExpression, expectedType: PsiType): Type {
var convertedExpression = convertExpression(expression)
val actualType = expression.getType()
if (actualType == null) return ErrorType()
val actualType = expression.getType() ?: return ErrorType()
var resultType = typeConverter.convertType(actualType, if (convertedExpression.isNullable) Nullability.Nullable else Nullability.NotNull)
if (actualType is PsiPrimitiveType && resultType.isNullable ||
@@ -142,9 +133,16 @@ class CodeConverter(
resultType = resultType.toNotNullType()
}
if (needConversion(actualType, expectedType) && convertedExpression !is LiteralExpression) {
val conversion = PRIMITIVE_TYPE_CONVERSIONS[expectedType.getCanonicalText()]
if (conversion != null) {
if (needConversion(actualType, expectedType)) {
val expectedTypeStr = expectedType.getCanonicalText()
val willConvert = if (convertedExpression is LiteralExpression
|| expression is PsiPrefixExpression && expression.isLiteralWithSign() )
expectedTypeStr == "float" || expectedTypeStr == "double"
else
PRIMITIVE_TYPE_CONVERSIONS[expectedTypeStr] != null
if (willConvert) {
resultType = typeConverter.convertType(expectedType, Nullability.NotNull)
}
}
@@ -152,6 +150,9 @@ class CodeConverter(
return resultType
}
private fun PsiPrefixExpression.isLiteralWithSign()
= getOperand() is PsiLiteralExpression && getOperationTokenType() in setOf(JavaTokenType.PLUS, JavaTokenType.MINUS)
public fun convertAnonymousClassBody(anonymousClass: PsiAnonymousClass): AnonymousClassBody {
return AnonymousClassBody(ClassBodyConverter(anonymousClass, converter, isOpenClass = false, isObject = false).convertBody(),
anonymousClass.getBaseClassType().resolve()?.isInterface() ?: false).assignPrototype(anonymousClass)
@@ -74,11 +74,12 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
}
override fun visitArrayInitializerExpression(expression: PsiArrayInitializerExpression) {
val expressionType = typeConverter.convertType(expression.getType())
val arrayType = expression.getType()
val componentType = (arrayType as? PsiArrayType)?.getComponentType()
val expressionType = typeConverter.convertType(arrayType)
assert(expressionType is ArrayType) { "Array initializer must have array type: expressionType = $expressionType expression = $expression" }
result = createArrayInitializerExpression(expressionType as ArrayType,
codeConverter.convertExpressions(expression.getInitializers()),
needExplicitType = true/*TODO: it's often redundant*/)
expression.getInitializers().map { codeConverter.convertExpression(it, componentType) })
}
override fun visitAssignmentExpression(expression: PsiAssignmentExpression) {
@@ -104,17 +105,13 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
}
override fun visitBinaryExpression(expression: PsiBinaryExpression) {
val operandsExpectedType = when (expression.getOperationTokenType()) {
JavaTokenType.ANDAND, JavaTokenType.OROR -> PsiType.BOOLEAN
var lhs = codeConverter.convertExpression(expression.getLOperand(), null)
var rhs = codeConverter.convertExpression(expression.getROperand(), null)
JavaTokenType.PLUS, JavaTokenType.MINUS, JavaTokenType.ASTERISK,
JavaTokenType.DIV, JavaTokenType.PERC, JavaTokenType.LTLT, JavaTokenType.GTGT,
JavaTokenType.GTGTGT -> expression.getType()
else -> null
if (expression.getOperationTokenType() in NON_NULL_OPERAND_OPS) {
lhs = BangBangExpression.surroundIfNullable(lhs)
rhs = BangBangExpression.surroundIfNullable(rhs)
}
val lhs = codeConverter.convertExpression(expression.getLOperand(), operandsExpectedType)
val rhs = codeConverter.convertExpression(expression.getROperand(), operandsExpectedType)
if (expression.getOperationTokenType() == JavaTokenType.GTGTGT) {
result = MethodCallExpression.buildNotNull(lhs, "ushr", listOf(rhs))
}
@@ -123,6 +120,18 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
}
}
private val NON_NULL_OPERAND_OPS = setOf(
JavaTokenType.ANDAND,
JavaTokenType.OROR,
JavaTokenType.PLUS,
JavaTokenType.MINUS,
JavaTokenType.ASTERISK,
JavaTokenType.DIV,
JavaTokenType.PERC,
JavaTokenType.LTLT,
JavaTokenType.GTGT,
JavaTokenType.GTGTGT)
override fun visitClassObjectAccessExpression(expression: PsiClassObjectAccessExpression) {
val operand = expression.getOperand()
val typeName = operand.getType().getCanonicalText()
@@ -173,8 +182,8 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
var text = expression.getText()!!
val type = expression.getType()
if (type != null) {
val canonicalTypeStr = type.getCanonicalText()
if (canonicalTypeStr == "double" || canonicalTypeStr == JAVA_LANG_DOUBLE) {
val typeStr = type.getCanonicalText()
if (typeStr == "double") {
text = text.replace("D", "").replace("d", "")
if (!text.contains(".")) {
text += ".0"
@@ -182,15 +191,15 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
}
if (canonicalTypeStr == "float" || canonicalTypeStr == JAVA_LANG_FLOAT) {
text = text.replace("F", "").replace("f", "") + "." + OperatorConventions.FLOAT + "()"
if (typeStr == "float") {
text = text.replace("F", "f")
}
if (canonicalTypeStr == "long" || canonicalTypeStr == JAVA_LANG_LONG) {
text = text.replace("L", "").replace("l", "")
if (typeStr == "long") {
text = text.replace("l", "L")
}
if (canonicalTypeStr == "int" || canonicalTypeStr == JAVA_LANG_INTEGER) {
if (typeStr == "int") {
text = if (value != null) value.toString() else text
}
}
@@ -341,7 +350,7 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
result = BinaryExpression(operand.left, operand.right, "!=")
}
else {
result = PrefixOperator(getOperatorString(token), operand)
result = PrefixExpression(getOperatorString(token), operand)
}
}
@@ -482,23 +491,6 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
}
}
protected fun getClassName(expression: PsiExpression): String {
var context = expression.getContext()
while (context != null) {
val _context = context!!
if (_context is PsiClass) {
val identifier = _context.getNameIdentifier()
if (identifier != null) {
return identifier.getText()!!
}
}
context = _context.getContext()
}
return ""
}
companion object {
private val needQualifierNameSet = setOf("java.lang.Byte", "java.lang.Double", "java.lang.Float", "java.lang.Long", "java.lang.Short")
}
@@ -125,7 +125,7 @@ class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
override fun visitForeachStatement(statement: PsiForeachStatement) {
val iteratorExpr = codeConverter.convertExpression(statement.getIteratedValue())
val iterator = if (iteratorExpr.isNullable) BangBangExpression(iteratorExpr).assignNoPrototype() else iteratorExpr
val iterator = BangBangExpression.surroundIfNullable(iteratorExpr)
val iterationParameter = statement.getIterationParameter()
result = ForeachStatement(iterationParameter.declarationIdentifier(),
if (codeConverter.settings.specifyLocalVariableTypeByDefault) codeConverter.typeConverter.convertVariableType(iterationParameter) else null,
@@ -37,6 +37,15 @@ class BangBangExpression(val expr: Expression) : Expression() {
override fun generateCode(builder: CodeBuilder) {
builder.appendOperand(this, expr).append("!!")
}
companion object {
fun surroundIfNullable(expression: Expression): Expression {
return if (expression.isNullable)
BangBangExpression(expression).assignNoPrototype()
else
expression
}
}
}
class BinaryExpression(val left: Expression, val right: Expression, val op: String) : Expression() {
@@ -69,7 +78,7 @@ class ParenthesizedExpression(val expression: Expression) : Expression() {
}
}
class PrefixOperator(val op: String, val expression: Expression) : Expression() {
class PrefixExpression(val op: String, val expression: Expression) : Expression() {
override fun generateCode(builder: CodeBuilder){
builder.append(op).appendOperand(this, expression)
}
@@ -155,7 +164,7 @@ class DownToExpression(val start: Expression, val end: Expression): Expression()
}
}
fun createArrayInitializerExpression(arrayType: ArrayType, initializers: List<Expression>, needExplicitType: Boolean) : MethodCallExpression {
fun createArrayInitializerExpression(arrayType: ArrayType, initializers: List<Expression>, needExplicitType: Boolean = true) : MethodCallExpression {
val elementType = arrayType.elementType
val createArrayFunction = if (elementType is PrimitiveType)
(elementType.toNotNullType().canonicalCode() + "Array").decapitalize()
@@ -163,31 +172,5 @@ fun createArrayInitializerExpression(arrayType: ArrayType, initializers: List<Ex
arrayType.toNotNullType().canonicalCode().decapitalize()
else
"array"
val doubleOrFloatTypes = setOf("double", "float", "java.lang.double", "java.lang.float")
val afterReplace = arrayType.toNotNullType().canonicalCode().replace("Array", "").toLowerCase().replace(">", "").replace("<", "").replace("?", "")
fun explicitConvertIfNeeded(initializer: Expression): Expression {
if (doubleOrFloatTypes.contains(afterReplace)) {
if (initializer is LiteralExpression) {
if (!initializer.canonicalCode().contains(".")) {
return LiteralExpression(initializer.literalText + ".0").assignPrototypesFrom(initializer)
}
}
else {
val conversionFunction = when {
afterReplace.contains("double") -> OperatorConventions.DOUBLE.getIdentifier()
afterReplace.contains("float") -> OperatorConventions.FLOAT.getIdentifier()
else -> null
}
if (conversionFunction != null) {
return MethodCallExpression.buildNotNull(initializer, conversionFunction).assignNoPrototype()
}
}
}
return initializer
}
return MethodCallExpression.buildNotNull(null, createArrayFunction, initializers.map { explicitConvertIfNeeded(it) })
return MethodCallExpression.buildNotNull(null, createArrayFunction, initializers)
}
+1 -1
View File
@@ -39,7 +39,7 @@ private fun Expression.precedence(): Int? {
return when(this) {
is QualifiedExpression, is MethodCallExpression, is ArrayAccessExpression, is PostfixOperator, is BangBangExpression, is StarExpression -> 0
is PrefixOperator -> 1
is PrefixExpression -> 1
is TypeCastExpression -> 2
@@ -1,4 +1,4 @@
val a = 0
val b = 0
val c = 0
val ds = doubleArray(a.toDouble(), b.toDouble(), c.toDouble())
val a = 0.0
val b = 0.0
val c = 0.0
val ds = doubleArray(a, b, c)
@@ -1 +1 @@
val a = floatArray(1.0, 2.0, 3.0)
val a = floatArray(1f, 2f, 3.0f)
@@ -1,2 +1,2 @@
//statement
java.lang.Double[] a = new java.lang.Double[]{1, 2, 3};
java.lang.Double[] a = new java.lang.Double[]{1.0, 2.0, 3.0};
@@ -1,2 +1,2 @@
//statement
java.lang.Float[] a = new java.lang.Float[]{1, 2, 3};
java.lang.Float[] a = new java.lang.Float[]{1.0f, 2f, 3f};
@@ -1 +1 @@
val a = array<Float>(1.0, 2.0, 3.0)
val a = array(1.0f, 2f, 3f)
@@ -3,7 +3,7 @@ import java.util.ArrayList
class Boxing {
fun test() {
var i: Int? = 0
val n = 0.0.toFloat()
val n = 0.0f
i = 1
var j = i!!
val k = i!! + 2
@@ -1,9 +1,8 @@
// ERROR: An integer literal does not conform to the expected type kotlin.Double
class A {
deprecated("")
volatile var field1 = 0
transient var field2 = 1
strictfp var field3: Double = 2
strictfp var field3 = 2.0
}
+1 -1
View File
@@ -11,6 +11,6 @@ public class Language(protected var code: String) : Serializable {
companion object {
public var ENGLISH: Language = Language("en")
public var SWEDISH: Language = Language("sv")
private val serialVersionUID = -2442762969929206780
private val serialVersionUID = -2442762969929206780L
}
}
@@ -0,0 +1,21 @@
class A {
private double d1 = 1;
private double d2 = 1.0;
private double d3 = 1.0f;
private double d4 = 1.0d;
private double d5 = 1.0D;
private double d6 = 1.0F;
private double d7 = Math.sqrt(2) - 1
void foo1(double d){}
void foo2(Double d){}
void bar() {
foo1(1);
foo1(1f);
foo1(1.0);
foo2(1.0);
d1 = 1.0
d2 = 1
}
}
@@ -0,0 +1,24 @@
class A {
private var d1 = 1.0
private var d2 = 1.0
private val d3 = 1.0
private val d4 = 1.0
private val d5 = 1.0
private val d6 = 1.0
private val d7 = Math.sqrt(2.0) - 1
fun foo1(d: Double) {
}
fun foo2(d: Double?) {
}
fun bar() {
foo1(1.0)
foo1(1.0)
foo1(1.0)
foo2(1.0)
d1 = 1.0
d2 = 1.0
}
}
@@ -1,21 +1,21 @@
class Test {
fun test() {
val l1 = 10
val l1 = 10L
val d1 = 10.0
val f1 = 10.0.toFloat()
val f1 = 10.0f
val l2 = 10
val l2 = 10L
val d2 = 10.0
val f2 = 10.0.toFloat()
val f2 = 10.0f
}
fun testBoxed() {
val l1 = 10
val l1 = 10L
val d1 = 10.0
val f1 = 10.0.toFloat()
val f1 = 10.0f
val l2 = 10
val l2 = 10L
val d2 = 10.0
val f2 = 10.0.toFloat()
val f2 = 10.0f
}
}
@@ -0,0 +1,24 @@
class A {
private float f1 = 1.0f;
private float f2 = 1.0F;
private float f3 = 1f;
private float f4 = 1;
private float f5 = 1F;
private float f6 = -1;
private float f7 = -1F;
private float f8 = +1;
void foo1(float f){}
void foo2(Float f){}
void bar() {
foo1(1);
foo2(1f);
foo1(1f);
foo1(1L);
foo1(-1);
foo1(-1L);
f1 = 1
f4 = 1.0f
}
}
@@ -0,0 +1,27 @@
class A {
private var f1 = 1.0f
private val f2 = 1.0f
private val f3 = 1f
private var f4 = 1f
private val f5 = 1f
private val f6 = -1f
private val f7 = -1f
private val f8 = +1f
fun foo1(f: Float) {
}
fun foo2(f: Float?) {
}
fun bar() {
foo1(1f)
foo2(1f)
foo1(1f)
foo1(1f)
foo1(-1f)
foo1(-1f)
f1 = 1f
f4 = 1.0f
}
}
@@ -0,0 +1,23 @@
class A {
private long l1 = 1L;
private long l2 = 1;
private long l3 = 1l;
private long l4 = -1;
private long l5 = 123456789101112;
private long l6 = -123456789101112;
private long l7 = +1;
private long l8 = +1L;
void foo1(long l){}
void foo2(Long l){}
void bar() {
foo1(1);
foo1(1L);
foo2(1L);
foo1(-1);
l1 = 10
l2 = 10L
l4 = 10
}
}
@@ -0,0 +1,26 @@
class A {
private var l1 = 1L
private var l2: Long = 1
private val l3 = 1L
private var l4: Long = -1
private val l5 = 123456789101112
private val l6 = -123456789101112
private val l7 = +1
private val l8 = +1L
fun foo1(l: Long) {
}
fun foo2(l: Long?) {
}
fun bar() {
foo1(1)
foo1(1L)
foo2(1L)
foo1(-1)
l1 = 10
l2 = 10L
l4 = 10
}
}
@@ -2908,18 +2908,36 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
doTest(fileName);
}
@TestMetadata("double.java")
public void testDouble() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/double.java");
doTest(fileName);
}
@TestMetadata("endsWithDFL.java")
public void testEndsWithDFL() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/endsWithDFL.java");
doTest(fileName);
}
@TestMetadata("float.java")
public void testFloat() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/float.java");
doTest(fileName);
}
@TestMetadata("hex.java")
public void testHex() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/hex.java");
doTest(fileName);
}
@TestMetadata("long.java")
public void testLong() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/long.java");
doTest(fileName);
}
@TestMetadata("octal.java")
public void testOctal() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/octal.java");
@@ -2908,18 +2908,36 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
doTest(fileName);
}
@TestMetadata("double.java")
public void testDouble() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/double.java");
doTest(fileName);
}
@TestMetadata("endsWithDFL.java")
public void testEndsWithDFL() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/endsWithDFL.java");
doTest(fileName);
}
@TestMetadata("float.java")
public void testFloat() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/float.java");
doTest(fileName);
}
@TestMetadata("hex.java")
public void testHex() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/hex.java");
doTest(fileName);
}
@TestMetadata("long.java")
public void testLong() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/long.java");
doTest(fileName);
}
@TestMetadata("octal.java")
public void testOctal() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/literalExpression/octal.java");