extensionsUtils.kt uses FuzzyType + more correct treatment of receiver nullability there

This commit is contained in:
Valentin Kipyatkov
2014-12-03 16:05:10 +03:00
parent 418b16e766
commit dfd5e773df
12 changed files with 143 additions and 111 deletions
@@ -36,6 +36,7 @@ import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.types.TypeUtils
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import org.jetbrains.jet.lexer.JetTokens
import org.jetbrains.jet.plugin.util.CallType
import org.jetbrains.jet.plugin.util.substituteExtensionIfCallable
public class ReferenceVariantsHelper(
@@ -43,12 +44,6 @@ public class ReferenceVariantsHelper(
private val visibilityFilter: (DeclarationDescriptor) -> Boolean
) {
public enum class CallType {
NORMAL
INFIX
SAFE
}
public data class ReceiversData(
public val receivers: Collection<ReceiverValue>,
public val callType: CallType
@@ -114,7 +109,7 @@ public class ReferenceVariantsHelper(
variant.getMemberScope().getDescriptorsFiltered(mask, nameFilter).filterTo(descriptors, ::filterIfInfix)
}
descriptors.addCallableExtensions(resolutionScope, receiverValue, dataFlowInfo, callType == CallType.INFIX, kindFilter, nameFilter)
descriptors.addCallableExtensions(resolutionScope, receiverValue, dataFlowInfo, callType, kindFilter, nameFilter)
}
return descriptors
@@ -132,7 +127,7 @@ public class ReferenceVariantsHelper(
for (descriptor in resolutionScope.getDescriptorsFiltered(kindFilter, nameFilter)) {
if (descriptor is CallableDescriptor && descriptor.getExtensionReceiverParameter() != null) {
descriptorsSet.addAll(descriptor.substituteExtensionIfCallable(receiverValues, context, dataFlowInfo, false))
descriptorsSet.addAll(descriptor.substituteExtensionIfCallable(receiverValues, context, dataFlowInfo, CallType.NORMAL))
}
else {
descriptorsSet.add(descriptor)
@@ -165,42 +160,17 @@ public class ReferenceVariantsHelper(
return type
}
private fun getReferenceVariantsReceiver(expression: JetSimpleNameExpression): Pair<JetExpression, CallType>? {
val receiverExpression = expression.getReceiverExpression() ?: return null
val parent = expression.getParent()
val callType = when (parent) {
is JetBinaryExpression -> CallType.INFIX
is JetCallExpression -> {
if ((parent.getParent() as JetQualifiedExpression).getOperationSign() == JetTokens.SAFE_ACCESS)
CallType.SAFE
else
CallType.NORMAL
}
is JetQualifiedExpression -> {
if (parent.getOperationSign() == JetTokens.SAFE_ACCESS)
CallType.SAFE
else
CallType.NORMAL
}
else -> return null
}
return receiverExpression to callType
}
private fun MutableCollection<DeclarationDescriptor>.addCallableExtensions(
resolutionScope: JetScope,
receiver: ReceiverValue,
dataFlowInfo: DataFlowInfo,
isInfixCall: Boolean,
callType: CallType,
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
) {
if (!kindFilter.excludes.contains(DescriptorKindExclude.Extensions)) {
for (callable in resolutionScope.getDescriptorsFiltered(kindFilter.exclude(DescriptorKindExclude.NonExtensions), nameFilter)) {
addAll((callable as CallableDescriptor).substituteExtensionIfCallable(receiver, isInfixCall, context, dataFlowInfo))
addAll((callable as CallableDescriptor).substituteExtensionIfCallable(receiver, callType, context, dataFlowInfo))
}
}
}
@@ -212,4 +182,31 @@ public class ReferenceVariantsHelper(
val resolutionScope = context[BindingContext.RESOLUTION_SCOPE, expression] ?: return listOf()
return resolutionScope.getDescriptorsFiltered(DescriptorKindFilter.PACKAGES, nameFilter).filter(visibilityFilter)
}
class object {
public fun getReferenceVariantsReceiver(expression: JetSimpleNameExpression): Pair<JetExpression, CallType>? {
val receiverExpression = expression.getReceiverExpression() ?: return null
val parent = expression.getParent()
val callType = when (parent) {
is JetBinaryExpression -> CallType.INFIX
is JetCallExpression -> {
if ((parent.getParent() as JetQualifiedExpression).getOperationSign() == JetTokens.SAFE_ACCESS)
CallType.SAFE
else
CallType.NORMAL
}
is JetQualifiedExpression -> {
if (parent.getOperationSign() == JetTokens.SAFE_ACCESS)
CallType.SAFE
else
CallType.NORMAL
}
else -> return null
}
return receiverExpression to callType
}
}
}
@@ -26,8 +26,6 @@ import java.util.LinkedHashMap
import org.jetbrains.jet.lang.types.Variance
import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintPosition
import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintsUtil
import org.jetbrains.jet.plugin.util.makeNotNullable
import org.jetbrains.jet.plugin.util.makeNullable
import org.jetbrains.jet.lang.types.TypeSubstitutor
import org.jetbrains.jet.lang.types.typeUtil.isSubtypeOf
@@ -36,7 +34,6 @@ fun CallableDescriptor.fuzzyReturnType(): FuzzyType? {
return FuzzyType(returnType, getTypeParameters())
}
//TODO: replace code in extensionsUtils.kt with use of FuzzyType
fun CallableDescriptor.fuzzyExtensionReceiverType(): FuzzyType? {
val receiverParameter = getExtensionReceiverParameter()
return if (receiverParameter != null) FuzzyType(receiverParameter.getType(), getTypeParameters()) else null
@@ -64,10 +61,30 @@ class FuzzyType(
}
}
public fun matchedSubstitutor(expectedType: JetType): TypeSubstitutor? {
public fun checkIsSubtypeOf(otherType: JetType): TypeSubstitutor?
= matchedSubstitutor(otherType, MatchKind.IS_SUBTYPE)
public fun checkIsSuperTypeOf(otherType: JetType): TypeSubstitutor?
= matchedSubstitutor(otherType, MatchKind.IS_SUPERTYPE)
private enum class MatchKind {
IS_SUBTYPE
IS_SUPERTYPE
}
private fun matchedSubstitutor(otherType: JetType, matchKind: MatchKind): TypeSubstitutor? {
if (type.isError()) return null
if (otherType.isError()) return null
fun JetType.checkInheritance(otherType: JetType): Boolean {
return when (matchKind) {
MatchKind.IS_SUBTYPE -> this.isSubtypeOf(otherType)
MatchKind.IS_SUPERTYPE -> otherType.isSubtypeOf(this)
}
}
if (usedTypeParameters == null || usedTypeParameters.isEmpty()) {
return if (type.isSubtypeOf(expectedType)) TypeSubstitutor.EMPTY else null
return if (type.checkInheritance(otherType)) TypeSubstitutor.EMPTY else null
}
val constraintSystem = ConstraintSystemImpl()
@@ -79,11 +96,15 @@ class FuzzyType(
}
constraintSystem.registerTypeVariables(typeVariables)
constraintSystem.addSubtypeConstraint(type, expectedType, ConstraintPosition.SPECIAL/*TODO?*/)
when (matchKind) {
MatchKind.IS_SUBTYPE -> constraintSystem.addSubtypeConstraint(type, otherType, ConstraintPosition.SPECIAL)
MatchKind.IS_SUPERTYPE -> constraintSystem.addSubtypeConstraint(otherType, type, ConstraintPosition.SPECIAL)
}
if (constraintSystem.getStatus().isSuccessful() && ConstraintsUtil.checkBoundsAreSatisfied(constraintSystem, true)) {
val substitutor = constraintSystem.getResultingSubstitutor()
val substitutedType = substitutor.substitute(type, Variance.INVARIANT/*TODO?*/)
return if (substitutedType.isSubtypeOf(expectedType)) substitutor else null
val substitutedType = substitutor.substitute(type, Variance.INVARIANT)
return if (substitutedType != null && substitutedType.checkInheritance(otherType)) substitutor else null
}
else {
return null
@@ -23,23 +23,19 @@ import org.jetbrains.jet.lang.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.jet.lang.resolve.scopes.JetScope
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor
import org.jetbrains.jet.lang.resolve.calls.smartcasts.SmartCastUtils
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintSystemImpl
import java.util.LinkedHashMap
import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor
import org.jetbrains.jet.lang.types.Variance
import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintPosition
import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintsUtil
import org.jetbrains.jet.utils.addIfNotNull
import java.util.HashSet
import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor
import org.jetbrains.jet.lang.types.TypeSubstitutor
import org.jetbrains.jet.lang.types.TypeUtils
public enum class CallType {
NORMAL
SAFE
INFIX
}
public fun CallableDescriptor.substituteExtensionIfCallable(receivers: Collection<ReceiverValue>,
context: BindingContext,
dataFlowInfo: DataFlowInfo,
isInfixCall: Boolean): Collection<CallableDescriptor> {
val stream = receivers.stream().flatMap { substituteExtensionIfCallable(it, isInfixCall, context, dataFlowInfo).stream() }
callType: CallType): Collection<CallableDescriptor> {
val stream = receivers.stream().flatMap { substituteExtensionIfCallable(it, callType, context, dataFlowInfo).stream() }
if (getTypeParameters().isEmpty()) { // optimization for non-generic callables
return stream.firstOrNull()?.let { listOf(it) } ?: listOf()
}
@@ -49,24 +45,36 @@ public fun CallableDescriptor.substituteExtensionIfCallable(receivers: Collectio
}
public fun CallableDescriptor.substituteExtensionIfCallableWithImplicitReceiver(scope: JetScope, context: BindingContext, dataFlowInfo: DataFlowInfo): Collection<CallableDescriptor>
= substituteExtensionIfCallable(scope.getImplicitReceiversHierarchy().map { it.getValue() }, context, dataFlowInfo, false)
= substituteExtensionIfCallable(scope.getImplicitReceiversHierarchy().map { it.getValue() }, context, dataFlowInfo, CallType.NORMAL)
public fun CallableDescriptor.substituteExtensionIfCallable(
receiver: ReceiverValue,
isInfixCall: Boolean,
callType: CallType,
bindingContext: BindingContext,
dataFlowInfo: DataFlowInfo
): Collection<CallableDescriptor> {
val receiverParameter = getExtensionReceiverParameter()!!
if (!receiver.exists()) return listOf()
if (isInfixCall && (this !is SimpleFunctionDescriptor || getValueParameters().size() != 1)) {
if (callType == CallType.INFIX && (this !is SimpleFunctionDescriptor || getValueParameters().size() != 1)) {
return listOf()
}
val substitutors = SmartCastUtils.getSmartCastVariants(receiver, bindingContext, dataFlowInfo)
.stream()
.map { checkReceiverResolution(it, receiverParameter, getTypeParameters()) }
var types = SmartCastUtils.getSmartCastVariants(receiver, bindingContext, dataFlowInfo).stream()
if (callType == CallType.SAFE) {
types = types.map { it.makeNotNullable() }
}
val extensionReceiverType = fuzzyExtensionReceiverType()!!
val substitutors = types
.map {
var substitutor = extensionReceiverType.checkIsSuperTypeOf(it)
// check if we may fail due to receiver expression being nullable
if (substitutor == null && TypeUtils.isNullableType(it) && !TypeUtils.isNullableType(extensionReceiverType.type)) {
substitutor = extensionReceiverType.checkIsSuperTypeOf(it.makeNotNullable())
}
substitutor
}
.filterNotNull()
if (getTypeParameters().isEmpty()) { // optimization for non-generic callables
return if (substitutors.any()) listOf(this) else listOf()
@@ -76,38 +84,3 @@ public fun CallableDescriptor.substituteExtensionIfCallable(
}
}
private fun checkReceiverResolution(
receiverType: JetType,
receiverParameter: ReceiverParameterDescriptor,
typeParameters: List<TypeParameterDescriptor>
): TypeSubstitutor? {
val typeParamsInReceiver = HashSet<TypeParameterDescriptor>()
typeParamsInReceiver.addUsedTypeParameters(receiverParameter.getType())
val constraintSystem = ConstraintSystemImpl()
val typeVariables = LinkedHashMap<TypeParameterDescriptor, Variance>()
for (typeParameter in typeParameters) {
if (typeParamsInReceiver.contains(typeParameter)) {
typeVariables[typeParameter] = Variance.INVARIANT
}
}
constraintSystem.registerTypeVariables(typeVariables)
constraintSystem.addSubtypeConstraint(receiverType, receiverParameter.getType(), ConstraintPosition.RECEIVER_POSITION)
if (constraintSystem.getStatus().isSuccessful() && ConstraintsUtil.checkBoundsAreSatisfied(constraintSystem, true)) {
return constraintSystem.getResultingSubstitutor()
}
else {
return null
}
}
private fun MutableSet<TypeParameterDescriptor>.addUsedTypeParameters(jetType: JetType) {
addIfNotNull(jetType.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor)
for (argument in jetType.getArguments()) {
addUsedTypeParameters(argument.getType())
}
}
@@ -24,7 +24,6 @@ import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.lang.resolve.*
import org.jetbrains.jet.lang.resolve.name.Name
import org.jetbrains.jet.lang.psi.psiUtil.getReceiverExpression
import org.jetbrains.jet.lang.resolve.scopes.JetScope
import com.intellij.openapi.project.Project
import java.util.HashSet
@@ -36,6 +35,8 @@ import org.jetbrains.jet.lang.resolve.calls.smartcasts.DataFlowInfo
import com.intellij.psi.stubs.StringStubIndexExtension
import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade
import org.jetbrains.jet.plugin.util.substituteExtensionIfCallable
import org.jetbrains.jet.plugin.util.CallType
import org.jetbrains.jet.plugin.codeInsight.ReferenceVariantsHelper
public class KotlinIndicesHelper(
private val project: Project,
@@ -110,9 +111,9 @@ public class KotlinIndicesHelper(
dataFlowInfo: DataFlowInfo) {
val matchingNames = fqNames.filter { nameFilter(it.shortName().asString()) }
val receiverExpression = expression.getReceiverExpression()
if (receiverExpression != null) {
val isInfixCall = expression.getParent() is JetBinaryExpression
val receiverPair = ReferenceVariantsHelper.getReferenceVariantsReceiver(expression)
if (receiverPair != null) {
val (receiverExpression, callType) = receiverPair
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE, receiverExpression]
if (expressionType == null || expressionType.isError()) return
@@ -122,7 +123,7 @@ public class KotlinIndicesHelper(
val receiverValue = ExpressionReceiver(receiverExpression, expressionType)
matchingNames.flatMapTo(this) {
findSuitableExtensions(it, index, receiverValue, dataFlowInfo, isInfixCall, resolutionScope, moduleDescriptor, bindingContext)
findSuitableExtensions(it, index, receiverValue, dataFlowInfo, callType, resolutionScope, moduleDescriptor, bindingContext)
}
}
else {
@@ -130,7 +131,7 @@ public class KotlinIndicesHelper(
for (receiver in resolutionScope.getImplicitReceiversHierarchy()) {
matchingNames.flatMapTo(this) {
findSuitableExtensions(it, index, receiver.getValue(), dataFlowInfo, false, resolutionScope, moduleDescriptor, bindingContext)
findSuitableExtensions(it, index, receiver.getValue(), dataFlowInfo, CallType.NORMAL, resolutionScope, moduleDescriptor, bindingContext)
}
}
}
@@ -143,7 +144,7 @@ public class KotlinIndicesHelper(
index: StringStubIndexExtension<out JetCallableDeclaration>?,
receiverValue: ReceiverValue,
dataFlowInfo: DataFlowInfo,
isInfixCall: Boolean,
callType: CallType,
resolutionScope: JetScope,
module: ModuleDescriptor,
bindingContext: BindingContext): Stream<CallableDescriptor> {
@@ -162,7 +163,7 @@ public class KotlinIndicesHelper(
return descriptors.stream()
.filter(visibilityFilter)
.flatMap { it.substituteExtensionIfCallable(receiverValue, isInfixCall, bindingContext, dataFlowInfo).stream() }
.flatMap { it.substituteExtensionIfCallable(receiverValue, callType, bindingContext, dataFlowInfo).stream() }
}
public fun getClassDescriptors(nameFilter: (String) -> Boolean, kindFilter: (ClassKind) -> Boolean): Collection<ClassDescriptor> {
@@ -41,6 +41,7 @@ import org.jetbrains.jet.plugin.refactoring.comparePossiblyOverridingDescriptors
import kotlin.properties.Delegates
import org.jetbrains.jet.lang.psi.psiUtil.getStrictParentOfType
import org.jetbrains.jet.plugin.util.makeNotNullable
import org.jetbrains.jet.plugin.util.CallType
class CompletionSessionConfiguration(
val completeNonImportedDeclarations: Boolean,
@@ -80,7 +81,7 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess
var receiverTypes = receivers.flatMap {
SmartCastUtils.getSmartCastVariantsWithLessSpecificExcluded(it, bindingContext, dataFlowInfo)
}
if (callType == ReferenceVariantsHelper.CallType.SAFE) {
if (callType == CallType.SAFE) {
receiverTypes = receiverTypes.map { it.makeNotNullable() }
}
receiverTypes
@@ -129,12 +129,12 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
val result = ArrayList<LookupElement>()
val types = descriptor.fuzzyTypes(smartCastTypes)
val classifier = { (expectedInfo: ExpectedInfo) ->
val substitutor = types.stream().map { it.matchedSubstitutor(expectedInfo.type) }.firstOrNull()
val substitutor = types.stream().map { it.checkIsSubtypeOf(expectedInfo.type) }.firstOrNull()
if (substitutor != null) {
ExpectedInfoClassification.matches(substitutor)
}
else {
val substitutor2 = types.stream().map { it.makeNotNullable().matchedSubstitutor(expectedInfo.type) }.firstOrNull()
val substitutor2 = types.stream().map { it.makeNotNullable().checkIsSubtypeOf(expectedInfo.type) }.firstOrNull()
if (substitutor2 != null) {
ExpectedInfoClassification.matchesIfNotNullable(substitutor2)
}
@@ -111,13 +111,13 @@ class ExpectedInfoClassification private(val substitutor: TypeSubstitutor?, val
}
fun FuzzyType.classifyExpectedInfo(expectedInfo: ExpectedInfo): ExpectedInfoClassification {
val substitutor = matchedSubstitutor(expectedInfo.type)
val substitutor = checkIsSubtypeOf(expectedInfo.type)
if (substitutor != null) {
return ExpectedInfoClassification.matches(substitutor)
}
if (isNullable()) {
val substitutor2 = makeNotNullable().matchedSubstitutor(expectedInfo.type)
val substitutor2 = makeNotNullable().checkIsSubtypeOf(expectedInfo.type)
if (substitutor2 != null) {
return ExpectedInfoClassification.matchesIfNotNullable(substitutor2)
}
@@ -0,0 +1,7 @@
fun<T> T.doSomething(t: T): T = t
fun foo(s: String?) {
s.<caret>
}
// EXIST: { itemText: "doSomething", tailText: "(t: String?) for T in <root>", typeText: "String?" }
@@ -0,0 +1,7 @@
fun<T> T.doSomething(t: T): T = t
fun foo(s: String?) {
s?.<caret>
}
// EXIST: { itemText: "doSomething", tailText: "(t: String) for T in <root>", typeText: "String" }
@@ -826,6 +826,18 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName);
}
@TestMetadata("SubstitutedSignature5.kt")
public void testSubstitutedSignature5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SubstitutedSignature5.kt");
doTest(fileName);
}
@TestMetadata("SubstitutedSignature6.kt")
public void testSubstitutedSignature6() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SubstitutedSignature6.kt");
doTest(fileName);
}
@TestMetadata("TopLevelClassCompletionInQualifiedCall.kt")
public void testTopLevelClassCompletionInQualifiedCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/TopLevelClassCompletionInQualifiedCall.kt");
@@ -826,6 +826,18 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName);
}
@TestMetadata("SubstitutedSignature5.kt")
public void testSubstitutedSignature5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SubstitutedSignature5.kt");
doTest(fileName);
}
@TestMetadata("SubstitutedSignature6.kt")
public void testSubstitutedSignature6() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SubstitutedSignature6.kt");
doTest(fileName);
}
@TestMetadata("TopLevelClassCompletionInQualifiedCall.kt")
public void testTopLevelClassCompletionInQualifiedCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/TopLevelClassCompletionInQualifiedCall.kt");
@@ -19,6 +19,7 @@ package org.jetbrains.jet.completion;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.jet.JUnit3RunnerWithInners;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.junit.runner.RunWith;