diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index 750d9ea6dc0..acfffab7be2 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -206,28 +206,22 @@ public class Converter(val project: Project, val settings: ConverterSettings) { return EnumConstant(Identifier(field.getName()!!), getComments(field), modifiers, - convertType(field.getType()), + convertType(field.getType(), Nullability.NotNull), convertElement(field.getArgumentList())) } - val isFinal = field.hasModifierProperty(PsiModifier.FINAL) - var kType = convertVariableType(field) - if (isFinal && field.getInitializer().isDefinitelyNotNull()) { - kType = kType.toNotNullType(); - } - return Field(Identifier(field.getName()!!), getComments(field), modifiers, - kType, + convertVariableType(field), convertExpression(field.getInitializer(), field.getType()), - isFinal, + field.hasModifierProperty(PsiModifier.FINAL), field.countWriteAccesses(field.getContainingClass())) } private fun convertMethod(method: PsiMethod, membersToRemove: MutableSet): Function { methodReturnType = method.getReturnType() - val returnType = convertType(method.getReturnType(), method.isAnnotatedAsNotNull()) + val returnType = convertType(method.getReturnType(), method.nullabilityFromAnnotations()) val modifiers = convertModifiers(method) @@ -448,41 +442,46 @@ public class Converter(val project: Project, val settings: ConverterSettings) { convertType(element.getType())) } - public fun convertType(`type`: PsiType?): Type { + public fun convertType(`type`: PsiType?, nullability: Nullability = Nullability.Default): Type { if (`type` == null) return Type.Empty - return `type`.accept(TypeVisitor(this))!! + val result = `type`.accept(TypeVisitor(this))!! + return when (nullability) { + Nullability.NotNull -> result.toNotNullType() + Nullability.Nullable -> result.toNullableType() + Nullability.Default -> result + } } public fun convertTypes(types: Array): List { return types.map { convertType(it) } } - public fun convertType(`type`: PsiType?, notNull: Boolean): Type { - val result = convertType(`type`) - if (notNull) { - return result.toNotNullType() + public fun convertVariableType(variable: PsiVariable): Type { + var nullability = variable.nullabilityFromAnnotations() + if (nullability == Nullability.Default) { + val initializer = variable.getInitializer() + if (initializer != null && variable.hasModifierProperty(PsiModifier.FINAL)) { + nullability = initializer.nullability() + } } - - return result + return convertType(variable.getType(), nullability) } - public fun convertVariableType(variable: PsiVariable): Type - = convertType(variable.getType(), variable.isAnnotatedAsNotNull()) - private fun convertToNotNullableTypes(types: Array): List - = types.map { convertType(it).toNotNullType() } + = types.map { convertType(it, Nullability.NotNull) } public fun convertParameterList(parameters: PsiParameterList): ParameterList = ParameterList(parameters.getParameters().map { convertParameter(it) }) public fun convertParameter(parameter: PsiParameter, - forceNotNull: Boolean = false, + nullability: Nullability = Nullability.Default, varValModifier: Parameter.VarValModifier = Parameter.VarValModifier.None, modifiers: Collection = listOf()): Parameter { var `type` = convertVariableType(parameter) - if (forceNotNull) { - `type` = `type`.toNotNullType() + when (nullability) { + Nullability.NotNull -> `type` = `type`.toNotNullType() + Nullability.Nullable -> `type` = `type`.toNullableType() } return Parameter(Identifier(parameter.getName()!!), `type`, varValModifier, modifiers) } @@ -573,6 +572,7 @@ public class Converter(val project: Project, val settings: ConverterSettings) { } val NOT_NULL_ANNOTATIONS: Set = setOf("org.jetbrains.annotations.NotNull", "com.sun.istack.internal.NotNull", "javax.annotation.Nonnull") +val NULLABLE_ANNOTATIONS: Set = setOf("org.jetbrains.annotations.Nullable", "com.sun.istack.internal.Nullable", "javax.annotation.Nullable") val PRIMITIVE_TYPE_CONVERSIONS: Map = mapOf( "byte" to BYTE.asString(), diff --git a/j2k/src/org/jetbrains/jet/j2k/Utils.kt b/j2k/src/org/jetbrains/jet/j2k/Utils.kt index 2fee6c8113b..e83722a5fe2 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Utils.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Utils.kt @@ -28,7 +28,7 @@ import com.intellij.psi.PsiModifierListOwner import java.util.ArrayList import com.intellij.psi.util.PsiUtil import com.intellij.psi.PsiThisExpression -import java.util.HashMap +import org.jetbrains.jet.j2k.ast.Nullability fun quoteKeywords(packageName: String): String = packageName.split("\\.").map { Identifier(it).toKotlin() }.makeString(".") @@ -52,13 +52,21 @@ fun findExpressionReferences(element: PsiElement, scope: PsiElement): Collection fun PsiElement.countWriteAccesses(scope: PsiElement?): Int = if (scope != null) findExpressionReferences(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0 -fun PsiModifierListOwner.isAnnotatedAsNotNull(): Boolean - = getModifierList()?.getAnnotations()?.any { NOT_NULL_ANNOTATIONS.contains(it.getQualifiedName()) } ?: false +fun PsiModifierListOwner.nullabilityFromAnnotations(): Nullability { + val annotations = getModifierList()?.getAnnotations() ?: return Nullability.Default + return if (annotations.any { NOT_NULL_ANNOTATIONS.contains(it.getQualifiedName()) }) + Nullability.NotNull + else if (annotations.any { NULLABLE_ANNOTATIONS.contains(it.getQualifiedName()) }) + Nullability.Nullable + else + Nullability.Default +} -fun PsiElement?.isDefinitelyNotNull(): Boolean = when(this) { - is PsiLiteralExpression -> getValue() != null - is PsiNewExpression -> true - else -> false +fun PsiElement?.nullability(): Nullability = when(this) { + is PsiLiteralExpression -> if (getValue() != null) Nullability.NotNull else Nullability.Nullable + is PsiNewExpression -> Nullability.NotNull + //TODO: other cases + else -> Nullability.Default } fun getDefaultInitializer(field: Field): String { diff --git a/j2k/src/org/jetbrains/jet/j2k/ast/Imports.kt b/j2k/src/org/jetbrains/jet/j2k/ast/Imports.kt index 8bbf9dcac9d..09aeb915730 100644 --- a/j2k/src/org/jetbrains/jet/j2k/ast/Imports.kt +++ b/j2k/src/org/jetbrains/jet/j2k/ast/Imports.kt @@ -30,7 +30,7 @@ class Import(val name: String) : Element { class ImportList(val imports: List) : Element { val filteredImports = imports.filter { - !it.name.isEmpty() && it.name !in NOT_NULL_ANNOTATIONS + !it.name.isEmpty() && it.name !in NOT_NULL_ANNOTATIONS && it.name !in NULLABLE_ANNOTATIONS }.filter { // If name is invalid, like with star imports, don't try to filter if (!isValidJavaFqName(it.name)) diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.kt index 06c4a33b64f..44465801fc0 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.kt @@ -25,16 +25,11 @@ class ElementVisitor(public val converter: Converter) : JavaElementVisitor() { protected set override fun visitLocalVariable(variable: PsiLocalVariable) { - var kType = converter.convertVariableType(variable) - val isFinal = variable.hasModifierProperty(PsiModifier.FINAL) - if (isFinal && variable.getInitializer().isDefinitelyNotNull()) { - kType = kType.toNotNullType() - } result = LocalVariable(Identifier(variable.getName()!!), converter.convertModifiers(variable), - kType, + converter.convertVariableType(variable), converter.convertExpression(variable.getInitializer(), variable.getType()), - converter.settings.forceLocalVariableImmutability || isFinal, + converter.settings.forceLocalVariableImmutability || variable.hasModifierProperty(PsiModifier.FINAL), converter.settings) } diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt index bf42627e294..d11d75331a4 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/ExpressionVisitor.kt @@ -183,7 +183,7 @@ open class ExpressionVisitor(protected val converter: Converter, private fun createNewEmptyArrayWithoutInitialization(expression: PsiNewExpression): Expression { return ArrayWithoutInitializationExpression( - converter.convertType(expression.getType(), true), + converter.convertType(expression.getType(), Nullability.NotNull), converter.convertExpressions(expression.getArrayDimensions())) } @@ -216,7 +216,7 @@ open class ExpressionVisitor(protected val converter: Converter, val insideSecondaryConstructor = containingConstructor != null && !containingConstructor.isPrimaryConstructor() val addReceiver = insideSecondaryConstructor && (expression.getReference()?.resolve() as? PsiField)?.getContainingClass() == containingConstructor!!.getContainingClass() - val isNullable = converter.convertType(expression.getType(), expression.isResolvedToNotNull()).isNullable + val isNullable = converter.convertType(expression.getType(), expression.nullability()).isNullable val referencedName = expression.getReferenceName()!! var identifier: Expression = Identifier(referencedName, isNullable) val qualifier = expression.getQualifierExpression() @@ -266,12 +266,12 @@ open class ExpressionVisitor(protected val converter: Converter, result = CallChainExpression(converter.convertExpression(qualifier), identifier) } - private fun PsiReference.isResolvedToNotNull(): Boolean { + private fun PsiReference.nullability(): Nullability { val target = resolve() return when(target) { - is PsiEnumConstant -> true - is PsiModifierListOwner -> target.isAnnotatedAsNotNull() - else -> false + is PsiEnumConstant -> Nullability.NotNull + is PsiModifierListOwner -> target.nullabilityFromAnnotations() + else -> Nullability.Default } } diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt index 70f23a88146..cff016f4a21 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.kt @@ -216,7 +216,7 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() { val catchBlocks = statement.getCatchBlocks() val catchBlockParameters = statement.getCatchBlockParameters() for (i in 0..catchBlocks.size - 1) { - catches.add(CatchStatement(converter.convertParameter(catchBlockParameters[i], true), + catches.add(CatchStatement(converter.convertParameter(catchBlockParameters[i], Nullability.NotNull), converter.convertBlock(catchBlocks[i]))) } result = TryStatement(converter.convertBlock(statement.getTryBlock()), @@ -226,7 +226,7 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() { override fun visitWhileStatement(statement: PsiWhileStatement) { var condition: PsiExpression? = statement.getCondition() val expression: Expression = (if (condition != null && condition?.getType() != null) - this.converter.convertExpression(condition, condition?.getType()) + converter.convertExpression(condition, condition?.getType()) else converter.convertExpression(condition)) result = WhileStatement(expression, converter.convertStatement(statement.getBody())) @@ -236,7 +236,7 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() { val returnValue = statement.getReturnValue() val methodReturnType = converter.methodReturnType val expression = (if (returnValue != null && methodReturnType != null) - this.converter.convertExpression(returnValue, methodReturnType) + converter.convertExpression(returnValue, methodReturnType) else converter.convertExpression(returnValue)) result = ReturnStatement(expression) 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 15596129836..c8097b7fd64 100644 --- a/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java +++ b/j2k/tests/test/org/jetbrains/jet/j2k/test/JavaToKotlinConverterTestGenerated.java @@ -16,17 +16,14 @@ package org.jetbrains.jet.j2k.test; -import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestSuite; - -import java.io.File; -import java.util.regex.Pattern; import org.jetbrains.jet.JetTestUtils; import org.jetbrains.jet.test.InnerTestClasses; import org.jetbrains.jet.test.TestMetadata; -import org.jetbrains.jet.j2k.test.AbstractJavaToKotlinConverterTest; +import java.io.File; +import java.util.regex.Pattern; /** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @@ -53,6 +50,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv doTest("j2k/tests/testData/ast/annotations/jetbrainsNotNullChainExpr.java"); } + @TestMetadata("jetbrainsNullable.java") + public void testJetbrainsNullable() throws Exception { + doTest("j2k/tests/testData/ast/annotations/jetbrainsNullable.java"); + } + } @TestMetadata("j2k/tests/testData/ast/anonymousBlock") diff --git a/j2k/tests/testData/ast/annotations/jetbrainsNullable.java b/j2k/tests/testData/ast/annotations/jetbrainsNullable.java new file mode 100644 index 00000000000..7a270b2b76d --- /dev/null +++ b/j2k/tests/testData/ast/annotations/jetbrainsNullable.java @@ -0,0 +1,31 @@ +//file +// !specifyLocalVariableTypeByDefault: true +package test; + +import org.jetbrains.annotations.Nullable; + +public class Test { + @Nullable String myStr = "String2"; + + public Test(@Nullable String str) { + myStr = str; + } + + public void sout(@Nullable String str) { + System.out.println(str); + } + + @Nullable + public String dummy(@Nullable String str) { + return str; + } + + public void test() { + sout("String"); + @Nullable String test = "String2"; + sout(test); + sout(dummy(test)); + + new Test(test); + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/annotations/jetbrainsNullable.kt b/j2k/tests/testData/ast/annotations/jetbrainsNullable.kt new file mode 100644 index 00000000000..4088d525ea0 --- /dev/null +++ b/j2k/tests/testData/ast/annotations/jetbrainsNullable.kt @@ -0,0 +1,27 @@ +// !specifyLocalVariableTypeByDefault: true +package test + +public class Test(str: String?) { + var myStr: String? = "String2" + + public fun sout(str: String?) { + System.out.println(str) + } + + public fun dummy(str: String?): String? { + return str + } + + public fun test() { + sout("String") + val test: String? = "String2" + sout(test) + sout(dummy(test)) + + Test(test) + } + + { + myStr = str + } +} \ No newline at end of file diff --git a/j2k/tests/testData/ast/packageStatement/orgJetbrainsJetJ2kIn.kt b/j2k/tests/testData/ast/packageStatement/orgJetbrainsJetJ2kIn.kt index 31190c76a54..48886e8330d 100644 --- a/j2k/tests/testData/ast/packageStatement/orgJetbrainsJetJ2kIn.kt +++ b/j2k/tests/testData/ast/packageStatement/orgJetbrainsJetJ2kIn.kt @@ -1,5 +1,3 @@ package org.jetbrains.jet.j2k.`in` -import org.jetbrains.annotations.Nullable - public class Converter() \ No newline at end of file