Create from Usage: Approximate unresolvable types

#KT-7722 Fixed
(cherry picked from commit 917cd22)
This commit is contained in:
Alexey Sedunov
2016-06-29 13:06:13 +03:00
parent a1e86e8bfa
commit ae06f01c95
10 changed files with 127 additions and 33 deletions
+1
View File
@@ -117,6 +117,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
###### Issues fixed
- [`KT-7722`](https://youtrack.jetbrains.com/issue/KT-7722) Approximate unresolvable types in "Create from Usage" quickfixes
- [`KT-11115`](https://youtrack.jetbrains.com/issue/KT-11115) Implement Members: Fix base member detection when abstract and non-abstract members with matching signatures are inherited from an interface
###### New features
@@ -31,6 +31,8 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.kotlin.utils.addToStdlib.singletonList
fun KotlinType.approximateFlexibleTypes(preferNotNull: Boolean = false): KotlinType {
if (isDynamic()) return this
@@ -100,4 +102,31 @@ fun KotlinType.anonymousObjectSuperTypeOrNull(): KotlinType? {
return immediateSupertypes().firstOrNull() ?: classDescriptor.builtIns.anyType
}
return null
}
fun KotlinType.getResolvableApproximations(scope: LexicalScope?, checkTypeParameters: Boolean): Sequence<KotlinType> {
return (singletonList() + TypeUtils.getAllSupertypes(this))
.asSequence()
.filter { it.isResolvableInScope(scope, checkTypeParameters) }
.mapNotNull mapArgs@ {
val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { it.type.isResolvableInScope(scope, checkTypeParameters) }
if (resolvableArgs.containsAll(it.arguments)) return@mapArgs it
val newArguments = (it.arguments zip it.constructor.parameters).map {
val (arg, param) = it
when {
arg in resolvableArgs -> arg
arg.projectionKind == Variance.OUT_VARIANCE ||
param.variance == Variance.OUT_VARIANCE -> TypeProjectionImpl(
arg.projectionKind,
arg.type.approximateWithResolvableType(scope, checkTypeParameters)
)
else -> return@mapArgs null
}
}
it.replace(newArguments)
}
}
@@ -33,6 +33,8 @@ import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.isResolvableInScope
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.getResolvableApproximations
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.endOffset
@@ -40,9 +42,9 @@ import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.kotlin.utils.addToStdlib.singletonList
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isFlexible
import org.jetbrains.kotlin.utils.ifEmpty
class SpecifyTypeExplicitlyIntention :
@@ -112,28 +114,7 @@ class SpecifyTypeExplicitlyIntention :
val resolutionFacade = contextElement.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(contextElement, BodyResolveMode.PARTIAL)
val scope = contextElement.getResolutionScope(bindingContext, resolutionFacade)
val types = (exprType.singletonList() + TypeUtils.getAllSupertypes(exprType))
.filter { it.isResolvableInScope(scope, true) }
.mapNotNull mapArgs@ {
val resolvableArgs = it.arguments.filterTo(SmartSet.create()) { it.type.isResolvableInScope(scope, true) }
if (resolvableArgs.containsAll(it.arguments)) return@mapArgs it
val newArguments = (it.arguments zip it.constructor.parameters).map {
val (arg, param) = it
when {
arg in resolvableArgs -> arg
arg.projectionKind == Variance.OUT_VARIANCE ||
param.variance == Variance.OUT_VARIANCE -> TypeProjectionImpl(
arg.projectionKind,
arg.type.approximateWithResolvableType(scope, true)
)
else -> return@mapArgs null
}
}
it.replace(newArguments)
}
.ifEmpty { return null }
val types = exprType.getResolvableApproximations(scope, true).toList().ifEmpty { return null }
return object : ChooseValueExpression<KotlinType>(types, types.first()) {
override fun getLookupString(element: KotlinType) = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(element)
override fun getResult(element: KotlinType) = IdeDescriptorRenderers.SOURCE_CODE.renderType(element)
@@ -146,7 +146,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
private val typeCandidates = HashMap<TypeInfo, List<TypeCandidate>>()
var placement: CallablePlacement by Delegates.notNull()
var placement: CallablePlacement? = null
private val elementsToShorten = ArrayList<KtElement>()
@@ -18,17 +18,20 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder
import com.intellij.psi.PsiElement
import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.ClassInfo
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.getResolvableApproximations
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.types.typeUtil.supertypes
import java.util.*
/**
@@ -86,14 +89,38 @@ abstract class TypeInfo(val variance: Variance) {
open fun getPossibleNamesFromExpression(bindingContext: BindingContext): Array<String> = ArrayUtil.EMPTY_STRING_ARRAY
abstract fun getPossibleTypes(builder: CallableBuilder): List<KotlinType>
private fun getScopeForTypeApproximation(config: CallableBuilderConfiguration, placement: CallablePlacement?): LexicalScope? {
if (placement == null) return config.originalElement.getResolutionScope()
val containingElement = when (placement) {
is CallablePlacement.NoReceiver -> {
placement.containingElement
}
is CallablePlacement.WithReceiver -> {
val receiverClassDescriptor =
placement.receiverTypeCandidate.theType.constructor.declarationDescriptor
val classDeclaration = receiverClassDescriptor?.let { DescriptorToSourceUtils.getSourceFromDescriptor(it) }
if (!config.isExtension && classDeclaration != null) classDeclaration else config.currentFile
}
else -> throw IllegalArgumentException("Unexpected placement: $placement")
}
return when (containingElement) {
is KtClassOrObject -> (containingElement.resolveToDescriptor() as? ClassDescriptorWithResolutionScopes)?.scopeForMemberDeclarationResolution
is KtBlockExpression -> (containingElement.statements.firstOrNull() ?: containingElement).getResolutionScope()
is KtElement -> containingElement.getContainingKtFile().getResolutionScope()
else -> null
}
}
protected fun KotlinType?.getPossibleSupertypes(variance: Variance, callableBuilder: CallableBuilder): List<KotlinType> {
if (this == null || ErrorUtils.containsErrorType(this)) {
return Collections.singletonList(callableBuilder.currentFileModule.builtIns.anyType)
}
val single = Collections.singletonList(this)
val scope = getScopeForTypeApproximation(callableBuilder.config, callableBuilder.placement)
val approximations = getResolvableApproximations(scope, false)
return when (variance) {
Variance.IN_VARIANCE -> single + supertypes()
else -> single
Variance.IN_VARIANCE -> approximations.toList()
else -> listOf(approximations.firstOrNull() ?: this)
}
}
}
@@ -0,0 +1,8 @@
// "Create extension function 'List<Int>.foo'" "true"
open class A
fun main(args: Array<String>) {
val list = listOf(1, 2, 4, 5)
list.<caret>foo { object : A() {} }
}
@@ -0,0 +1,12 @@
// "Create extension function 'List<Int>.foo'" "true"
open class A
fun main(args: Array<String>) {
val list = listOf(1, 2, 4, 5)
list.foo { object : A() {} }
}
fun <E> List<E>.foo(function: () -> A) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@@ -0,0 +1,10 @@
// "Create extension function 'List<Int>.foo'" "true"
open class A
fun main(args: Array<String>) {
class Local : A()
val list = listOf(1, 2, 4, 5)
list.<caret>foo { Local() }
}
@@ -0,0 +1,14 @@
// "Create extension function 'List<Int>.foo'" "true"
open class A
fun main(args: Array<String>) {
class Local : A()
val list = listOf(1, 2, 4, 5)
list.foo { Local() }
}
fun <E> List<E>.foo(function: () -> A) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@@ -1956,6 +1956,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createFunction/call"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("approximateAnonymousObjectRuntime.kt")
public void testApproximateAnonymousObjectRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/approximateAnonymousObjectRuntime.kt");
doTest(fileName);
}
@TestMetadata("approximateLocalClassRuntime.kt")
public void testApproximateLocalClassRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/approximateLocalClassRuntime.kt");
doTest(fileName);
}
@TestMetadata("argumentTypeMismatch.kt")
public void testArgumentTypeMismatch() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/argumentTypeMismatch.kt");