Java to Kotlin converter: more correct logic for specifying field type

This commit is contained in:
Valentin Kipyatkov
2014-06-24 19:48:31 +04:00
parent 6e473506fc
commit 63ebfe2c03
12 changed files with 67 additions and 64 deletions
+21 -15
View File
@@ -251,8 +251,7 @@ public class Converter private(val project: Project, val settings: ConverterSett
Modifiers.Empty, Modifiers.Empty,
null, null,
initializer, initializer,
true, true).assignNoPrototype()
settings).assignNoPrototype()
newStatements.add(0, DeclarationStatement(listOf(localVar)).assignNoPrototype()) newStatements.add(0, DeclarationStatement(listOf(localVar)).assignNoPrototype())
constructor.block = Block(newStatements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype() constructor.block = Block(newStatements, LBrace().assignNoPrototype(), RBrace().assignNoPrototype()).assignNoPrototype()
} }
@@ -286,30 +285,37 @@ public class Converter private(val project: Project, val settings: ConverterSett
convertElement(field.getArgumentList())) convertElement(field.getArgumentList()))
} }
else { else {
val convertedType = typeConverter.convertVariableType(field)
val isVal = isVal(field) val isVal = isVal(field)
val typeToDeclare = variableTypeToDeclare(field,
settings.specifyFieldTypeByDefault || modifiers.isPublic || modifiers.isProtected,
isVal && modifiers.isPrivate)
Field(name, Field(name,
annotations, annotations,
modifiers, modifiers,
convertedType, typeToDeclare ?: typeConverter.convertVariableType(field),
convertExpression(field.getInitializer(), field.getType()), convertExpression(field.getInitializer(), field.getType()),
isVal, isVal,
shouldDeclareType(field, isVal, convertedType), typeToDeclare != null,
field.hasWriteAccesses(field.getContainingClass())) field.hasWriteAccesses(field.getContainingClass()))
} }
return converted.assignPrototype(field) return converted.assignPrototype(field)
} }
private fun shouldDeclareType(field: PsiField, isVal: Boolean, convertedType: Type): Boolean { fun variableTypeToDeclare(variable: PsiVariable, specifyAlways: Boolean, canChangeType: Boolean): Type? {
if (settings.specifyFieldTypeByDefault) return true fun convertType() = typeConverter.convertVariableType(variable)
val initializer = field.getInitializer() ?: return true
fun typeMatches() = convertedType == typeConverter.convertExpressionType(initializer) if (specifyAlways) return convertType()
return if (field.hasModifierProperty(PsiModifier.PRIVATE))
!isVal && !typeMatches() val initializer = variable.getInitializer()
else if (field.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) if (initializer == null) return convertType()
!typeMatches() if (initializer is PsiLiteralExpression && initializer.getType() == PsiType.NULL) return convertType()
else
true if (canChangeType) return null
val convertedType = convertType()
var initializerType = 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
} }
private fun isVal(field: PsiField): Boolean { private fun isVal(field: PsiField): Boolean {
+8 -28
View File
@@ -92,7 +92,7 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver
if (nullability == Nullability.Default) { if (nullability == Nullability.Default) {
val initializer = variable.getInitializer() val initializer = variable.getInitializer()
if (initializer != null) { if (initializer != null) {
val initializerNullability = initializer.nullability(false) val initializerNullability = initializer.nullability()
if (variable.isEffectivelyFinal()) { if (variable.isEffectivelyFinal()) {
nullability = initializerNullability nullability = initializerNullability
} }
@@ -133,7 +133,7 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver
for (call in findMethodCalls(method, scope)) { for (call in findMethodCalls(method, scope)) {
val args = call.getArgumentList().getExpressions() val args = call.getArgumentList().getExpressions()
if (args.size == parameters.size) { if (args.size == parameters.size) {
if (args[parameterIndex].nullability(false) == Nullability.Nullable) { if (args[parameterIndex].nullability() == Nullability.Nullable) {
nullability = Nullability.Nullable nullability = Nullability.Nullable
break break
} }
@@ -172,7 +172,7 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver
if (nullability == Nullability.Default) { if (nullability == Nullability.Default) {
method.getBody()?.accept(object: JavaRecursiveElementVisitor() { method.getBody()?.accept(object: JavaRecursiveElementVisitor() {
override fun visitReturnStatement(statement: PsiReturnStatement) { override fun visitReturnStatement(statement: PsiReturnStatement) {
if (statement.getReturnValue()?.nullability(false) == Nullability.Nullable) { if (statement.getReturnValue()?.nullability() == Nullability.Nullable) {
nullability = Nullability.Nullable nullability = Nullability.Nullable
} }
} }
@@ -195,10 +195,6 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver
return nullability return nullability
} }
public fun convertExpressionType(expression: PsiExpression): Type {
return convertType(expression.getType(), expression.nullability(true))
}
private fun searchScope(element: PsiElement): PsiElement? { private fun searchScope(element: PsiElement): PsiElement? {
return when(element) { return when(element) {
is PsiParameter -> element.getDeclarationScope() is PsiParameter -> element.getDeclarationScope()
@@ -209,38 +205,22 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver
} }
} }
private fun PsiExpression.nullability(useDeclarationsNullability: Boolean): Nullability { private fun PsiExpression.nullability(): Nullability {
return when (this) { return when (this) {
is PsiLiteralExpression -> if (getType() != PsiType.NULL) Nullability.NotNull else Nullability.Nullable is PsiLiteralExpression -> if (getType() != PsiType.NULL) Nullability.NotNull else Nullability.Nullable
is PsiNewExpression -> Nullability.NotNull is PsiNewExpression -> Nullability.NotNull
is PsiConditionalExpression -> { is PsiConditionalExpression -> {
val nullability1 = getThenExpression()?.nullability(useDeclarationsNullability) val nullability1 = getThenExpression()?.nullability()
if (nullability1 == Nullability.Nullable) return Nullability.Nullable if (nullability1 == Nullability.Nullable) return Nullability.Nullable
val nullability2 = getElseExpression()?.nullability(useDeclarationsNullability) val nullability2 = getElseExpression()?.nullability()
if (nullability2 == Nullability.Nullable) return Nullability.Nullable if (nullability2 == Nullability.Nullable) return Nullability.Nullable
if (nullability1 == Nullability.NotNull && nullability2 == Nullability.NotNull) return Nullability.NotNull if (nullability1 == Nullability.NotNull && nullability2 == Nullability.NotNull) return Nullability.NotNull
Nullability.Default Nullability.Default
} }
is PsiParenthesizedExpression -> getExpression()?.nullability(useDeclarationsNullability) ?: Nullability.Default is PsiParenthesizedExpression -> getExpression()?.nullability() ?: Nullability.Default
is PsiMethodCallExpression -> if (useDeclarationsNullability) {
val method = resolveMethod()
if (method != null) methodNullability(method) else Nullability.Default
}
else {
Nullability.Default
}
is PsiReferenceExpression -> if (useDeclarationsNullability) {
val variable = resolve() as? PsiVariable
if (variable != null) variableNullability(variable) else Nullability.Default
}
else {
Nullability.Default
}
//TODO: some other cases //TODO: some other cases
@@ -252,7 +232,7 @@ class TypeConverter(val settings: ConverterSettings, val conversionScope: Conver
private fun isNullableFromUsage(usage: PsiExpression): Boolean { private fun isNullableFromUsage(usage: PsiExpression): Boolean {
val parent = usage.getParent() ?: return false val parent = usage.getParent() ?: return false
if (parent is PsiAssignmentExpression && parent.getOperationTokenType() == JavaTokenType.EQ && usage == parent.getLExpression()) { if (parent is PsiAssignmentExpression && parent.getOperationTokenType() == JavaTokenType.EQ && usage == parent.getLExpression()) {
return parent.getRExpression()?.nullability(false) == Nullability.Nullable return parent.getRExpression()?.nullability() == Nullability.Nullable
} }
else if (parent is PsiBinaryExpression) { else if (parent is PsiBinaryExpression) {
val operationType = parent.getOperationTokenType() val operationType = parent.getOperationTokenType()
@@ -26,8 +26,7 @@ class LocalVariable(
private val modifiers: Modifiers, private val modifiers: Modifiers,
private val explicitType: Type?, private val explicitType: Type?,
private val initializer: Expression, private val initializer: Expression,
private val isVal: Boolean, private val isVal: Boolean
private val settings: ConverterSettings
) : Element() { ) : Element() {
override fun generateCode(builder: CodeBuilder) { override fun generateCode(builder: CodeBuilder) {
@@ -27,26 +27,13 @@ class ElementVisitor(private val converter: Converter) : JavaElementVisitor() {
protected set protected set
override fun visitLocalVariable(variable: PsiLocalVariable) { override fun visitLocalVariable(variable: PsiLocalVariable) {
val isVal = variable.hasModifierProperty(PsiModifier.FINAL) || !variable.hasWriteAccesses(variable.getContainingMethod())
result = LocalVariable(variable.declarationIdentifier(), result = LocalVariable(variable.declarationIdentifier(),
converter.convertAnnotations(variable), converter.convertAnnotations(variable),
converter.convertModifiers(variable), converter.convertModifiers(variable),
explicitType(variable), converter.variableTypeToDeclare(variable, converter.settings.specifyLocalVariableTypeByDefault, isVal),
converter.convertExpression(variable.getInitializer(), variable.getType()), converter.convertExpression(variable.getInitializer(), variable.getType()),
converter.settings.forceLocalVariableImmutability || variable.hasModifierProperty(PsiModifier.FINAL), converter.settings.forceLocalVariableImmutability || isVal)
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) { override fun visitExpressionList(list: PsiExpressionList) {
@@ -1074,6 +1074,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
doTest("j2k/tests/testData/ast/field/classChildExtendsBase.java"); doTest("j2k/tests/testData/ast/field/classChildExtendsBase.java");
} }
@TestMetadata("conversion.java")
public void testConversion() throws Exception {
doTest("j2k/tests/testData/ast/field/conversion.java");
}
@TestMetadata("internalField.java") @TestMetadata("internalField.java")
public void testInternalField() throws Exception { public void testInternalField() throws Exception {
doTest("j2k/tests/testData/ast/field/internalField.java"); doTest("j2k/tests/testData/ast/field/internalField.java");
@@ -11,7 +11,7 @@ class Foo {
class Bar { class Bar {
@NotNull @NotNull
Foo fooNotNull = Foo(); Foo fooNotNull = new Foo();
Foo fooNullable = null; Foo fooNullable = null;
} }
@@ -8,7 +8,7 @@ class Foo() {
} }
class Bar() { class Bar() {
var fooNotNull: Foo = Foo() var fooNotNull = Foo()
var fooNullable: Foo? = null var fooNullable: Foo? = null
} }
@@ -0,0 +1,10 @@
//file
class A {
private Integer i = getByte();
static byte getByte() { return 0; }
void foo() {
i = 10;
}
}
@@ -0,0 +1,14 @@
class A() {
private var i = getByte().toInt()
fun foo() {
i = 10
}
class object {
fun getByte(): Byte {
return 0
}
}
}
@@ -11,5 +11,5 @@ class One() {
} }
class Test() { class Test() {
var b: Byte = One.myContainer.myInt.toByte() var b = One.myContainer.myInt.toByte()
} }
@@ -2,6 +2,7 @@
// !forceLocalVariableImmutability: false // !forceLocalVariableImmutability: false
public void foo() { public void foo() {
int i = 1; int i = 1;
i++;
String s = ""; String s = "";
s += "a"; s += "a";
} }
@@ -1,6 +1,7 @@
// !forceLocalVariableImmutability: false // !forceLocalVariableImmutability: false
public fun foo() { public fun foo() {
var i = 1 var i = 1
i++
var s = "" var s = ""
s += "a" s += "a"
} }