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()!!),
|
||||
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<PsiMember>): 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<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> {
|
||||
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<out PsiType?>): List<Type>
|
||||
= 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<Modifier> = 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<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(
|
||||
"byte" to BYTE.asString(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -30,7 +30,7 @@ class Import(val name: String) : Element {
|
||||
|
||||
class ImportList(val imports: List<Import>) : 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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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`
|
||||
|
||||
import org.jetbrains.annotations.Nullable
|
||||
|
||||
public class Converter()
|
||||
Reference in New Issue
Block a user