diff --git a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessor.kt index 77830d28874..7386c950bc8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessor.kt @@ -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) } diff --git a/j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt index f00a70be78b..41016814af9 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/CodeConverter.kt @@ -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 diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt index a491fd9698e..0a12b1976d1 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt @@ -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() diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/Expressions.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/Expressions.kt index eba58d2c442..e3acf6f0e2e 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/Expressions.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/Expressions.kt @@ -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() { diff --git a/j2k/testData/fileOrElement/assertStatement/assertNotNull.kt b/j2k/testData/fileOrElement/assertStatement/assertNotNull.kt index 5f89f3a2754..bc0a465253c 100644 --- a/j2k/testData/fileOrElement/assertStatement/assertNotNull.kt +++ b/j2k/testData/fileOrElement/assertStatement/assertNotNull.kt @@ -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? diff --git a/j2k/testData/fileOrElement/nullability/notNullCast.kt b/j2k/testData/fileOrElement/nullability/notNullCast.kt index b81a0db3eee..aa27ae1e729 100644 --- a/j2k/testData/fileOrElement/nullability/notNullCast.kt +++ b/j2k/testData/fileOrElement/nullability/notNullCast.kt @@ -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) { diff --git a/j2k/testData/fileOrElement/nullability/nullableInitializer1.java b/j2k/testData/fileOrElement/nullability/nullableInitializer1.java new file mode 100644 index 00000000000..afe81569341 --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableInitializer1.java @@ -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); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/nullability/nullableInitializer1.kt b/j2k/testData/fileOrElement/nullability/nullableInitializer1.kt new file mode 100644 index 00000000000..20ee3486e90 --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableInitializer1.kt @@ -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] + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/nullability/nullableInitializer2.java b/j2k/testData/fileOrElement/nullability/nullableInitializer2.java new file mode 100644 index 00000000000..e78fcb05898 --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableInitializer2.java @@ -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); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/nullability/nullableInitializer2.kt b/j2k/testData/fileOrElement/nullability/nullableInitializer2.kt new file mode 100644 index 00000000000..c3e663487dd --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableInitializer2.kt @@ -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] + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/nullability/nullableInitializer3.java b/j2k/testData/fileOrElement/nullability/nullableInitializer3.java new file mode 100644 index 00000000000..b19f2c7d97e --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableInitializer3.java @@ -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); + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/nullability/nullableInitializer3.kt b/j2k/testData/fileOrElement/nullability/nullableInitializer3.kt new file mode 100644 index 00000000000..55039f75fb0 --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableInitializer3.kt @@ -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] + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/nullability/nullableInitializer4.java b/j2k/testData/fileOrElement/nullability/nullableInitializer4.java new file mode 100644 index 00000000000..28c9133c932 --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableInitializer4.java @@ -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); + } + } +} \ No newline at end of file diff --git a/j2k/testData/fileOrElement/nullability/nullableInitializer4.kt b/j2k/testData/fileOrElement/nullability/nullableInitializer4.kt new file mode 100644 index 00000000000..bce8164be9e --- /dev/null +++ b/j2k/testData/fileOrElement/nullability/nullableInitializer4.kt @@ -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] + } + } +} \ No newline at end of file diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java index 5adc9aec53c..0e5cc53a351 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java @@ -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"); diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java index dafcc87fe01..74359756b27 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.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");