From 470027bfd8fe72b34d8db8b4a6239df35a232313 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Fri, 25 Mar 2016 16:28:59 +0300 Subject: [PATCH] J2K: specify type for variables with anonymous type if they have write accesses --- Changelog.md | 1 + j2k/src/org/jetbrains/kotlin/j2k/Converter.kt | 27 +++++++++++++- .../jetbrains/kotlin/j2k/ReferenceSearcher.kt | 8 ++++ j2k/src/org/jetbrains/kotlin/j2k/ast/Types.kt | 2 + .../fileOrElement/field/specifyType.java | 33 +++++++++++++++++ .../fileOrElement/field/specifyType.kt | 37 +++++++++++++++++++ 6 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 9ef7a76d52c..e10651c1689 100644 --- a/Changelog.md +++ b/Changelog.md @@ -23,6 +23,7 @@ - Place comments from the middle of the call to the end - Drop line breaks between operator arguments (except '+', "-", "&&" and "||") - Add non-null assertions on call site for non-null parameters +- Specify type for variables with anonymous type if they have write accesses ### Tools. Android - Fixed sequential build with kapt and stubs enabled when Kotlin source file was modified and no Java source files were modified diff --git a/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt b/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt index 59e1e8efe2b..e2a044fba2d 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/Converter.kt @@ -451,13 +451,36 @@ class Converter private constructor( } } + val initializerType = createDefaultCodeConverter().convertedExpressionType(initializer, variable.type) + // do not add explicit type when initializer is not resolved, let user add it if really needed + if (initializerType is ErrorType) return false + + if (shouldSpecifyTypeForAnonymousType(variable, initializerType)) return true + if (canChangeType) return false - var initializerType = createDefaultCodeConverter().convertedExpressionType(initializer, variable.type) - if (initializerType is ErrorType) return false // do not add explicit type when initializer is not resolved, let user add it if really needed return type != initializerType } + // add explicit type when initializer has anonymous type, + // the variable is private or local and + // it has write accesses or is stored to another variable + private fun shouldSpecifyTypeForAnonymousType(variable: PsiVariable, initializerType: Type): Boolean { + if (initializerType !is ClassType || !initializerType.isAnonymous()) return false + + val scope: PsiElement? = + when { + variable is PsiField && variable.hasModifierProperty(PsiModifier.PRIVATE) -> variable.containingClass + variable is PsiLocalVariable -> variable.getContainingMethod() + else -> null + } + + if (scope == null) return false + + return variable.hasWriteAccesses(referenceSearcher, scope) || + variable.isInVariableInitializer(referenceSearcher, scope) + } + fun convertMethod( method: PsiMethod, fieldsToDrop: MutableSet?, diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ReferenceSearcher.kt b/j2k/src/org/jetbrains/kotlin/j2k/ReferenceSearcher.kt index c9b6c0ac2a5..8b825231cea 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ReferenceSearcher.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ReferenceSearcher.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.j2k import com.intellij.psi.* +import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiUtil interface ReferenceSearcher { @@ -67,6 +68,13 @@ fun PsiField.isVar(searcher: ReferenceSearcher): Boolean { fun PsiVariable.hasWriteAccesses(searcher: ReferenceSearcher, scope: PsiElement?): Boolean = if (scope != null) searcher.findVariableUsages(this, scope).any { PsiUtil.isAccessedForWriting(it) } else false +fun PsiVariable.isInVariableInitializer(searcher: ReferenceSearcher, scope: PsiElement?): Boolean { + return if (scope != null) searcher.findVariableUsages(this, scope).any { + val parent = PsiTreeUtil.skipParentsOfType(it, PsiParenthesizedExpression::class.java) + parent is PsiVariable && parent.initializer == it + } else false +} + object EmptyReferenceSearcher: ReferenceSearcher { override fun findLocalUsages(element: PsiElement, scope: PsiElement): Collection = emptyList() override fun hasInheritors(`class`: PsiClass) = false diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/Types.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/Types.kt index 779af4c1ffb..65b7da5627d 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/Types.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/Types.kt @@ -100,6 +100,8 @@ class ClassType(val referenceElement: ReferenceElement, nullability: Nullability override fun toNotNullType(): Type = ClassType(referenceElement, Nullability.NotNull, settings).assignPrototypesFrom(this) override fun toNullableType(): Type = ClassType(referenceElement, Nullability.Nullable, settings).assignPrototypesFrom(this) + + fun isAnonymous() = referenceElement.name.isEmpty } class ArrayType(val elementType: Type, nullability: Nullability, settings: ConverterSettings) diff --git a/j2k/testData/fileOrElement/field/specifyType.java b/j2k/testData/fileOrElement/field/specifyType.java index df0dfd3b35e..80a89a1aded 100644 --- a/j2k/testData/fileOrElement/field/specifyType.java +++ b/j2k/testData/fileOrElement/field/specifyType.java @@ -25,4 +25,37 @@ class A { field9 = null; field10 = null; } + + interface I + + private I anonymous = new I() { + }; + + public I anonymous2 = new I() { + }; + + private I anonymous3 = new I() { + }; + + private I iimpl = anonymous; + + void testAnonymousObject(Object i) { + if (true) { + iimpl = (I) i; + } + else if (true) { + anonymous3 = (I) i; + } + + I anonymousLocal1 = new I() { + }; + + I anonymousLocal2 = new I() { + }; + + I iimpl = anonymousLocal1; + if (true) { + anonymousLocal2 = (I) i; + } + } } \ No newline at end of file diff --git a/j2k/testData/fileOrElement/field/specifyType.kt b/j2k/testData/fileOrElement/field/specifyType.kt index fcaaa51d9b5..9f35d9038ad 100644 --- a/j2k/testData/fileOrElement/field/specifyType.kt +++ b/j2k/testData/fileOrElement/field/specifyType.kt @@ -26,4 +26,41 @@ internal class A { field9 = null field10 = null } + + internal interface I + + private val anonymous: I = object : I { + + } + + var anonymous2: I = object : I { + + } + + private var anonymous3: I = object : I { + + } + + private var iimpl = anonymous + + fun testAnonymousObject(i: Any) { + if (true) { + iimpl = i as I + } else if (true) { + anonymous3 = i as I + } + + val anonymousLocal1: I = object : I { + + } + + var anonymousLocal2: I = object : I { + + } + + val iimpl = anonymousLocal1 + if (true) { + anonymousLocal2 = i as I + } + } } \ No newline at end of file