J2K: Get nullability from variable initializer when converting call

#KT-11534 In Progress
This commit is contained in:
Natalia Ukhorskaya
2016-03-25 19:49:49 +03:00
parent f3a3c0886f
commit 98c3b39f7e
16 changed files with 257 additions and 9 deletions
@@ -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
View File
@@ -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");