J2K: Use parameters nullability for method call
This commit is contained in:
@@ -12,3 +12,4 @@
|
|||||||
- Support conversion for annotation constructor calls
|
- Support conversion for annotation constructor calls
|
||||||
- Place comments from the middle of the call to the end
|
- 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)
|
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
|
if (expression == null) return Identifier.Empty
|
||||||
|
|
||||||
var convertedExpression = convertExpression(expression)
|
var convertedExpression = convertExpression(expression)
|
||||||
|
|
||||||
|
if (convertedExpression.isNullable && expectedNullability != null && !expectedNullability.isNullable(settings)) {
|
||||||
|
convertedExpression = BangBangExpression.surroundIfNullable(convertedExpression)
|
||||||
|
}
|
||||||
|
|
||||||
if (expectedType == null || expectedType == PsiType.VOID) return convertedExpression
|
if (expectedType == null || expectedType == PsiType.VOID) return convertedExpression
|
||||||
|
|
||||||
val actualType = expression.type ?: return convertedExpression
|
val actualType = expression.type ?: return convertedExpression
|
||||||
|
|||||||
@@ -114,21 +114,19 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
|
|||||||
|
|
||||||
val operationTokenType = expression.operationTokenType
|
val operationTokenType = expression.operationTokenType
|
||||||
|
|
||||||
|
val expectedNullability = if (operationTokenType in NON_NULL_OPERAND_OPS) Nullability.NotNull else null
|
||||||
|
|
||||||
val leftOperandExpectedType = getOperandExpectedType(left, right, operationTokenType)
|
val leftOperandExpectedType = getOperandExpectedType(left, right, operationTokenType)
|
||||||
var leftConverted = codeConverter.convertExpression(left, leftOperandExpectedType)
|
var leftConverted = codeConverter.convertExpression(left, leftOperandExpectedType, expectedNullability)
|
||||||
var rightConverted = codeConverter.convertExpression(
|
var rightConverted = codeConverter.convertExpression(
|
||||||
right,
|
right,
|
||||||
if (leftOperandExpectedType == null)
|
if (leftOperandExpectedType == null)
|
||||||
getOperandExpectedType(right, left, operationTokenType)
|
getOperandExpectedType(right, left, operationTokenType)
|
||||||
else
|
else
|
||||||
null
|
null,
|
||||||
|
expectedNullability
|
||||||
)
|
)
|
||||||
|
|
||||||
if (operationTokenType in NON_NULL_OPERAND_OPS) {
|
|
||||||
leftConverted = BangBangExpression.surroundIfNullable(leftConverted)
|
|
||||||
rightConverted = BangBangExpression.surroundIfNullable(rightConverted)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operationTokenType == JavaTokenType.GTGTGT) {
|
if (operationTokenType == JavaTokenType.GTGTGT) {
|
||||||
result = MethodCallExpression.buildNotNull(leftConverted, "ushr", listOf(rightConverted))
|
result = MethodCallExpression.buildNotNull(leftConverted, "ushr", listOf(rightConverted))
|
||||||
}
|
}
|
||||||
@@ -635,15 +633,16 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val resolved = expression.resolveMethod()
|
val resolved = expression.resolveMethod()
|
||||||
val parameters = resolved?.parameterList?.parameters
|
val parameters = resolved?.parameterList?.parameters ?: arrayOf()
|
||||||
val expectedTypes = parameters?.map { it.type } ?: listOf()
|
|
||||||
|
|
||||||
val commentsAndSpacesInheritance = CommentsAndSpacesInheritance.LINE_BREAKS
|
val commentsAndSpacesInheritance = CommentsAndSpacesInheritance.LINE_BREAKS
|
||||||
|
|
||||||
return if (arguments.size == expectedTypes.size) {
|
return if (arguments.size == parameters.size) {
|
||||||
arguments.mapIndexed { i, argument ->
|
arguments.mapIndexed { i, argument ->
|
||||||
val converted = codeConverter.convertExpression(argument, expectedTypes[i])
|
val expectedNullability = typeConverter.variableNullability(parameters[i])
|
||||||
val result = if (parameters != null && i == arguments.lastIndex && parameters[i].isVarArgs && argument.type is PsiArrayType)
|
val converted = codeConverter.convertExpression(argument, parameters[i].type, expectedNullability)
|
||||||
|
|
||||||
|
val result = if (i == arguments.lastIndex && parameters[i].isVarArgs && argument.type is PsiArrayType)
|
||||||
StarExpression(converted)
|
StarExpression(converted)
|
||||||
else
|
else
|
||||||
converted
|
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)
|
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? {
|
protected fun convertMethodCallWithReceiverCast(qualifier: PsiExpression?, arguments: Array<PsiExpression>, typeArgumentsConverted: List<Type>, codeConverter: CodeConverter): MethodCallExpression? {
|
||||||
val convertedArguments = arguments.map { codeConverter.convertExpression(it) }
|
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 ignoreCaseExpression = ignoreCaseArgument?.let { codeConverter.convertExpression(it) } ?: LiteralExpression("true").assignNoPrototype()
|
||||||
val ignoreCaseArgumentExpression = AssignmentExpression(Identifier("ignoreCase").assignNoPrototype(), ignoreCaseExpression, Operator.EQ).assignNoPrototype()
|
val ignoreCaseArgumentExpression = AssignmentExpression(Identifier("ignoreCase").assignNoPrototype(), ignoreCaseExpression, Operator.EQ).assignNoPrototype()
|
||||||
return MethodCallExpression.build(codeConverter.convertExpression(qualifier), methodName,
|
return MethodCallExpression.build(codeConverter.convertExpression(qualifier), methodName,
|
||||||
codeConverter.convertExpressions(arguments) + ignoreCaseArgumentExpression,
|
arguments.map { codeConverter.convertExpression(it, null, Nullability.NotNull) } + ignoreCaseArgumentExpression,
|
||||||
typeArgumentsConverted, false)
|
typeArgumentsConverted, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,8 +119,7 @@ class DefaultStatementConverter : JavaElementVisitor(), StatementConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitForeachStatement(statement: PsiForeachStatement) {
|
override fun visitForeachStatement(statement: PsiForeachStatement) {
|
||||||
val iteratorExpr = codeConverter.convertExpression(statement.iteratedValue)
|
val iterator = codeConverter.convertExpression(statement.iteratedValue, null, Nullability.NotNull)
|
||||||
val iterator = BangBangExpression.surroundIfNullable(iteratorExpr)
|
|
||||||
val iterationParameter = statement.iterationParameter
|
val iterationParameter = statement.iterationParameter
|
||||||
result = ForeachStatement(iterationParameter.declarationIdentifier(),
|
result = ForeachStatement(iterationParameter.declarationIdentifier(),
|
||||||
if (codeConverter.settings.specifyLocalVariableTypeByDefault) codeConverter.typeConverter.convertVariableType(iterationParameter) else null,
|
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)
|
= convertType(method.returnType, methodNullability(method), methodMutability(method)).assignPrototype(method.returnTypeElement)
|
||||||
|
|
||||||
fun variableNullability(variable: PsiVariable): Nullability
|
fun variableNullability(variable: PsiVariable): Nullability
|
||||||
= nullabilityFlavor.forVariableType(variable)
|
= nullabilityFlavor.forVariableType(variable, true)
|
||||||
|
|
||||||
fun methodNullability(method: PsiMethod): Nullability
|
fun methodNullability(method: PsiMethod): Nullability
|
||||||
= nullabilityFlavor.forMethodReturnType(method)
|
= nullabilityFlavor.forMethodReturnType(method)
|
||||||
|
|
||||||
fun variableMutability(variable: PsiVariable): Mutability
|
fun variableMutability(variable: PsiVariable): Mutability
|
||||||
= mutabilityFlavor.forVariableType(variable)
|
= mutabilityFlavor.forVariableType(variable, true)
|
||||||
|
|
||||||
fun methodMutability(method: PsiMethod): Mutability
|
fun methodMutability(method: PsiMethod): Mutability
|
||||||
= mutabilityFlavor.forMethodReturnType(method)
|
= mutabilityFlavor.forMethodReturnType(method)
|
||||||
@@ -117,15 +117,15 @@ class TypeConverter(val converter: Converter) {
|
|||||||
open fun forVariableTypeAfterUsageSearch(variable: PsiVariable): T = default
|
open fun forVariableTypeAfterUsageSearch(variable: PsiVariable): T = default
|
||||||
open fun fromMethodBody(body: PsiCodeBlock): 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]
|
val cached = cache[variable]
|
||||||
if (cached != null) return cached
|
if (cached != null) return cached
|
||||||
val value = withRecursionPrevention(variable) { forVariableTypeNoCache(variable) }
|
val value = withRecursionPrevention(variable) { forVariableTypeNoCache(variable, checkScope) }
|
||||||
cache[variable] = value
|
cache[variable] = value
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun forVariableTypeNoCache(variable: PsiVariable): T {
|
private fun forVariableTypeNoCache(variable: PsiVariable, checkScope: Boolean): T {
|
||||||
if (variable is PsiEnumConstant) return forEnumConstant
|
if (variable is PsiEnumConstant) return forEnumConstant
|
||||||
|
|
||||||
val variableType = variable.type
|
val variableType = variable.type
|
||||||
@@ -135,6 +135,10 @@ class TypeConverter(val converter: Converter) {
|
|||||||
value = fromAnnotations(variable)
|
value = fromAnnotations(variable)
|
||||||
if (value != default) return value
|
if (value != default) return value
|
||||||
|
|
||||||
|
if (checkScope && !converter.inConversionScope(variable)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
if (variable is PsiParameter) {
|
if (variable is PsiParameter) {
|
||||||
val scope = variable.declarationScope
|
val scope = variable.declarationScope
|
||||||
if (scope is PsiMethod) {
|
if (scope is PsiMethod) {
|
||||||
@@ -143,7 +147,7 @@ class TypeConverter(val converter: Converter) {
|
|||||||
val superSignatures = scope.hierarchicalMethodSignature.superSignatures
|
val superSignatures = scope.hierarchicalMethodSignature.superSignatures
|
||||||
value = superSignatures.map { signature ->
|
value = superSignatures.map { signature ->
|
||||||
val params = signature.method.parameterList.parameters
|
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
|
}.firstOrNull { it != default } ?: default
|
||||||
if (value != default) return value
|
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);
|
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")
|
@TestMetadata("NullableIntNoCrash.java")
|
||||||
public void testNullableIntNoCrash() throws Exception {
|
public void testNullableIntNoCrash() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/NullableIntNoCrash.java");
|
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/NullableIntNoCrash.java");
|
||||||
|
|||||||
@@ -3493,6 +3493,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("NullableIntNoCrash.java")
|
||||||
public void testNullableIntNoCrash() throws Exception {
|
public void testNullableIntNoCrash() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/NullableIntNoCrash.java");
|
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/NullableIntNoCrash.java");
|
||||||
|
|||||||
Reference in New Issue
Block a user