Java to Kotlin converter: specify local variable type when necessary
#KT-5277 Fixed
This commit is contained in:
@@ -249,7 +249,7 @@ public class Converter private(val project: Project, val settings: ConverterSett
|
||||
val localVar = LocalVariable(SecondaryConstructor.tempValIdentifier(),
|
||||
Annotations.Empty,
|
||||
Modifiers.Empty,
|
||||
{ ClassType(className, listOf(), Nullability.NotNull, settings) },
|
||||
null,
|
||||
initializer,
|
||||
true,
|
||||
settings).assignNoPrototype()
|
||||
@@ -554,34 +554,70 @@ public class Converter private(val project: Project, val settings: ConverterSett
|
||||
return Parameter(parameter.declarationIdentifier(), `type`, varValModifier, convertAnnotations(parameter), modifiers).assignPrototype(parameter)
|
||||
}
|
||||
|
||||
fun convertExpression(argument: PsiExpression?, expectedType: PsiType?): Expression {
|
||||
if (argument == null) return Identifier.Empty
|
||||
fun convertExpression(expression: PsiExpression?, expectedType: PsiType?): Expression {
|
||||
if (expression == null) return Identifier.Empty
|
||||
|
||||
var expression = convertExpression(argument)
|
||||
val actualType = argument.getType()
|
||||
val isPrimitiveTypeOrNull = actualType == null || actualType is PsiPrimitiveType
|
||||
if (isPrimitiveTypeOrNull && expression.isNullable) {
|
||||
expression = BangBangExpression(expression)
|
||||
var convertedExpression = convertExpression(expression)
|
||||
if (expectedType == null || expectedType == PsiType.VOID) return convertedExpression
|
||||
|
||||
val actualType = expression.getType()
|
||||
if (actualType == null) return convertedExpression
|
||||
|
||||
if (actualType is PsiPrimitiveType && convertedExpression.isNullable ||
|
||||
expectedType is PsiPrimitiveType && actualType is PsiClassType) {
|
||||
convertedExpression = BangBangExpression(convertedExpression)
|
||||
}
|
||||
else if (expectedType is PsiPrimitiveType && actualType is PsiClassType) {
|
||||
if (PsiPrimitiveType.getUnboxedType(actualType) == expectedType) {
|
||||
expression = BangBangExpression(expression)
|
||||
|
||||
if (canConvertType(actualType, expectedType) && convertedExpression !is LiteralExpression) {
|
||||
val conversion = PRIMITIVE_TYPE_CONVERSIONS[expectedType.getCanonicalText()]
|
||||
if (conversion != null) {
|
||||
convertedExpression = MethodCallExpression.buildNotNull(convertedExpression, conversion)
|
||||
}
|
||||
}
|
||||
|
||||
if (actualType != null) {
|
||||
if (isConversionNeeded(actualType, expectedType) && expression !is LiteralExpression) {
|
||||
val conversion = PRIMITIVE_TYPE_CONVERSIONS[expectedType?.getCanonicalText()]
|
||||
if (conversion != null) {
|
||||
expression = MethodCallExpression.buildNotNull(expression, conversion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return expression.assignPrototype(argument)
|
||||
return convertedExpression.assignPrototype(expression)
|
||||
}
|
||||
|
||||
fun convertedExpressionType(expression: PsiExpression, expectedType: PsiType): Type {
|
||||
var convertedExpression = convertExpression(expression)
|
||||
val actualType = expression.getType()
|
||||
if (actualType == null) return ErrorType()
|
||||
var resultType = typeConverter.convertType(actualType, if (convertedExpression.isNullable) Nullability.Nullable else Nullability.NotNull)
|
||||
|
||||
if (actualType is PsiPrimitiveType && resultType.isNullable ||
|
||||
expectedType is PsiPrimitiveType && actualType is PsiClassType) {
|
||||
resultType = resultType.toNotNullType()
|
||||
}
|
||||
|
||||
if (canConvertType(actualType, expectedType) && convertedExpression !is LiteralExpression) {
|
||||
val conversion = PRIMITIVE_TYPE_CONVERSIONS[expectedType.getCanonicalText()]
|
||||
if (conversion != null) {
|
||||
resultType = typeConverter.convertType(expectedType, Nullability.NotNull)
|
||||
}
|
||||
}
|
||||
|
||||
return resultType
|
||||
}
|
||||
|
||||
private fun canConvertType(actual: PsiType, expected: PsiType): Boolean {
|
||||
val expectedStr = expected.getCanonicalText()
|
||||
val actualStr = actual.getCanonicalText()
|
||||
if (expectedStr == actualStr) return false
|
||||
val o1 = expectedStr == typeConversionMap[actualStr]
|
||||
val o2 = actualStr == typeConversionMap[expectedStr]
|
||||
return o1 == o2
|
||||
}
|
||||
|
||||
private val typeConversionMap: Map<String, String> = mapOf(
|
||||
JAVA_LANG_BYTE to "byte",
|
||||
JAVA_LANG_SHORT to "short",
|
||||
JAVA_LANG_INTEGER to "int",
|
||||
JAVA_LANG_LONG to "long",
|
||||
JAVA_LANG_FLOAT to "float",
|
||||
JAVA_LANG_DOUBLE to "double",
|
||||
JAVA_LANG_CHARACTER to "char"
|
||||
)
|
||||
|
||||
fun convertIdentifier(identifier: PsiIdentifier?): Identifier {
|
||||
if (identifier == null) return Identifier.Empty
|
||||
|
||||
@@ -690,27 +726,6 @@ public class Converter private(val project: Project, val settings: ConverterSett
|
||||
val annotation = Annotation(Identifier("throws").assignNoPrototype(), arguments, false)
|
||||
return Annotations(listOf(annotation.assignPrototype(throwsList)), true).assignPrototype(throwsList)
|
||||
}
|
||||
|
||||
private val TYPE_MAP: Map<String, String> = mapOf(
|
||||
JAVA_LANG_BYTE to "byte",
|
||||
JAVA_LANG_SHORT to "short",
|
||||
JAVA_LANG_INTEGER to "int",
|
||||
JAVA_LANG_LONG to "long",
|
||||
JAVA_LANG_FLOAT to "float",
|
||||
JAVA_LANG_DOUBLE to "double",
|
||||
JAVA_LANG_CHARACTER to "char"
|
||||
)
|
||||
|
||||
private fun isConversionNeeded(actual: PsiType?, expected: PsiType?): Boolean {
|
||||
if (actual == null || expected == null) return false
|
||||
|
||||
val expectedStr = expected.getCanonicalText()
|
||||
val actualStr = actual.getCanonicalText()
|
||||
if (expectedStr == actualStr) return false
|
||||
val o1 = expectedStr == TYPE_MAP[actualStr]
|
||||
val o2 = actualStr == TYPE_MAP[expectedStr]
|
||||
return o1 == o2
|
||||
}
|
||||
}
|
||||
|
||||
val NOT_NULL_ANNOTATIONS: Set<String> = setOf("org.jetbrains.annotations.NotNull", "com.sun.istack.internal.NotNull", "javax.annotation.Nonnull")
|
||||
|
||||
@@ -24,23 +24,19 @@ class LocalVariable(
|
||||
private val identifier: Identifier,
|
||||
private val annotations: Annotations,
|
||||
private val modifiers: Modifiers,
|
||||
private val typeCalculator: () -> Type /* we use lazy type calculation for better performance */,
|
||||
private val explicitType: Type?,
|
||||
private val initializer: Expression,
|
||||
private val isVal: Boolean,
|
||||
private val settings: ConverterSettings
|
||||
) : Element() {
|
||||
|
||||
override fun generateCode(builder: CodeBuilder) {
|
||||
builder.append(annotations).append(if (isVal) "val " else "var ")
|
||||
if (initializer.isEmpty) {
|
||||
builder append identifier append ":" append typeCalculator()
|
||||
builder append annotations append (if (isVal) "val " else "var ") append identifier
|
||||
if (explicitType != null) {
|
||||
builder append ":" append explicitType
|
||||
}
|
||||
else {
|
||||
val shouldSpecifyType = settings.specifyLocalVariableTypeByDefault
|
||||
if (shouldSpecifyType)
|
||||
builder append identifier append ":" append typeCalculator() append " = " append initializer
|
||||
else
|
||||
builder append identifier append " = " append initializer
|
||||
if (!initializer.isEmpty) {
|
||||
builder append " = " append initializer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,25 @@ class ElementVisitor(private val converter: Converter) : JavaElementVisitor() {
|
||||
result = LocalVariable(variable.declarationIdentifier(),
|
||||
converter.convertAnnotations(variable),
|
||||
converter.convertModifiers(variable),
|
||||
{ typeConverter.convertVariableType(variable) },
|
||||
explicitType(variable),
|
||||
converter.convertExpression(variable.getInitializer(), variable.getType()),
|
||||
converter.settings.forceLocalVariableImmutability || variable.hasModifierProperty(PsiModifier.FINAL),
|
||||
converter.settings)
|
||||
}
|
||||
|
||||
private fun explicitType(variable: PsiLocalVariable): Type? {
|
||||
fun convertType() = typeConverter.convertVariableType(variable)
|
||||
if (converter.settings.specifyLocalVariableTypeByDefault) return convertType()
|
||||
val initializer = variable.getInitializer()
|
||||
if (initializer == null) return convertType()
|
||||
if (initializer is PsiLiteralExpression && initializer.getType() == PsiType.NULL) return convertType()
|
||||
if (variable.hasModifierProperty(PsiModifier.FINAL) || !variable.hasWriteAccesses(variable.getContainingMethod())) return null
|
||||
val convertedType = convertType()
|
||||
var initializerType = converter.convertedExpressionType(initializer, variable.getType())
|
||||
if (initializerType is ErrorType) return null // do not add explicit type when initializer is not resolved, let user add it if really needed
|
||||
return if (convertedType == initializerType) null else convertedType
|
||||
}
|
||||
|
||||
override fun visitExpressionList(list: PsiExpressionList) {
|
||||
result = ExpressionList(converter.convertExpressions(list.getExpressions()))
|
||||
}
|
||||
|
||||
@@ -86,9 +86,17 @@ class ExpressionVisitor(private val converter: Converter,
|
||||
}
|
||||
|
||||
override fun visitBinaryExpression(expression: PsiBinaryExpression) {
|
||||
val lhs = converter.convertExpression(expression.getLOperand(), expression.getType())
|
||||
val rhs = converter.convertExpression(expression.getROperand(), expression.getType())
|
||||
if (expression.getOperationSign().getTokenType() == JavaTokenType.GTGTGT) {
|
||||
val operandsExpectedType = when (expression.getOperationTokenType()) {
|
||||
JavaTokenType.ANDAND, JavaTokenType.OROR -> PsiType.BOOLEAN
|
||||
|
||||
JavaTokenType.PLUS, JavaTokenType.MINUS, JavaTokenType.ASTERISK,
|
||||
JavaTokenType.DIV, JavaTokenType.PERC, JavaTokenType.LTLT, JavaTokenType.GTGT -> expression.getType()
|
||||
|
||||
else -> null
|
||||
}
|
||||
val lhs = converter.convertExpression(expression.getLOperand(), operandsExpectedType)
|
||||
val rhs = converter.convertExpression(expression.getROperand(), operandsExpectedType)
|
||||
if (expression.getOperationTokenType() == JavaTokenType.GTGTGT) {
|
||||
result = MethodCallExpression.buildNotNull(lhs, "ushr", listOf(rhs))
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1852,11 +1852,46 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("j2k/tests/testData/ast/localVariable"), Pattern.compile("^(.+)\\.java$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("autoBangBang.java")
|
||||
public void testAutoBangBang() throws Exception {
|
||||
doTest("j2k/tests/testData/ast/localVariable/autoBangBang.java");
|
||||
}
|
||||
|
||||
@TestMetadata("conversion.java")
|
||||
public void testConversion() throws Exception {
|
||||
doTest("j2k/tests/testData/ast/localVariable/conversion.java");
|
||||
}
|
||||
|
||||
@TestMetadata("literalConversion.java")
|
||||
public void testLiteralConversion() throws Exception {
|
||||
doTest("j2k/tests/testData/ast/localVariable/literalConversion.java");
|
||||
}
|
||||
|
||||
@TestMetadata("nullIInitializer.java")
|
||||
public void testNullIInitializer() throws Exception {
|
||||
doTest("j2k/tests/testData/ast/localVariable/nullIInitializer.java");
|
||||
}
|
||||
|
||||
@TestMetadata("object.java")
|
||||
public void testObject() throws Exception {
|
||||
doTest("j2k/tests/testData/ast/localVariable/object.java");
|
||||
}
|
||||
|
||||
@TestMetadata("unboxing.java")
|
||||
public void testUnboxing() throws Exception {
|
||||
doTest("j2k/tests/testData/ast/localVariable/unboxing.java");
|
||||
}
|
||||
|
||||
@TestMetadata("valTypeDoNotMatch.java")
|
||||
public void testValTypeDoNotMatch() throws Exception {
|
||||
doTest("j2k/tests/testData/ast/localVariable/valTypeDoNotMatch.java");
|
||||
}
|
||||
|
||||
@TestMetadata("varTypeDoNotMatch.java")
|
||||
public void testVarTypeDoNotMatch() throws Exception {
|
||||
doTest("j2k/tests/testData/ast/localVariable/varTypeDoNotMatch.java");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TestMetadata("j2k/tests/testData/ast/methodCallExpression")
|
||||
|
||||
@@ -2,7 +2,7 @@ import java.util.ArrayList
|
||||
|
||||
class Boxing() {
|
||||
fun test() {
|
||||
val i = 0
|
||||
val i: Int? = 0
|
||||
val n = 0.0.toFloat()
|
||||
i = 1
|
||||
val j = i!!
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//method
|
||||
void foo(Integer i) {
|
||||
int i1 = i;
|
||||
i1++;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo(i: Int) {
|
||||
val i1 = i!!
|
||||
i1++
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//method
|
||||
void foo(byte b) {
|
||||
Integer i = b;
|
||||
if (p) i = 10;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo(b: Byte) {
|
||||
val i = b.toInt()
|
||||
if (p) i = 10
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//method
|
||||
void foo(p: Boolean) {
|
||||
long i = 1;
|
||||
if (p) i = 10;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
val i: Long = 1
|
||||
if (p) i = 10
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
//statement
|
||||
String s = null;
|
||||
@@ -0,0 +1 @@
|
||||
val s: String? = null
|
||||
@@ -0,0 +1,5 @@
|
||||
//method
|
||||
void foo(p: Boolean) {
|
||||
Integer i = 1;
|
||||
if (p) i = null;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
val i: Int? = 1
|
||||
if (p) i = null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
//method
|
||||
void foo(p: Boolean) {
|
||||
Object o = "";
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
val o = ""
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//method
|
||||
void foo(p: Boolean) {
|
||||
Object o = "";
|
||||
if (p) o = null;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
val o: Any? = ""
|
||||
if (p) o = null
|
||||
}
|
||||
@@ -4,7 +4,7 @@ trait I {
|
||||
|
||||
class C() {
|
||||
fun foo(i: I, b: Boolean) {
|
||||
val result = i.getString()
|
||||
val result: String? = i.getString()
|
||||
if (b) result = null
|
||||
if (result != null) {
|
||||
print(result)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
val s = null
|
||||
val s: String? = null
|
||||
if (!s!!.isEmpty()) {
|
||||
}
|
||||
Reference in New Issue
Block a user