New J2K: handle correctly short names which are imported by default in kotlin (like List or Result)

Kotlin default import inserter was unable to correctly insert such imports

#KT-34165 fixed
This commit is contained in:
Ilya Kirillov
2019-10-04 12:30:02 +03:00
parent 81341e3fd3
commit f950a0246c
53 changed files with 181 additions and 40 deletions
@@ -6,18 +6,28 @@
package org.jetbrains.kotlin.nj2k
import com.intellij.psi.CommonClassNames
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.load.java.NULLABILITY_ANNOTATIONS
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
class JKImportStorage {
class JKImportStorage(languageSettings: LanguageVersionSettings) {
private val imports = mutableSetOf<FqName>()
private val defaultImports: Set<FqName> =
JvmPlatformAnalyzerServices.getDefaultImports(
languageSettings,
includeLowPriorityImports = true
).mapNotNull { import ->
if (import.isAllUnder) null else import.fqName
}.toSet()
fun addImport(import: FqName) {
if (isImportNeeded(import)) {
if (isImportNeeded(import, allowSingleIdentifierImport = false)) {
imports += import
}
}
@@ -28,23 +38,17 @@ class JKImportStorage {
fun getImports(): Set<FqName> = imports
fun isImportNeeded(fqName: FqName, allowSingleIdentifierImport: Boolean = false): Boolean {
if (!allowSingleIdentifierImport && fqName.asString().count { it == '.' } < 1) return false
if (fqName in NULLABILITY_ANNOTATIONS) return false
if (fqName in defaultImports) return false
return true
}
fun isImportNeeded(fqName: String, allowSingleIdentifierImport: Boolean = false): Boolean =
isImportNeeded(FqName(fqName), allowSingleIdentifierImport)
companion object {
fun isImportNeeded(fqName: FqName): Boolean {
if (fqName in NULLABILITY_ANNOTATIONS) return false
if (fqName in JAVA_TYPE_WRAPPERS_WHICH_HAVE_CONFLICTS_WITH_KOTLIN_ONES) return false
return true
}
fun isImportNeededForCall(qualifiedExpression: KtQualifiedExpression): Boolean {
val shortName = qualifiedExpression.getCalleeExpressionIfAny()?.text ?: return true
if (shortName !in SHORT_NAMES) return true
val fqName = qualifiedExpression.selectorExpression?.mainReference?.resolve()?.getKotlinFqName() ?: return true
return isImportNeeded(fqName)
}
fun isImportNeeded(fqName: String): Boolean = isImportNeeded(FqName(fqName))
private val JAVA_TYPE_WRAPPERS_WHICH_HAVE_CONFLICTS_WITH_KOTLIN_ONES = setOf(
FqName(CommonClassNames.JAVA_LANG_BYTE),
FqName(CommonClassNames.JAVA_LANG_SHORT),
@@ -53,9 +57,15 @@ class JKImportStorage {
FqName(CommonClassNames.JAVA_LANG_DOUBLE)
)
private val SHORT_NAMES =
(NULLABILITY_ANNOTATIONS + JAVA_TYPE_WRAPPERS_WHICH_HAVE_CONFLICTS_WITH_KOTLIN_ONES)
.map { it.shortName().asString() }
.toSet()
private val SHORT_NAMES = JAVA_TYPE_WRAPPERS_WHICH_HAVE_CONFLICTS_WITH_KOTLIN_ONES
.map { it.shortName().identifier }
.toSet()
fun isImportNeededForCall(qualifiedExpression: KtQualifiedExpression): Boolean {
val shortName = qualifiedExpression.getCalleeExpressionIfAny()?.text ?: return true
if (shortName !in SHORT_NAMES) return true
val fqName = qualifiedExpression.selectorExpression?.mainReference?.resolve()?.getKotlinFqName() ?: return true
return fqName !in JAVA_TYPE_WRAPPERS_WHICH_HAVE_CONFLICTS_WITH_KOTLIN_ONES
}
}
}
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.nj2k
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.Module
@@ -27,13 +26,18 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Computable
import com.intellij.psi.*
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.j2k.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.printing.JKCodeBuilder
import org.jetbrains.kotlin.nj2k.types.JKTypeFactory
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportList
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.resolve.ImportPath
class NewJavaToKotlinConverter(
val project: Project,
@@ -66,12 +70,11 @@ class NewJavaToKotlinConverter(
val kotlinFile = ApplicationManager.getApplication().runReadAction(Computable {
KtPsiFactory(project).createFileWithLightClassSupport("dummy.kt", result!!.text, files[i])
})
runBlocking(EDT.ModalityStateElement(ModalityState.defaultModalityState())) {
withContext(EDT) {
CommandProcessor.getInstance().runUndoTransparentAction {
result!!.importsToAdd.forEach {
postProcessor.insertImport(kotlinFile, it)
}
ApplicationManager.getApplication().invokeAndWait {
CommandProcessor.getInstance().runUndoTransparentAction {
runWriteAction {
kotlinFile.addImports(result!!.importsToAdd)
}
}
}
@@ -95,6 +98,33 @@ class NewJavaToKotlinConverter(
}
}
private fun KtFile.addImports(imports: Collection<FqName>) {
val factory = KtPsiFactory(this)
var importList = importList
for (import in imports) {
val importDirective = factory.createImportDirective(ImportPath(import, isAllUnder = false))
if (importList == null) {
importList = addImportList(importDirective.parent as KtImportList)
} else {
importList.add(importDirective)
}
}
}
private fun KtFile.addImportList(importList: KtImportList): KtImportList {
if (packageDirective != null) {
return addAfter(importList, packageDirective) as KtImportList
}
val firstDeclaration = findChildByClass(KtDeclaration::class.java)
if (firstDeclaration != null) {
return addBefore(importList, firstDeclaration) as KtImportList
}
return add(importList) as KtImportList
}
override fun elementsToKotlin(inputElements: List<PsiElement>, processor: WithProgressProcessor): Result {
val phaseDescription = "Converting Java code to Kotlin code"
val contextElement = inputElements.firstOrNull() ?: return Result(emptyList(), null, null)
@@ -102,9 +132,14 @@ class NewJavaToKotlinConverter(
val typeFactory = JKTypeFactory(symbolProvider)
symbolProvider.typeFactory = typeFactory
symbolProvider.preBuildTree(inputElements)
val importStorage = JKImportStorage()
val treeBuilder = JavaToJKTreeBuilder(symbolProvider, typeFactory, converterServices, importStorage)
val languageVersion = when {
contextElement.isPhysical -> contextElement.languageVersionSettings
else -> LanguageVersionSettingsImpl.DEFAULT
}
val importStorage = JKImportStorage(languageVersion)
val treeBuilder = JavaToJKTreeBuilder(symbolProvider, typeFactory, converterServices, importStorage)
// we want to leave all imports as is in the case when user is converting only imports
val saveImports = inputElements.all { element ->
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.nj2k.conversions
import org.jetbrains.kotlin.nj2k.JKImportStorage
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
import org.jetbrains.kotlin.nj2k.tree.JKImportList
import org.jetbrains.kotlin.nj2k.tree.JKTreeElement
@@ -14,7 +13,7 @@ class FilterImportsConversion(context: NewJ2kConverterContext) : RecursiveApplic
override fun applyToElement(element: JKTreeElement): JKTreeElement {
if (element !is JKImportList) return recurse(element)
element.imports = element.imports.filter { import ->
JKImportStorage.isImportNeeded(import.name.value)
context.importStorage.isImportNeeded(import.name.value, allowSingleIdentifierImport = true)
}
return recurse(element)
}
@@ -6,9 +6,9 @@
package org.jetbrains.kotlin.nj2k.printing
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.util.Processor
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.nj2k.JKImportStorage
import org.jetbrains.kotlin.nj2k.escaped
@@ -77,7 +77,13 @@ private class CanBeShortenedCache(project: Project) {
}
fun canBeShortened(symbol: JKClassSymbol): Boolean = canBeShortenedCache.getOrPut(symbol.name) {
!shortNameCache.processClassesWithName(symbol.name, { false }, searchScope, null)
var symbolsWithSuchNameCount = 0
val processSymbol = { _: PsiClass ->
symbolsWithSuchNameCount++
symbolsWithSuchNameCount <= 1 //stop if met more than one symbol with such name
}
shortNameCache.processClassesWithName(symbol.name, processSymbol, searchScope, null)
symbolsWithSuchNameCount == 1
}
companion object {
@@ -1,6 +1,7 @@
package to
import java.io.File
import java.util.ArrayList
class JavaClass {
@@ -1,5 +1,7 @@
package to
import java.util.ArrayList
class JavaClass {
internal fun foo() {
@@ -1,3 +1,5 @@
import java.util.HashSet
internal class Foo {
fun foo(o: HashSet<*>) {
val o2: HashSet<*> = o
@@ -1,3 +1,5 @@
import java.util.HashSet
internal class Foo {
fun foo(o: HashSet<*>) {
var foo = 0
+2
View File
@@ -1,5 +1,7 @@
package demo
import java.util.HashMap
internal class Test {
constructor() {}
constructor(s: String?) {}
+2
View File
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class A {
private val list1 = ArrayList<String>()
private val list2: MutableList<String> = ArrayList()
+2
View File
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class A {
private val field1: List<String> = ArrayList()
val field2: List<String> = ArrayList()
+2
View File
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class C {
fun foo1(list: MutableList<String?>) {
for (i in list.indices) {
+2
View File
@@ -1,3 +1,5 @@
import java.util.HashMap
internal object Test {
@JvmStatic
fun main(args: Array<String>) {
+2
View File
@@ -1,6 +1,8 @@
// ERROR: The integer literal does not conform to the expected type CapturedType(*)
// ERROR: The integer literal does not conform to the expected type CapturedType(*)
// ERROR: Type argument is not within its bounds: should be subtype of 'String?'
import java.util.HashMap
internal class G<T : String?>(t: T)
class Java {
internal fun test() {
+2
View File
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal object A {
@JvmStatic
fun main(args: Array<String>) {
+2
View File
@@ -1,5 +1,7 @@
package test
import java.util.HashMap
class TestPrimitiveFromMap {
fun foo(map: HashMap<String?, Int?>): Int {
return map["zzz"]!!
+2
View File
@@ -1,3 +1,5 @@
import java.util.HashMap
class TestMethodReference {
private val hashMap = HashMap<String, String>()
fun update(key: String, msg: String) {
+2
View File
@@ -1,3 +1,5 @@
import java.util.HashMap
class TestMethodReference {
private val hashMap = HashMap<String, String>()
fun update(key: String, msg: String) {
+1
View File
@@ -1,4 +1,5 @@
import Foo.SomeClass
import java.util.ArrayList
internal interface FooInterface {
fun foo(): ArrayList<out SomeClass?>?
+2
View File
@@ -1,3 +1,5 @@
import java.util.HashMap
class TestClass {
private val hashMap: Map<String, List<Int>> = HashMap()
}
+1
View File
@@ -1,3 +1,4 @@
import java.util.ArrayList
import java.util.function.Consumer
internal class Test {
+2
View File
@@ -1,5 +1,7 @@
package demo
import demo.One
internal class Container {
var myString = "1"
}
+2
View File
@@ -1,5 +1,7 @@
package demo
import demo.One
internal class Container {
var myInt = 1
}
+2
View File
@@ -1,5 +1,7 @@
package demo
import demo.One
internal class Container {
var myInt = 1
}
+2
View File
@@ -1,5 +1,7 @@
package demo
import demo.One
internal class Container {
var myInt = 1
}
+2
View File
@@ -1,5 +1,7 @@
package demo
import demo.One
internal class Container {
var myBoolean = true
}
+2
View File
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class A {
var list: List<String> = ArrayList()
}
+1
View File
@@ -1,3 +1,4 @@
import java.util.Comparator
import java.util.stream.Collectors
internal class Test {
@@ -1,3 +1,4 @@
import java.util.Comparator
import java.util.stream.Collectors
internal class Test {
+2
View File
@@ -1,4 +1,6 @@
// ERROR: Unresolved reference: LinkedList
import java.util.ArrayList
class ForEach {
fun test() {
val xs = ArrayList<Any>()
+2
View File
@@ -1,4 +1,6 @@
// ERROR: Unresolved reference: LinkedList
import java.util.ArrayList
class Lists {
fun test() {
val xs: MutableList<Any?> = ArrayList()
@@ -1,3 +1,5 @@
import java.util.HashMap
internal enum class E {
A, B, C
}
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class A {
fun createCollection(): MutableCollection<String> {
return ArrayList()
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class A {
private val collection: MutableCollection<String>
fun createCollection(): MutableCollection<String> {
+2
View File
@@ -1,3 +1,5 @@
import java.util.HashMap
internal class IteratorTest {
var mutableMap1: MutableMap<String, String> = HashMap()
var mutableMap2: MutableMap<String, String> = HashMap()
+2
View File
@@ -1,3 +1,5 @@
import java.util.ArrayList
class TestMutableCollection {
val list: MutableList<String> = ArrayList()
fun test() {
@@ -1,3 +1,5 @@
import java.util.ArrayList
class Test {
internal var list: List<MutableList<Int>> = ArrayList()
fun test() {
@@ -1,3 +1,5 @@
import java.util.ArrayList
class Owner {
var list: MutableList<String> = ArrayList()
}
@@ -1,5 +1,7 @@
package test
import java.util.ArrayList
internal class User {
fun main() {
val list: List<*> = ArrayList<Any?>()
@@ -1,7 +1,9 @@
package test
import java.util.ArrayList
internal class User {
fun main() {
val list: List<*> = ArrayList<Any?>()
}
}
}
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class User {
fun main() {
val list: List<String> = ArrayList()
@@ -1,5 +1,7 @@
package org.test
import java.util.ArrayList
internal class Member
internal class User {
fun main() {
+2
View File
@@ -1,5 +1,7 @@
package test
import java.util.ArrayList
class Test {
private var myProp: String? = null
private var myIntProp: Int? = null
+1
View File
@@ -1,5 +1,6 @@
import javaApi.JavaClass
import kotlinApi.KotlinClass
import java.util.HashMap
internal class X {
operator fun get(index: Int): Int {
@@ -1,3 +1,5 @@
import java.util.HashMap
internal class Test {
fun test(map: HashMap<String?, String?>) {
map.forEach { (key: String?, value: String?) -> foo(key, value) }
+2
View File
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class C<T> {
fun foo1(src: Collection<T>) {
val t = src.iterator().next()
+2
View File
@@ -1,5 +1,7 @@
package demo
import java.util.HashMap
internal class Test {
fun main() {
val commonMap = HashMap<String, Int>()
+2
View File
@@ -1,5 +1,7 @@
package demo
import java.util.ArrayList
internal class Test {
fun main() {
val common: List<String> = ArrayList()
@@ -1,3 +1,5 @@
import java.util.ArrayList
internal class A {
private val field1: List<String> = ArrayList()
val field2: List<String> = ArrayList()
@@ -1,5 +1,7 @@
package demo
import java.util.ArrayList
class TestJava {
fun f(result: Function1<String?, Unit>) {
result.invoke("a")
@@ -1,2 +1,4 @@
import java.util.ArrayList
internal interface I<T : List<Iterator<String?>?>?>
internal class C : I<ArrayList<Iterator<String?>?>?>
+2
View File
@@ -1,3 +1,5 @@
import java.util.HashMap
internal class A {
fun foo() {
val map1 = getMap1<String, Int>()
+2 -1
View File
@@ -2,7 +2,8 @@
// ERROR: Type mismatch: inferred type is (CapturedType(*)!!..Any?) but String? was expected
// ERROR: Type mismatch: inferred type is Any? but String? was expected
// ERROR: Type mismatch: inferred type is (CapturedType(*)!!..Any?) but String? was expected
// ERROR: Type mismatch: inferred type is kotlin.collections.HashMap<Any?, Any?> /* = java.util.HashMap<Any?, Any?> */ but Map<String?, String?> was expected
// ERROR: Type mismatch: inferred type is HashMap<Any?, Any?> but Map<String?, String?> was expected
import java.util.HashMap
import java.util.Properties
internal object A {