Java to Kotlin converter: more smartness about nullability

This commit is contained in:
Valentin Kipyatkov
2014-06-06 21:26:25 +04:00
parent e947ad7294
commit 0695f6b3d7
18 changed files with 143 additions and 10 deletions
+1 -1
View File
@@ -218,7 +218,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
typeConverter.convertVariableType(field),
convertExpression(field.getInitializer(), field.getType()),
field.hasModifierProperty(PsiModifier.FINAL),
field.countWriteAccesses(field.getContainingClass()))
field.hasWriteAccesses(field.getContainingClass()))
}
private fun convertMethod(method: PsiMethod, membersToRemove: MutableSet<PsiMember>): Function {
+14 -1
View File
@@ -44,7 +44,7 @@ class TypeConverter(val settings: ConverterSettings) {
val initializer = variable.getInitializer()
if (initializer != null) {
val initializerNullability = initializer.nullability()
if (variable.hasModifierProperty(PsiModifier.FINAL)) { //TODO: replace check for final modifier with effective final
if (variable.isEffectivelyFinal()) {
nullability = initializerNullability
}
else if (initializerNullability == Nullability.Nullable) { // if variable is not final then non-nullability of initializer does not mean that variable is non-null
@@ -159,6 +159,19 @@ class TypeConverter(val settings: ConverterSettings) {
return otherOperand is PsiLiteralExpression && otherOperand.getType() == PsiType.NULL
}
}
else if (parent is PsiVariable && usage == parent.getInitializer() && parent.isEffectivelyFinal()) {
return convertVariableType(parent).isNullable
}
return false
}
private fun PsiVariable.isEffectivelyFinal(): Boolean {
if (hasModifierProperty(PsiModifier.FINAL)) return true
return when(this) {
is PsiLocalVariable -> !hasWriteAccesses(getContainingMethod())
is PsiField -> if (hasModifierProperty(PsiModifier.PRIVATE)) !hasWriteAccesses(getContainingClass()) else false
is PsiParameter -> true
else -> false
}
}
}
+3
View File
@@ -46,6 +46,9 @@ fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodC
fun PsiVariable.countWriteAccesses(scope: PsiElement?): Int
= if (scope != null) findVariableUsages(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0
fun PsiVariable.hasWriteAccesses(scope: PsiElement?): Boolean
= if (scope != null) findVariableUsages(this, scope).any { PsiUtil.isAccessedForWriting(it) } else false
fun PsiModifierListOwner.nullabilityFromAnnotations(): Nullability {
val annotations = getModifierList()?.getAnnotations() ?: return Nullability.Default
return if (annotations.any { NOT_NULL_ANNOTATIONS.contains(it.getQualifiedName()) })
@@ -22,7 +22,7 @@ class EnumConstant(
modifiers: Set<Modifier>,
`type`: Type,
params: Element
) : Field(identifier, members, modifiers, `type`.toNotNullType(), params, true, 0) {
) : Field(identifier, members, modifiers, `type`.toNotNullType(), params, true, false) {
override fun toKotlin(): String {
if (initializer.toKotlin().isEmpty()) {
+2 -2
View File
@@ -26,13 +26,13 @@ open class Field(
val `type`: Type,
val initializer: Element,
val isVal: Boolean,
val writingAccesses: Int
private val hasWriteAccesses: Boolean
) : Member(comments, modifiers) {
override fun toKotlin(): String {
val declaration = commentsToKotlin() + modifiersToKotlin() + (if (isVal) "val " else "var ") + identifier.toKotlin() + " : " + `type`.toKotlin()
return if (initializer.isEmpty)
declaration + (if (isVal && !isStatic() && writingAccesses != 0) "" else " = " + getDefaultInitializer(this))
declaration + (if (isVal && !isStatic() && hasWriteAccesses) "" else " = " + getDefaultInitializer(this))
else
declaration + " = " + initializer.toKotlin()
}
@@ -21,6 +21,7 @@ import org.jetbrains.jet.j2k.Converter
import org.jetbrains.jet.j2k.ast.*
import org.jetbrains.jet.j2k.countWriteAccesses
import java.util.ArrayList
import org.jetbrains.jet.j2k.hasWriteAccesses
class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
public var result: Statement = Statement.Empty
@@ -82,8 +83,8 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
val loopVar = initialization?.getFirstChild() as? PsiLocalVariable
val onceWritableIterator = loopVar != null
&& loopVar.countWriteAccesses(body) == 0
&& loopVar.countWriteAccesses(condition) == 0
&& !loopVar.hasWriteAccesses(body)
&& !loopVar.hasWriteAccesses(condition)
&& loopVar.countWriteAccesses(update) == 1
val operationTokenType = (condition as? PsiBinaryExpression)?.getOperationTokenType()
@@ -1926,6 +1926,26 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
doTest("j2k/tests/testData/ast/nullability/MethodResultComparedWithNull.java");
}
@TestMetadata("MethodResultComparedWithNull2.java")
public void testMethodResultComparedWithNull2() throws Exception {
doTest("j2k/tests/testData/ast/nullability/MethodResultComparedWithNull2.java");
}
@TestMetadata("MethodResultComparedWithNull3.java")
public void testMethodResultComparedWithNull3() throws Exception {
doTest("j2k/tests/testData/ast/nullability/MethodResultComparedWithNull3.java");
}
@TestMetadata("MethodResultComparedWithNull4.java")
public void testMethodResultComparedWithNull4() throws Exception {
doTest("j2k/tests/testData/ast/nullability/MethodResultComparedWithNull4.java");
}
@TestMetadata("MethodResultInitializesNullableField.java")
public void testMethodResultInitializesNullableField() throws Exception {
doTest("j2k/tests/testData/ast/nullability/MethodResultInitializesNullableField.java");
}
@TestMetadata("MethodReturnsNull.java")
public void testMethodReturnsNull() throws Exception {
doTest("j2k/tests/testData/ast/nullability/MethodReturnsNull.java");
@@ -11,7 +11,7 @@ class Library() {
class User() {
fun main() {
val lib: Library? = Library()
val lib: Library = Library()
lib?.call()
lib?.getString()?.isEmpty()
@@ -1,6 +1,6 @@
//file
class C {
private String s = "";
private String s = x();
void foo() {
if (s == null) {
@@ -1,5 +1,5 @@
class C() {
private var s: String? = ""
private var s: String? = x()
fun foo() {
if (s == null) {
@@ -0,0 +1,13 @@
//file
interface I {
String getString();
}
class C {
void foo(I i) {
final String result = i.getString();
if (result != null) {
print(result);
}
}
}
@@ -0,0 +1,12 @@
trait I {
public fun getString(): String?
}
class C() {
fun foo(i: I) {
val result = i.getString()
if (result != null) {
print(result)
}
}
}
@@ -0,0 +1,13 @@
//file
interface I {
String getString();
}
class C {
void foo(I i) {
String result = i.getString();
if (result != null) {
print(result);
}
}
}
@@ -0,0 +1,12 @@
trait I {
public fun getString(): String?
}
class C() {
fun foo(i: I) {
val result = i.getString()
if (result != null) {
print(result)
}
}
}
@@ -0,0 +1,14 @@
//file
interface I {
String getString();
}
class C {
void foo(I i, boolean b) {
String result = i.getString();
if (b) result = null;
if (result != null) {
print(result);
}
}
}
@@ -0,0 +1,14 @@
trait I {
public fun getString(): String
}
class C() {
fun foo(i: I, b: Boolean) {
val result = i.getString()
if (b)
result = null
if (result != null) {
print(result)
}
}
}
@@ -0,0 +1,8 @@
//file
import org.jetbrains.annotations.Nullable;
class C {
@Nullable private final String string = getString();
static String getString() { return x(); }
}
@@ -0,0 +1,10 @@
class C() {
private val string: String? = getString()
class object {
fun getString(): String? {
return x()
}
}
}