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),
|
typeConverter.convertVariableType(field),
|
||||||
convertExpression(field.getInitializer(), field.getType()),
|
convertExpression(field.getInitializer(), field.getType()),
|
||||||
field.hasModifierProperty(PsiModifier.FINAL),
|
field.hasModifierProperty(PsiModifier.FINAL),
|
||||||
field.countWriteAccesses(field.getContainingClass()))
|
field.hasWriteAccesses(field.getContainingClass()))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertMethod(method: PsiMethod, membersToRemove: MutableSet<PsiMember>): Function {
|
private fun convertMethod(method: PsiMethod, membersToRemove: MutableSet<PsiMember>): Function {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TypeConverter(val settings: ConverterSettings) {
|
|||||||
val initializer = variable.getInitializer()
|
val initializer = variable.getInitializer()
|
||||||
if (initializer != null) {
|
if (initializer != null) {
|
||||||
val initializerNullability = initializer.nullability()
|
val initializerNullability = initializer.nullability()
|
||||||
if (variable.hasModifierProperty(PsiModifier.FINAL)) { //TODO: replace check for final modifier with effective final
|
if (variable.isEffectivelyFinal()) {
|
||||||
nullability = initializerNullability
|
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
|
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
|
return otherOperand is PsiLiteralExpression && otherOperand.getType() == PsiType.NULL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (parent is PsiVariable && usage == parent.getInitializer() && parent.isEffectivelyFinal()) {
|
||||||
|
return convertVariableType(parent).isNullable
|
||||||
|
}
|
||||||
return false
|
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
|
fun PsiVariable.countWriteAccesses(scope: PsiElement?): Int
|
||||||
= if (scope != null) findVariableUsages(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0
|
= 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 {
|
fun PsiModifierListOwner.nullabilityFromAnnotations(): Nullability {
|
||||||
val annotations = getModifierList()?.getAnnotations() ?: return Nullability.Default
|
val annotations = getModifierList()?.getAnnotations() ?: return Nullability.Default
|
||||||
return if (annotations.any { NOT_NULL_ANNOTATIONS.contains(it.getQualifiedName()) })
|
return if (annotations.any { NOT_NULL_ANNOTATIONS.contains(it.getQualifiedName()) })
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class EnumConstant(
|
|||||||
modifiers: Set<Modifier>,
|
modifiers: Set<Modifier>,
|
||||||
`type`: Type,
|
`type`: Type,
|
||||||
params: Element
|
params: Element
|
||||||
) : Field(identifier, members, modifiers, `type`.toNotNullType(), params, true, 0) {
|
) : Field(identifier, members, modifiers, `type`.toNotNullType(), params, true, false) {
|
||||||
|
|
||||||
override fun toKotlin(): String {
|
override fun toKotlin(): String {
|
||||||
if (initializer.toKotlin().isEmpty()) {
|
if (initializer.toKotlin().isEmpty()) {
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ open class Field(
|
|||||||
val `type`: Type,
|
val `type`: Type,
|
||||||
val initializer: Element,
|
val initializer: Element,
|
||||||
val isVal: Boolean,
|
val isVal: Boolean,
|
||||||
val writingAccesses: Int
|
private val hasWriteAccesses: Boolean
|
||||||
) : Member(comments, modifiers) {
|
) : Member(comments, modifiers) {
|
||||||
|
|
||||||
override fun toKotlin(): String {
|
override fun toKotlin(): String {
|
||||||
val declaration = commentsToKotlin() + modifiersToKotlin() + (if (isVal) "val " else "var ") + identifier.toKotlin() + " : " + `type`.toKotlin()
|
val declaration = commentsToKotlin() + modifiersToKotlin() + (if (isVal) "val " else "var ") + identifier.toKotlin() + " : " + `type`.toKotlin()
|
||||||
return if (initializer.isEmpty)
|
return if (initializer.isEmpty)
|
||||||
declaration + (if (isVal && !isStatic() && writingAccesses != 0) "" else " = " + getDefaultInitializer(this))
|
declaration + (if (isVal && !isStatic() && hasWriteAccesses) "" else " = " + getDefaultInitializer(this))
|
||||||
else
|
else
|
||||||
declaration + " = " + initializer.toKotlin()
|
declaration + " = " + initializer.toKotlin()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.jetbrains.jet.j2k.Converter
|
|||||||
import org.jetbrains.jet.j2k.ast.*
|
import org.jetbrains.jet.j2k.ast.*
|
||||||
import org.jetbrains.jet.j2k.countWriteAccesses
|
import org.jetbrains.jet.j2k.countWriteAccesses
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
import org.jetbrains.jet.j2k.hasWriteAccesses
|
||||||
|
|
||||||
class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
||||||
public var result: Statement = Statement.Empty
|
public var result: Statement = Statement.Empty
|
||||||
@@ -82,8 +83,8 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
|||||||
|
|
||||||
val loopVar = initialization?.getFirstChild() as? PsiLocalVariable
|
val loopVar = initialization?.getFirstChild() as? PsiLocalVariable
|
||||||
val onceWritableIterator = loopVar != null
|
val onceWritableIterator = loopVar != null
|
||||||
&& loopVar.countWriteAccesses(body) == 0
|
&& !loopVar.hasWriteAccesses(body)
|
||||||
&& loopVar.countWriteAccesses(condition) == 0
|
&& !loopVar.hasWriteAccesses(condition)
|
||||||
&& loopVar.countWriteAccesses(update) == 1
|
&& loopVar.countWriteAccesses(update) == 1
|
||||||
|
|
||||||
val operationTokenType = (condition as? PsiBinaryExpression)?.getOperationTokenType()
|
val operationTokenType = (condition as? PsiBinaryExpression)?.getOperationTokenType()
|
||||||
|
|||||||
@@ -1926,6 +1926,26 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
|
|||||||
doTest("j2k/tests/testData/ast/nullability/MethodResultComparedWithNull.java");
|
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")
|
@TestMetadata("MethodReturnsNull.java")
|
||||||
public void testMethodReturnsNull() throws Exception {
|
public void testMethodReturnsNull() throws Exception {
|
||||||
doTest("j2k/tests/testData/ast/nullability/MethodReturnsNull.java");
|
doTest("j2k/tests/testData/ast/nullability/MethodReturnsNull.java");
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ class Library() {
|
|||||||
|
|
||||||
class User() {
|
class User() {
|
||||||
fun main() {
|
fun main() {
|
||||||
val lib: Library? = Library()
|
val lib: Library = Library()
|
||||||
lib?.call()
|
lib?.call()
|
||||||
lib?.getString()?.isEmpty()
|
lib?.getString()?.isEmpty()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//file
|
//file
|
||||||
class C {
|
class C {
|
||||||
private String s = "";
|
private String s = x();
|
||||||
|
|
||||||
void foo() {
|
void foo() {
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class C() {
|
class C() {
|
||||||
private var s: String? = ""
|
private var s: String? = x()
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
if (s == null) {
|
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