Java to Kotlin converter: more smartness about nullability
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user