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.PsiElement
|
||||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||||
|
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||||
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
import com.intellij.util.SmartList
|
import com.intellij.util.SmartList
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
||||||
@@ -65,6 +67,8 @@ class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
|
|||||||
|
|
||||||
if (modificationStamp == file.modificationStamp) break
|
if (modificationStamp == file.modificationStamp) break
|
||||||
|
|
||||||
|
(PsiModificationTracker.SERVICE.getInstance(file.project) as? PsiModificationTrackerImpl)?.incOutOfCodeBlockModificationCounter()
|
||||||
|
|
||||||
elementToActions = collectAvailableActions(file, rangeMarker)
|
elementToActions = collectAvailableActions(file, rangeMarker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,9 +74,7 @@ class CodeConverter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun convertLocalVariable(variable: PsiLocalVariable): LocalVariable {
|
fun convertLocalVariable(variable: PsiLocalVariable): LocalVariable {
|
||||||
val isVal = variable.hasModifierProperty(PsiModifier.FINAL) ||
|
val isVal = canChangeType(variable)
|
||||||
variable.initializer == null/* we do not know actually and prefer val until we have better analysis*/ ||
|
|
||||||
!variable.hasWriteAccesses(converter.referenceSearcher, variable.getContainingMethod())
|
|
||||||
val type = typeConverter.convertVariableType(variable)
|
val type = typeConverter.convertVariableType(variable)
|
||||||
val explicitType = type.check { settings.specifyLocalVariableTypeByDefault || converter.shouldDeclareVariableType(variable, type, isVal) }
|
val explicitType = type.check { settings.specifyLocalVariableTypeByDefault || converter.shouldDeclareVariableType(variable, type, isVal) }
|
||||||
return LocalVariable(variable.declarationIdentifier(),
|
return LocalVariable(variable.declarationIdentifier(),
|
||||||
@@ -87,6 +85,12 @@ class CodeConverter(
|
|||||||
isVal).assignPrototype(variable)
|
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 {
|
fun convertExpression(expression: PsiExpression?, expectedType: PsiType?, expectedNullability: Nullability? = null): Expression {
|
||||||
if (expression == null) return Identifier.Empty
|
if (expression == null) return Identifier.Empty
|
||||||
|
|
||||||
|
|||||||
@@ -531,7 +531,21 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
|
|||||||
|
|
||||||
val referenceName = expression.referenceName!!
|
val referenceName = expression.referenceName!!
|
||||||
val target = expression.resolve()
|
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
|
val qualifier = expression.qualifierExpression
|
||||||
|
|
||||||
var identifier = Identifier(referenceName, isNullable).assignNoPrototype()
|
var identifier = Identifier(referenceName, isNullable).assignNoPrototype()
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ class TypeCastExpression(val type: Type, val expression: Expression) : Expressio
|
|||||||
override fun generateCode(builder: CodeBuilder) {
|
override fun generateCode(builder: CodeBuilder) {
|
||||||
builder.appendOperand(this, expression).append(" as ").append(type)
|
builder.appendOperand(this, expression).append(" as ").append(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val isNullable: Boolean
|
||||||
|
get() = type.isNullable
|
||||||
}
|
}
|
||||||
|
|
||||||
class LiteralExpression(val literalText: String) : Expression() {
|
class LiteralExpression(val literalText: String) : Expression() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ internal abstract class C {
|
|||||||
val s1 = f()!!
|
val s1 = f()!!
|
||||||
|
|
||||||
val s2 = g() ?: error("g should not return null")
|
val s2 = g() ?: error("g should not return null")
|
||||||
val h = s2!!.hashCode()
|
val h = s2.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract fun f(): String?
|
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 {
|
class Passenger {
|
||||||
open class PassParent
|
open class PassParent
|
||||||
|
|
||||||
@@ -18,9 +16,9 @@ class Passenger {
|
|||||||
val pass = provideNullable(1)
|
val pass = provideNullable(1)
|
||||||
if (1 == 2) {
|
if (1 == 2) {
|
||||||
assert(pass != null)
|
assert(pass != null)
|
||||||
accept2(pass as PassChild?)
|
accept2((pass as PassChild?)!!)
|
||||||
}
|
}
|
||||||
accept2(pass as PassChild?)
|
accept2((pass as PassChild?)!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept1(p: 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);
|
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")
|
@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");
|
||||||
|
|||||||
@@ -3517,6 +3517,30 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
|
|||||||
doTest(fileName);
|
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")
|
@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