KT-4418 Converter from java should honor "@Nullable" annotations

#KT-4418 Fixed
This commit is contained in:
Valentin Kipyatkov
2014-06-04 16:00:05 +04:00
parent 118b23061b
commit f96721fa7e
10 changed files with 117 additions and 56 deletions
+25 -25
View File
@@ -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(),
+15 -7
View File
@@ -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 {
+1 -1
View File
@@ -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()