KT-4418 Converter from java should honor "@Nullable" annotations
#KT-4418 Fixed
This commit is contained in:
@@ -206,28 +206,22 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
|
|||||||
return EnumConstant(Identifier(field.getName()!!),
|
return EnumConstant(Identifier(field.getName()!!),
|
||||||
getComments(field),
|
getComments(field),
|
||||||
modifiers,
|
modifiers,
|
||||||
convertType(field.getType()),
|
convertType(field.getType(), Nullability.NotNull),
|
||||||
convertElement(field.getArgumentList()))
|
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()!!),
|
return Field(Identifier(field.getName()!!),
|
||||||
getComments(field),
|
getComments(field),
|
||||||
modifiers,
|
modifiers,
|
||||||
kType,
|
convertVariableType(field),
|
||||||
convertExpression(field.getInitializer(), field.getType()),
|
convertExpression(field.getInitializer(), field.getType()),
|
||||||
isFinal,
|
field.hasModifierProperty(PsiModifier.FINAL),
|
||||||
field.countWriteAccesses(field.getContainingClass()))
|
field.countWriteAccesses(field.getContainingClass()))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertMethod(method: PsiMethod, membersToRemove: MutableSet<PsiMember>): Function {
|
private fun convertMethod(method: PsiMethod, membersToRemove: MutableSet<PsiMember>): Function {
|
||||||
methodReturnType = method.getReturnType()
|
methodReturnType = method.getReturnType()
|
||||||
val returnType = convertType(method.getReturnType(), method.isAnnotatedAsNotNull())
|
val returnType = convertType(method.getReturnType(), method.nullabilityFromAnnotations())
|
||||||
|
|
||||||
val modifiers = convertModifiers(method)
|
val modifiers = convertModifiers(method)
|
||||||
|
|
||||||
@@ -448,41 +442,46 @@ public class Converter(val project: Project, val settings: ConverterSettings) {
|
|||||||
convertType(element.getType()))
|
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
|
if (`type` == null) return Type.Empty
|
||||||
|
|
||||||
return `type`.accept<Type>(TypeVisitor(this))!!
|
val result = `type`.accept<Type>(TypeVisitor(this))!!
|
||||||
|
return when (nullability) {
|
||||||
|
Nullability.NotNull -> result.toNotNullType()
|
||||||
|
Nullability.Nullable -> result.toNullableType()
|
||||||
|
Nullability.Default -> result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun convertTypes(types: Array<PsiType>): List<Type> {
|
public fun convertTypes(types: Array<PsiType>): List<Type> {
|
||||||
return types.map { convertType(it) }
|
return types.map { convertType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun convertType(`type`: PsiType?, notNull: Boolean): Type {
|
public fun convertVariableType(variable: PsiVariable): Type {
|
||||||
val result = convertType(`type`)
|
var nullability = variable.nullabilityFromAnnotations()
|
||||||
if (notNull) {
|
if (nullability == Nullability.Default) {
|
||||||
return result.toNotNullType()
|
val initializer = variable.getInitializer()
|
||||||
|
if (initializer != null && variable.hasModifierProperty(PsiModifier.FINAL)) {
|
||||||
|
nullability = initializer.nullability()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return convertType(variable.getType(), nullability)
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun convertVariableType(variable: PsiVariable): Type
|
|
||||||
= convertType(variable.getType(), variable.isAnnotatedAsNotNull())
|
|
||||||
|
|
||||||
private fun convertToNotNullableTypes(types: Array<out PsiType?>): List<Type>
|
private fun convertToNotNullableTypes(types: Array<out PsiType?>): List<Type>
|
||||||
= types.map { convertType(it).toNotNullType() }
|
= types.map { convertType(it, Nullability.NotNull) }
|
||||||
|
|
||||||
public fun convertParameterList(parameters: PsiParameterList): ParameterList
|
public fun convertParameterList(parameters: PsiParameterList): ParameterList
|
||||||
= ParameterList(parameters.getParameters().map { convertParameter(it) })
|
= ParameterList(parameters.getParameters().map { convertParameter(it) })
|
||||||
|
|
||||||
public fun convertParameter(parameter: PsiParameter,
|
public fun convertParameter(parameter: PsiParameter,
|
||||||
forceNotNull: Boolean = false,
|
nullability: Nullability = Nullability.Default,
|
||||||
varValModifier: Parameter.VarValModifier = Parameter.VarValModifier.None,
|
varValModifier: Parameter.VarValModifier = Parameter.VarValModifier.None,
|
||||||
modifiers: Collection<Modifier> = listOf()): Parameter {
|
modifiers: Collection<Modifier> = listOf()): Parameter {
|
||||||
var `type` = convertVariableType(parameter)
|
var `type` = convertVariableType(parameter)
|
||||||
if (forceNotNull) {
|
when (nullability) {
|
||||||
`type` = `type`.toNotNullType()
|
Nullability.NotNull -> `type` = `type`.toNotNullType()
|
||||||
|
Nullability.Nullable -> `type` = `type`.toNullableType()
|
||||||
}
|
}
|
||||||
return Parameter(Identifier(parameter.getName()!!), `type`, varValModifier, modifiers)
|
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<String> = setOf("org.jetbrains.annotations.NotNull", "com.sun.istack.internal.NotNull", "javax.annotation.Nonnull")
|
val NOT_NULL_ANNOTATIONS: Set<String> = setOf("org.jetbrains.annotations.NotNull", "com.sun.istack.internal.NotNull", "javax.annotation.Nonnull")
|
||||||
|
val NULLABLE_ANNOTATIONS: Set<String> = setOf("org.jetbrains.annotations.Nullable", "com.sun.istack.internal.Nullable", "javax.annotation.Nullable")
|
||||||
|
|
||||||
val PRIMITIVE_TYPE_CONVERSIONS: Map<String, String> = mapOf(
|
val PRIMITIVE_TYPE_CONVERSIONS: Map<String, String> = mapOf(
|
||||||
"byte" to BYTE.asString(),
|
"byte" to BYTE.asString(),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import com.intellij.psi.PsiModifierListOwner
|
|||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import com.intellij.psi.util.PsiUtil
|
import com.intellij.psi.util.PsiUtil
|
||||||
import com.intellij.psi.PsiThisExpression
|
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(".")
|
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
|
fun PsiElement.countWriteAccesses(scope: PsiElement?): Int
|
||||||
= if (scope != null) findExpressionReferences(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0
|
= if (scope != null) findExpressionReferences(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0
|
||||||
|
|
||||||
fun PsiModifierListOwner.isAnnotatedAsNotNull(): Boolean
|
fun PsiModifierListOwner.nullabilityFromAnnotations(): Nullability {
|
||||||
= getModifierList()?.getAnnotations()?.any { NOT_NULL_ANNOTATIONS.contains(it.getQualifiedName()) } ?: false
|
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) {
|
fun PsiElement?.nullability(): Nullability = when(this) {
|
||||||
is PsiLiteralExpression -> getValue() != null
|
is PsiLiteralExpression -> if (getValue() != null) Nullability.NotNull else Nullability.Nullable
|
||||||
is PsiNewExpression -> true
|
is PsiNewExpression -> Nullability.NotNull
|
||||||
else -> false
|
//TODO: other cases
|
||||||
|
else -> Nullability.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDefaultInitializer(field: Field): String {
|
fun getDefaultInitializer(field: Field): String {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Import(val name: String) : Element {
|
|||||||
|
|
||||||
class ImportList(val imports: List<Import>) : Element {
|
class ImportList(val imports: List<Import>) : Element {
|
||||||
val filteredImports = imports.filter {
|
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 {
|
}.filter {
|
||||||
// If name is invalid, like with star imports, don't try to filter
|
// If name is invalid, like with star imports, don't try to filter
|
||||||
if (!isValidJavaFqName(it.name))
|
if (!isValidJavaFqName(it.name))
|
||||||
|
|||||||
@@ -25,16 +25,11 @@ class ElementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
|||||||
protected set
|
protected set
|
||||||
|
|
||||||
override fun visitLocalVariable(variable: PsiLocalVariable) {
|
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()!!),
|
result = LocalVariable(Identifier(variable.getName()!!),
|
||||||
converter.convertModifiers(variable),
|
converter.convertModifiers(variable),
|
||||||
kType,
|
converter.convertVariableType(variable),
|
||||||
converter.convertExpression(variable.getInitializer(), variable.getType()),
|
converter.convertExpression(variable.getInitializer(), variable.getType()),
|
||||||
converter.settings.forceLocalVariableImmutability || isFinal,
|
converter.settings.forceLocalVariableImmutability || variable.hasModifierProperty(PsiModifier.FINAL),
|
||||||
converter.settings)
|
converter.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ open class ExpressionVisitor(protected val converter: Converter,
|
|||||||
|
|
||||||
private fun createNewEmptyArrayWithoutInitialization(expression: PsiNewExpression): Expression {
|
private fun createNewEmptyArrayWithoutInitialization(expression: PsiNewExpression): Expression {
|
||||||
return ArrayWithoutInitializationExpression(
|
return ArrayWithoutInitializationExpression(
|
||||||
converter.convertType(expression.getType(), true),
|
converter.convertType(expression.getType(), Nullability.NotNull),
|
||||||
converter.convertExpressions(expression.getArrayDimensions()))
|
converter.convertExpressions(expression.getArrayDimensions()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ open class ExpressionVisitor(protected val converter: Converter,
|
|||||||
val insideSecondaryConstructor = containingConstructor != null && !containingConstructor.isPrimaryConstructor()
|
val insideSecondaryConstructor = containingConstructor != null && !containingConstructor.isPrimaryConstructor()
|
||||||
val addReceiver = insideSecondaryConstructor && (expression.getReference()?.resolve() as? PsiField)?.getContainingClass() == containingConstructor!!.getContainingClass()
|
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()!!
|
val referencedName = expression.getReferenceName()!!
|
||||||
var identifier: Expression = Identifier(referencedName, isNullable)
|
var identifier: Expression = Identifier(referencedName, isNullable)
|
||||||
val qualifier = expression.getQualifierExpression()
|
val qualifier = expression.getQualifierExpression()
|
||||||
@@ -266,12 +266,12 @@ open class ExpressionVisitor(protected val converter: Converter,
|
|||||||
result = CallChainExpression(converter.convertExpression(qualifier), identifier)
|
result = CallChainExpression(converter.convertExpression(qualifier), identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun PsiReference.isResolvedToNotNull(): Boolean {
|
private fun PsiReference.nullability(): Nullability {
|
||||||
val target = resolve()
|
val target = resolve()
|
||||||
return when(target) {
|
return when(target) {
|
||||||
is PsiEnumConstant -> true
|
is PsiEnumConstant -> Nullability.NotNull
|
||||||
is PsiModifierListOwner -> target.isAnnotatedAsNotNull()
|
is PsiModifierListOwner -> target.nullabilityFromAnnotations()
|
||||||
else -> false
|
else -> Nullability.Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
|||||||
val catchBlocks = statement.getCatchBlocks()
|
val catchBlocks = statement.getCatchBlocks()
|
||||||
val catchBlockParameters = statement.getCatchBlockParameters()
|
val catchBlockParameters = statement.getCatchBlockParameters()
|
||||||
for (i in 0..catchBlocks.size - 1) {
|
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])))
|
converter.convertBlock(catchBlocks[i])))
|
||||||
}
|
}
|
||||||
result = TryStatement(converter.convertBlock(statement.getTryBlock()),
|
result = TryStatement(converter.convertBlock(statement.getTryBlock()),
|
||||||
@@ -226,7 +226,7 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
|||||||
override fun visitWhileStatement(statement: PsiWhileStatement) {
|
override fun visitWhileStatement(statement: PsiWhileStatement) {
|
||||||
var condition: PsiExpression? = statement.getCondition()
|
var condition: PsiExpression? = statement.getCondition()
|
||||||
val expression: Expression = (if (condition != null && condition?.getType() != null)
|
val expression: Expression = (if (condition != null && condition?.getType() != null)
|
||||||
this.converter.convertExpression(condition, condition?.getType())
|
converter.convertExpression(condition, condition?.getType())
|
||||||
else
|
else
|
||||||
converter.convertExpression(condition))
|
converter.convertExpression(condition))
|
||||||
result = WhileStatement(expression, converter.convertStatement(statement.getBody()))
|
result = WhileStatement(expression, converter.convertStatement(statement.getBody()))
|
||||||
@@ -236,7 +236,7 @@ class StatementVisitor(public val converter: Converter) : JavaElementVisitor() {
|
|||||||
val returnValue = statement.getReturnValue()
|
val returnValue = statement.getReturnValue()
|
||||||
val methodReturnType = converter.methodReturnType
|
val methodReturnType = converter.methodReturnType
|
||||||
val expression = (if (returnValue != null && methodReturnType != null)
|
val expression = (if (returnValue != null && methodReturnType != null)
|
||||||
this.converter.convertExpression(returnValue, methodReturnType)
|
converter.convertExpression(returnValue, methodReturnType)
|
||||||
else
|
else
|
||||||
converter.convertExpression(returnValue))
|
converter.convertExpression(returnValue))
|
||||||
result = ReturnStatement(expression)
|
result = ReturnStatement(expression)
|
||||||
|
|||||||
@@ -16,17 +16,14 @@
|
|||||||
|
|
||||||
package org.jetbrains.jet.j2k.test;
|
package org.jetbrains.jet.j2k.test;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
|
||||||
import junit.framework.Test;
|
import junit.framework.Test;
|
||||||
import junit.framework.TestSuite;
|
import junit.framework.TestSuite;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import org.jetbrains.jet.JetTestUtils;
|
import org.jetbrains.jet.JetTestUtils;
|
||||||
import org.jetbrains.jet.test.InnerTestClasses;
|
import org.jetbrains.jet.test.InnerTestClasses;
|
||||||
import org.jetbrains.jet.test.TestMetadata;
|
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 */
|
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
@@ -53,6 +50,11 @@ public class JavaToKotlinConverterTestGenerated extends AbstractJavaToKotlinConv
|
|||||||
doTest("j2k/tests/testData/ast/annotations/jetbrainsNotNullChainExpr.java");
|
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")
|
@TestMetadata("j2k/tests/testData/ast/anonymousBlock")
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
package org.jetbrains.jet.j2k.`in`
|
package org.jetbrains.jet.j2k.`in`
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable
|
|
||||||
|
|
||||||
public class Converter()
|
public class Converter()
|
||||||
Reference in New Issue
Block a user