diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index 9fa056c9759..9cc2ab96d22 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -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): Function { diff --git a/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt b/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt index 6fb8cd154e3..83cb378260e 100644 --- a/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt @@ -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 + } + } } diff --git a/j2k/src/org/jetbrains/jet/j2k/Utils.kt b/j2k/src/org/jetbrains/jet/j2k/Utils.kt index f2a968372f5..4931c2dbd9d 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Utils.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Utils.kt @@ -46,6 +46,9 @@ fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection, `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()) { diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/Field.kt b/j2k/src/org/jetbrains/jet/j2k/ast/Field.kt index c14dd36538a..ddd6248345f 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/Field.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/Field.kt @@ -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() } diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt index cff016f4a21..ff380df91f4 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt @@ -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() diff --git a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java index abcbf24944e..12d5fcbff9d 100644 --- a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java +++ b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java @@ -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"); diff --git a/j2k/tests/testData/ast/callChainExpression/libraryMethodCallFromInstance-settings.kt b/j2k/tests/testData/ast/callChainExpression/libraryMethodCallFromInstance-settings.kt index fd94784d473..05b3060358c 100644 --- a/j2k/tests/testData/ast/callChainExpression/libraryMethodCallFromInstance-settings.kt +++ b/j2k/tests/testData/ast/callChainExpression/libraryMethodCallFromInstance-settings.kt @@ -11,7 +11,7 @@ class Library() { class User() { fun main() { - val lib: Library? = Library() + val lib: Library = Library() lib?.call() lib?.getString()?.isEmpty() diff --git a/j2k/tests/testData/ast/nullability/FieldComparedWithNull.java b/j2k/tests/testData/ast/nullability/FieldComparedWithNull.java index b19c911c486..2fd9ed055ac 100644 --- a/j2k/tests/testData/ast/nullability/FieldComparedWithNull.java +++ b/j2k/tests/testData/ast/nullability/FieldComparedWithNull.java @@ -1,6 +1,6 @@ //file class C { - private String s = ""; + private String s = x(); void foo() { if (s == null) { diff --git a/j2k/tests/testData/ast/nullability/FieldComparedWithNull.kt b/j2k/tests/testData/ast/nullability/FieldComparedWithNull.kt index 10d88a97c62..7b8d014387f 100644 --- a/j2k/tests/testData/ast/nullability/FieldComparedWithNull.kt +++ b/j2k/tests/testData/ast/nullability/FieldComparedWithNull.kt @@ -1,5 +1,5 @@ class C() { - private var s: String? = "" + private var s: String? = x() fun foo() { if (s == null) { diff --git a/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull2.java b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull2.java new file mode 100644 index 00000000000..0d83ae7d514 --- /dev/null +++ b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull2.java @@ -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); + } + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull2.kt b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull2.kt new file mode 100644 index 00000000000..11b86f8b960 --- /dev/null +++ b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull2.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull3.java b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull3.java new file mode 100644 index 00000000000..7da404b1584 --- /dev/null +++ b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull3.java @@ -0,0 +1,13 @@ +//file +interface I { + String getString(); +} + +class C { + void foo(I i) { + String result = i.getString(); + if (result != null) { + print(result); + } + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull3.kt b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull3.kt new file mode 100644 index 00000000000..11b86f8b960 --- /dev/null +++ b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull3.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull4.java b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull4.java new file mode 100644 index 00000000000..f03bf624786 --- /dev/null +++ b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull4.java @@ -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); + } + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull4.kt b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull4.kt new file mode 100644 index 00000000000..0e3bcc4f627 --- /dev/null +++ b/j2k/tests/testData/ast/nullability/MethodResultComparedWithNull4.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/nullability/MethodResultInitializesNullableField.java b/j2k/tests/testData/ast/nullability/MethodResultInitializesNullableField.java new file mode 100644 index 00000000000..0e72dc9a50f --- /dev/null +++ b/j2k/tests/testData/ast/nullability/MethodResultInitializesNullableField.java @@ -0,0 +1,8 @@ +//file +import org.jetbrains.annotations.Nullable; + +class C { + @Nullable private final String string = getString(); + + static String getString() { return x(); } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/nullability/MethodResultInitializesNullableField.kt b/j2k/tests/testData/ast/nullability/MethodResultInitializesNullableField.kt new file mode 100644 index 00000000000..3bde9e28c4b --- /dev/null +++ b/j2k/tests/testData/ast/nullability/MethodResultInitializesNullableField.kt @@ -0,0 +1,10 @@ +class C() { + private val string: String? = getString() + + class object { + + fun getString(): String? { + return x() + } + } +} \ No newline at end of file