J2K: Get nullability from variable initializer when converting call
#KT-11534 In Progress
This commit is contained in:
@@ -21,6 +21,8 @@ import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import com.intellij.util.SmartList
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
||||
@@ -65,6 +67,8 @@ class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
|
||||
|
||||
if (modificationStamp == file.modificationStamp) break
|
||||
|
||||
(PsiModificationTracker.SERVICE.getInstance(file.project) as? PsiModificationTrackerImpl)?.incOutOfCodeBlockModificationCounter()
|
||||
|
||||
elementToActions = collectAvailableActions(file, rangeMarker)
|
||||
}
|
||||
|
||||
|
||||
@@ -74,9 +74,7 @@ class CodeConverter(
|
||||
}
|
||||
|
||||
fun convertLocalVariable(variable: PsiLocalVariable): LocalVariable {
|
||||
val isVal = variable.hasModifierProperty(PsiModifier.FINAL) ||
|
||||
variable.initializer == null/* we do not know actually and prefer val until we have better analysis*/ ||
|
||||
!variable.hasWriteAccesses(converter.referenceSearcher, variable.getContainingMethod())
|
||||
val isVal = canChangeType(variable)
|
||||
val type = typeConverter.convertVariableType(variable)
|
||||
val explicitType = type.check { settings.specifyLocalVariableTypeByDefault || converter.shouldDeclareVariableType(variable, type, isVal) }
|
||||
return LocalVariable(variable.declarationIdentifier(),
|
||||
@@ -87,6 +85,12 @@ class CodeConverter(
|
||||
isVal).assignPrototype(variable)
|
||||
}
|
||||
|
||||
fun canChangeType(variable: PsiLocalVariable) : Boolean {
|
||||
return variable.hasModifierProperty(PsiModifier.FINAL) ||
|
||||
variable.initializer == null/* we do not know actually and prefer val until we have better analysis*/ ||
|
||||
!variable.hasWriteAccesses(converter.referenceSearcher, variable.getContainingMethod())
|
||||
}
|
||||
|
||||
fun convertExpression(expression: PsiExpression?, expectedType: PsiType?, expectedNullability: Nullability? = null): Expression {
|
||||
if (expression == null) return Identifier.Empty
|
||||
|
||||
|
||||
@@ -531,7 +531,21 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
|
||||
|
||||
val referenceName = expression.referenceName!!
|
||||
val target = expression.resolve()
|
||||
val isNullable = target is PsiVariable && typeConverter.variableNullability(target).isNullable(codeConverter.settings)
|
||||
|
||||
fun isNullable(target: PsiVariable): Boolean {
|
||||
if (typeConverter.variableNullability(target).isNullable(codeConverter.settings)) return true
|
||||
|
||||
val canChangeType = when (target) {
|
||||
is PsiLocalVariable -> codeConverter.canChangeType(target)
|
||||
is PsiField -> target.hasModifierProperty(PsiModifier.PRIVATE)
|
||||
else -> return false
|
||||
}
|
||||
val shouldDeclareVariableType = converter.shouldDeclareVariableType(target, converter.typeConverter.convertVariableType(target), canChangeType)
|
||||
return !shouldDeclareVariableType && !converter.settings.specifyFieldTypeByDefault && codeConverter.convertExpression(target.initializer).isNullable
|
||||
}
|
||||
|
||||
val isNullable = target is PsiVariable && isNullable(target)
|
||||
|
||||
val qualifier = expression.qualifierExpression
|
||||
|
||||
var identifier = Identifier(referenceName, isNullable).assignNoPrototype()
|
||||
|
||||
@@ -67,6 +67,9 @@ class TypeCastExpression(val type: Type, val expression: Expression) : Expressio
|
||||
override fun generateCode(builder: CodeBuilder) {
|
||||
builder.appendOperand(this, expression).append(" as ").append(type)
|
||||
}
|
||||
|
||||
override val isNullable: Boolean
|
||||
get() = type.isNullable
|
||||
}
|
||||
|
||||
class LiteralExpression(val literalText: String) : Expression() {
|
||||
|
||||
@@ -3,7 +3,7 @@ internal abstract class C {
|
||||
val s1 = f()!!
|
||||
|
||||
val s2 = g() ?: error("g should not return null")
|
||||
val h = s2!!.hashCode()
|
||||
val h = s2.hashCode()
|
||||
}
|
||||
|
||||
internal abstract fun f(): String?
|
||||
|
||||
+2
-4
@@ -1,5 +1,3 @@
|
||||
// ERROR: Type mismatch: inferred type is Passenger.PassChild? but Passenger.PassChild was expected
|
||||
// ERROR: Type mismatch: inferred type is Passenger.PassChild? but Passenger.PassChild was expected
|
||||
class Passenger {
|
||||
open class PassParent
|
||||
|
||||
@@ -18,9 +16,9 @@ class Passenger {
|
||||
val pass = provideNullable(1)
|
||||
if (1 == 2) {
|
||||
assert(pass != null)
|
||||
accept2(pass as PassChild?)
|
||||
accept2((pass as PassChild?)!!)
|
||||
}
|
||||
accept2(pass as PassChild?)
|
||||
accept2((pass as PassChild?)!!)
|
||||
}
|
||||
|
||||
fun accept1(p: PassChild) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class Test {
|
||||
public String nullableString(int p) {
|
||||
return p > 0 ? "response" : null;
|
||||
}
|
||||
|
||||
private String nullableInitializerField = nullableString(3);
|
||||
private String nullableInitializerFieldFinal = nullableString(3);
|
||||
public String nullableInitializerPublicField = nullableString(3);
|
||||
|
||||
public void testProperty() {
|
||||
nullableInitializerField = "aaa"
|
||||
|
||||
nullableInitializerField.charAt(0);
|
||||
nullableInitializerFieldFinal.charAt(0);
|
||||
nullableInitializerPublicField.charAt(0);
|
||||
}
|
||||
|
||||
public void testLocalVariable() {
|
||||
String nullableInitializerVal = nullableString(3);
|
||||
nullableInitializerVal.charAt(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// ERROR: Type mismatch: inferred type is String? but String was expected
|
||||
// ERROR: Type mismatch: inferred type is String? but String was expected
|
||||
class Test {
|
||||
fun nullableString(p: Int): String? {
|
||||
return if (p > 0) "response" else null
|
||||
}
|
||||
|
||||
private var nullableInitializerField: String = nullableString(3)
|
||||
private val nullableInitializerFieldFinal = nullableString(3)
|
||||
var nullableInitializerPublicField: String = nullableString(3)
|
||||
|
||||
fun testProperty() {
|
||||
nullableInitializerField = "aaa"
|
||||
|
||||
nullableInitializerField[0]
|
||||
nullableInitializerFieldFinal!![0]
|
||||
nullableInitializerPublicField[0]
|
||||
}
|
||||
|
||||
fun testLocalVariable() {
|
||||
val nullableInitializerVal = nullableString(3)
|
||||
nullableInitializerVal!![0]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class TestJava {
|
||||
public Object nullableObj(int p) {
|
||||
return p > 0 ? "response" : null;
|
||||
}
|
||||
|
||||
public String nullableInitializerFieldCast = (String) nullableObj(3);
|
||||
private String nullableInitializerPrivateFieldCast = (String) nullableObj(3);
|
||||
|
||||
public void testProperty() {
|
||||
nullableInitializerFieldCast.charAt(0);
|
||||
nullableInitializerPrivateFieldCast.charAt(0);
|
||||
}
|
||||
|
||||
public void testLocalVariable() {
|
||||
String nullableInitializerValCast = (String) nullableObj(3);
|
||||
|
||||
nullableInitializerValCast.charAt(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// ERROR: Type mismatch: inferred type is String? but String was expected
|
||||
class TestJava {
|
||||
fun nullableObj(p: Int): Any? {
|
||||
return if (p > 0) "response" else null
|
||||
}
|
||||
|
||||
var nullableInitializerFieldCast: String = nullableObj(3) as String?
|
||||
private val nullableInitializerPrivateFieldCast = nullableObj(3) as String?
|
||||
|
||||
fun testProperty() {
|
||||
nullableInitializerFieldCast[0]
|
||||
nullableInitializerPrivateFieldCast!![0]
|
||||
}
|
||||
|
||||
fun testLocalVariable() {
|
||||
val nullableInitializerValCast = nullableObj(3) as String?
|
||||
|
||||
nullableInitializerValCast!![0]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class Test {
|
||||
public String notNullString(int p) {
|
||||
return "response";
|
||||
}
|
||||
|
||||
private String notNullInitializerField = notNullString(3);
|
||||
public String notNullInitializerPublicField = notNullString(3);
|
||||
|
||||
public void testProperty() {
|
||||
notNullInitializerField.charAt(0);
|
||||
notNullInitializerPublicField.charAt(0);
|
||||
}
|
||||
|
||||
public void testLocalVariable() {
|
||||
String notNullInitializerVal = notNullString(3);
|
||||
notNullInitializerVal.charAt(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
class Test {
|
||||
fun notNullString(p: Int): String {
|
||||
return "response"
|
||||
}
|
||||
|
||||
private val notNullInitializerField = notNullString(3)
|
||||
var notNullInitializerPublicField = notNullString(3)
|
||||
|
||||
fun testProperty() {
|
||||
notNullInitializerField[0]
|
||||
notNullInitializerPublicField[0]
|
||||
}
|
||||
|
||||
fun testLocalVariable() {
|
||||
val notNullInitializerVal = notNullString(3)
|
||||
notNullInitializerVal[0]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class TestJava {
|
||||
private String notNullInitializerFieldNullableUsage = "aaa";
|
||||
private String notNullInitializerFieldNotNullUsage = "aaa";
|
||||
|
||||
private String nullInitializerFieldNullableUsage = null;
|
||||
private String nullInitializerFieldNotNullUsage = null;
|
||||
|
||||
public void testNotNull(@Nullable Object obj) {
|
||||
if (true) {
|
||||
notNullInitializerFieldNullableUsage = (String) obj;
|
||||
notNullInitializerFieldNotNullUsage = "str";
|
||||
|
||||
notNullInitializerFieldNullableUsage.charAt(1);
|
||||
notNullInitializerFieldNotNullUsage.charAt(1);
|
||||
}
|
||||
else {
|
||||
nullInitializerFieldNullableUsage = (String) obj;
|
||||
nullInitializerFieldNotNullUsage = "str";
|
||||
|
||||
nullInitializerFieldNullableUsage.charAt(1);
|
||||
nullInitializerFieldNotNullUsage.charAt(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// ERROR: Type mismatch: inferred type is String? but String was expected
|
||||
class TestJava {
|
||||
private var notNullInitializerFieldNullableUsage = "aaa"
|
||||
private var notNullInitializerFieldNotNullUsage = "aaa"
|
||||
|
||||
private var nullInitializerFieldNullableUsage: String? = null
|
||||
private var nullInitializerFieldNotNullUsage: String? = null
|
||||
|
||||
fun testNotNull(obj: Any?) {
|
||||
if (true) {
|
||||
notNullInitializerFieldNullableUsage = obj as String?
|
||||
notNullInitializerFieldNotNullUsage = "str"
|
||||
|
||||
notNullInitializerFieldNullableUsage[1]
|
||||
notNullInitializerFieldNotNullUsage[1]
|
||||
} else {
|
||||
nullInitializerFieldNullableUsage = obj as String?
|
||||
nullInitializerFieldNotNullUsage = "str"
|
||||
|
||||
nullInitializerFieldNullableUsage!![1]
|
||||
nullInitializerFieldNotNullUsage!![1]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3517,6 +3517,30 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableInitializer1.java")
|
||||
public void testNullableInitializer1() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableInitializer1.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableInitializer2.java")
|
||||
public void testNullableInitializer2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableInitializer2.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableInitializer3.java")
|
||||
public void testNullableInitializer3() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableInitializer3.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableInitializer4.java")
|
||||
public void testNullableInitializer4() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableInitializer4.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NullableIntNoCrash.java")
|
||||
public void testNullableIntNoCrash() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/NullableIntNoCrash.java");
|
||||
|
||||
@@ -3517,6 +3517,30 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableInitializer1.java")
|
||||
public void testNullableInitializer1() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableInitializer1.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableInitializer2.java")
|
||||
public void testNullableInitializer2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableInitializer2.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableInitializer3.java")
|
||||
public void testNullableInitializer3() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableInitializer3.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullableInitializer4.java")
|
||||
public void testNullableInitializer4() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/nullability/nullableInitializer4.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