diff --git a/Changelog.md b/Changelog.md index 1b33a4f4c57..94ae2e24862 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,4 +11,5 @@ - Protected members used outside of inheritors are converted as public - Support conversion for annotation constructor calls - Place comments from the middle of the call to the end -- Drop line breaks between operator arguments (except '+', "-", "&&" and "||") \ No newline at end of file +- Drop line breaks between operator arguments (except '+', "-", "&&" and "||") +- Add non-null assertions on call site for non-null parameters \ No newline at end of file diff --git a/j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt index 8c91135d443..f00a70be78b 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt @@ -87,10 +87,15 @@ class CodeConverter( isVal).assignPrototype(variable) } - fun convertExpression(expression: PsiExpression?, expectedType: PsiType?): Expression { + fun convertExpression(expression: PsiExpression?, expectedType: PsiType?, expectedNullability: Nullability? = null): Expression { if (expression == null) return Identifier.Empty var convertedExpression = convertExpression(expression) + + if (convertedExpression.isNullable && expectedNullability != null && !expectedNullability.isNullable(settings)) { + convertedExpression = BangBangExpression.surroundIfNullable(convertedExpression) + } + if (expectedType == null || expectedType == PsiType.VOID) return convertedExpression val actualType = expression.type ?: return convertedExpression diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt index 1b88ca79eea..88fff47aa95 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt @@ -114,21 +114,19 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { val operationTokenType = expression.operationTokenType + val expectedNullability = if (operationTokenType in NON_NULL_OPERAND_OPS) Nullability.NotNull else null + val leftOperandExpectedType = getOperandExpectedType(left, right, operationTokenType) - var leftConverted = codeConverter.convertExpression(left, leftOperandExpectedType) + var leftConverted = codeConverter.convertExpression(left, leftOperandExpectedType, expectedNullability) var rightConverted = codeConverter.convertExpression( right, if (leftOperandExpectedType == null) getOperandExpectedType(right, left, operationTokenType) else - null + null, + expectedNullability ) - if (operationTokenType in NON_NULL_OPERAND_OPS) { - leftConverted = BangBangExpression.surroundIfNullable(leftConverted) - rightConverted = BangBangExpression.surroundIfNullable(rightConverted) - } - if (operationTokenType == JavaTokenType.GTGTGT) { result = MethodCallExpression.buildNotNull(leftConverted, "ushr", listOf(rightConverted)) } @@ -635,15 +633,16 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { } val resolved = expression.resolveMethod() - val parameters = resolved?.parameterList?.parameters - val expectedTypes = parameters?.map { it.type } ?: listOf() + val parameters = resolved?.parameterList?.parameters ?: arrayOf() val commentsAndSpacesInheritance = CommentsAndSpacesInheritance.LINE_BREAKS - return if (arguments.size == expectedTypes.size) { + return if (arguments.size == parameters.size) { arguments.mapIndexed { i, argument -> - val converted = codeConverter.convertExpression(argument, expectedTypes[i]) - val result = if (parameters != null && i == arguments.lastIndex && parameters[i].isVarArgs && argument.type is PsiArrayType) + val expectedNullability = typeConverter.variableNullability(parameters[i]) + val converted = codeConverter.convertExpression(argument, parameters[i].type, expectedNullability) + + val result = if (i == arguments.lastIndex && parameters[i].isVarArgs && argument.type is PsiArrayType) StarExpression(converted) else converted diff --git a/j2k/src/org/jetbrains/kotlin/j2k/SpecialMethod.kt b/j2k/src/org/jetbrains/kotlin/j2k/SpecialMethod.kt index de2c0d3f605..c36559c1854 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/SpecialMethod.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/SpecialMethod.kt @@ -414,7 +414,7 @@ enum class SpecialMethod(val qualifiedClassName: String?, val methodName: String } protected fun convertWithChangedName(name: String, qualifier: PsiExpression?, arguments: Array, typeArgumentsConverted: List, codeConverter: CodeConverter) - = MethodCallExpression.buildNotNull(codeConverter.convertExpression(qualifier), name, codeConverter.convertExpressions(arguments), typeArgumentsConverted) + = MethodCallExpression.buildNotNull(codeConverter.convertExpression(qualifier), name, arguments.map { codeConverter.convertExpression(it, null, Nullability.NotNull) }, typeArgumentsConverted) protected fun convertMethodCallWithReceiverCast(qualifier: PsiExpression?, arguments: Array, typeArgumentsConverted: List, codeConverter: CodeConverter): MethodCallExpression? { val convertedArguments = arguments.map { codeConverter.convertExpression(it) } @@ -475,7 +475,7 @@ private fun addIgnoreCaseArgument( val ignoreCaseExpression = ignoreCaseArgument?.let { codeConverter.convertExpression(it) } ?: LiteralExpression("true").assignNoPrototype() val ignoreCaseArgumentExpression = AssignmentExpression(Identifier("ignoreCase").assignNoPrototype(), ignoreCaseExpression, Operator.EQ).assignNoPrototype() return MethodCallExpression.build(codeConverter.convertExpression(qualifier), methodName, - codeConverter.convertExpressions(arguments) + ignoreCaseArgumentExpression, + arguments.map { codeConverter.convertExpression(it, null, Nullability.NotNull) } + ignoreCaseArgumentExpression, typeArgumentsConverted, false) } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/StatementConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/StatementConverter.kt index fff424d6fbd..27840fcd12b 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/StatementConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/StatementConverter.kt @@ -119,8 +119,7 @@ class DefaultStatementConverter : JavaElementVisitor(), StatementConverter { } override fun visitForeachStatement(statement: PsiForeachStatement) { - val iteratorExpr = codeConverter.convertExpression(statement.iteratedValue) - val iterator = BangBangExpression.surroundIfNullable(iteratorExpr) + val iterator = codeConverter.convertExpression(statement.iteratedValue, null, Nullability.NotNull) val iterationParameter = statement.iterationParameter result = ForeachStatement(iterationParameter.declarationIdentifier(), if (codeConverter.settings.specifyLocalVariableTypeByDefault) codeConverter.typeConverter.convertVariableType(iterationParameter) else null, diff --git a/j2k/src/org/jetbrains/kotlin/j2k/TypeConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/TypeConverter.kt index 29be91944bf..264eb7f8ef4 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/TypeConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/TypeConverter.kt @@ -72,13 +72,13 @@ class TypeConverter(val converter: Converter) { = convertType(method.returnType, methodNullability(method), methodMutability(method)).assignPrototype(method.returnTypeElement) fun variableNullability(variable: PsiVariable): Nullability - = nullabilityFlavor.forVariableType(variable) + = nullabilityFlavor.forVariableType(variable, true) fun methodNullability(method: PsiMethod): Nullability = nullabilityFlavor.forMethodReturnType(method) fun variableMutability(variable: PsiVariable): Mutability - = mutabilityFlavor.forVariableType(variable) + = mutabilityFlavor.forVariableType(variable, true) fun methodMutability(method: PsiMethod): Mutability = mutabilityFlavor.forMethodReturnType(method) @@ -117,15 +117,15 @@ class TypeConverter(val converter: Converter) { open fun forVariableTypeAfterUsageSearch(variable: PsiVariable): T = default open fun fromMethodBody(body: PsiCodeBlock): T = default - fun forVariableType(variable: PsiVariable): T { + fun forVariableType(variable: PsiVariable, checkScope: Boolean): T { val cached = cache[variable] if (cached != null) return cached - val value = withRecursionPrevention(variable) { forVariableTypeNoCache(variable) } + val value = withRecursionPrevention(variable) { forVariableTypeNoCache(variable, checkScope) } cache[variable] = value return value } - private fun forVariableTypeNoCache(variable: PsiVariable): T { + private fun forVariableTypeNoCache(variable: PsiVariable, checkScope: Boolean): T { if (variable is PsiEnumConstant) return forEnumConstant val variableType = variable.type @@ -135,6 +135,10 @@ class TypeConverter(val converter: Converter) { value = fromAnnotations(variable) if (value != default) return value + if (checkScope && !converter.inConversionScope(variable)) { + return value + } + if (variable is PsiParameter) { val scope = variable.declarationScope if (scope is PsiMethod) { @@ -143,7 +147,7 @@ class TypeConverter(val converter: Converter) { val superSignatures = scope.hierarchicalMethodSignature.superSignatures value = superSignatures.map { signature -> val params = signature.method.parameterList.parameters - if (paramIndex < params.size) forVariableType(params[paramIndex]) else default + if (paramIndex < params.size) forVariableType(params[paramIndex], false) else default }.firstOrNull { it != default } ?: default if (value != default) return value } diff --git a/j2k/testData/fileOrElement/nullability/nullableField.java b/j2k/testData/fileOrElement/nullability/nullableField.java new file mode 100644 index 00000000000..d47990c679d --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableField.java @@ -0,0 +1,45 @@ +package test; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class Test { + private String myProp; + private Integer myIntProp; + + public void onCreate() { + myProp = ""; + myIntProp = 1; + } + + public void test() { + foo1(myProp); + foo2(myProp); + foo3(myProp); + + myProp.charAt(myIntProp); + System.out.println(myProp); + + boolean b = "aaa".equals(myProp); + String s = "aaa" + myProp; + + myProp.compareToIgnoreCase(myProp); + + List list = new ArrayList(); + list.remove(myIntProp); + } + + public void foo1(@NotNull String s) { + + } + + public void foo2(String s) { + + } + + public void foo3(@Nullable String s) { + + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/nullability/nullableField.kt b/j2k/testData/fileOrElement/nullability/nullableField.kt new file mode 100644 index 00000000000..d7b8c1a5645 --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableField.kt @@ -0,0 +1,42 @@ +package test + +import java.util.ArrayList + +class Test { + private var myProp: String? = null + private var myIntProp: Int? = null + + fun onCreate() { + myProp = "" + myIntProp = 1 + } + + fun test() { + foo1(myProp!!) + foo2(myProp!!) + foo3(myProp) + + myProp!![myIntProp!!] + println(myProp) + + val b = "aaa" == myProp + val s = "aaa" + myProp!! + + myProp!!.compareTo(myProp!!, ignoreCase = true) + + val list = ArrayList() + list.remove(myIntProp!!) + } + + fun foo1(s: String) { + + } + + fun foo2(s: String) { + + } + + fun foo3(s: String?) { + + } +} \ No newline at end of file diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java index 58b8044e915..98ac97ddef9 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java @@ -3493,6 +3493,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo doTest(fileName); } + @TestMetadata("nullableField.java") + public void testNullableField() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableField.java"); + doTest(fileName); + } + @TestMetadata("NullableIntNoCrash.java") public void testNullableIntNoCrash() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/NullableIntNoCrash.java"); diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java index 0dba621c6f1..7473292688f 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java @@ -3493,6 +3493,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo doTest(fileName); } + @TestMetadata("nullableField.java") + public void testNullableField() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableField.java"); + doTest(fileName); + } + @TestMetadata("NullableIntNoCrash.java") public void testNullableIntNoCrash() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/NullableIntNoCrash.java");