J2K: Use parameters nullability for method call
This commit is contained in:
+2
-1
@@ -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 "||")
|
||||
- Drop line breaks between operator arguments (except '+', "-", "&&" and "||")
|
||||
- Add non-null assertions on call site for non-null parameters
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -414,7 +414,7 @@ enum class SpecialMethod(val qualifiedClassName: String?, val methodName: String
|
||||
}
|
||||
|
||||
protected fun convertWithChangedName(name: String, qualifier: PsiExpression?, arguments: Array<PsiExpression>, typeArgumentsConverted: List<Type>, 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<PsiExpression>, typeArgumentsConverted: List<Type>, 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<Integer> list = new ArrayList<Integer>();
|
||||
list.remove(myIntProp);
|
||||
}
|
||||
|
||||
public void foo1(@NotNull String s) {
|
||||
|
||||
}
|
||||
|
||||
public void foo2(String s) {
|
||||
|
||||
}
|
||||
|
||||
public void foo3(@Nullable String s) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<Int>()
|
||||
list.remove(myIntProp!!)
|
||||
}
|
||||
|
||||
fun foo1(s: String) {
|
||||
|
||||
}
|
||||
|
||||
fun foo2(s: String) {
|
||||
|
||||
}
|
||||
|
||||
fun foo3(s: String?) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user