diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/impl/LocalVariableDescriptor.java b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/impl/LocalVariableDescriptor.java index 1b5ecc79c73..2f723a6acb1 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/impl/LocalVariableDescriptor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/impl/LocalVariableDescriptor.java @@ -42,6 +42,7 @@ public class LocalVariableDescriptor extends VariableDescriptorImpl { @NotNull @Override public LocalVariableDescriptor substitute(@NotNull TypeSubstitutor substitutor) { + if (substitutor.isEmpty()) return this; throw new UnsupportedOperationException(); // TODO } diff --git a/core/descriptors/src/org/jetbrains/jet/lang/descriptors/impl/ValueParameterDescriptorImpl.java b/core/descriptors/src/org/jetbrains/jet/lang/descriptors/impl/ValueParameterDescriptorImpl.java index dac58fe6687..51e41cbcda2 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/descriptors/impl/ValueParameterDescriptorImpl.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/descriptors/impl/ValueParameterDescriptorImpl.java @@ -108,6 +108,7 @@ public class ValueParameterDescriptorImpl extends VariableDescriptorImpl impleme @NotNull @Override public ValueParameterDescriptor substitute(@NotNull TypeSubstitutor substitutor) { + if (substitutor.isEmpty()) return this; throw new UnsupportedOperationException(); // TODO } diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/TypeUtils.kt b/core/descriptors/src/org/jetbrains/jet/lang/types/TypeUtils.kt index 2d41316f4a5..da0081ac0eb 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/types/TypeUtils.kt +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/TypeUtils.kt @@ -25,6 +25,7 @@ import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.lang.types.Flexibility import org.jetbrains.jet.lang.types.TypeConstructor import org.jetbrains.jet.utils.toReadOnlyList +import org.jetbrains.jet.lang.types.checker.JetTypeChecker fun JetType.getContainedTypeParameters(): Collection { val declarationDescriptor = getConstructor().getDeclarationDescriptor() @@ -59,3 +60,6 @@ public fun JetType.getContainedAndCapturedTypeParameterConstructors(): Collectio return typeParameters.map { it.getTypeConstructor() }.toReadOnlyList() } +public fun JetType.isSubtypeOf(superType: JetType): Boolean = JetTypeChecker.DEFAULT.isSubtypeOf(this, superType) + + diff --git a/idea/src/org/jetbrains/jet/plugin/completion/CompletionUtils.kt b/idea/src/org/jetbrains/jet/plugin/completion/CompletionUtils.kt index 8ea63e4bb17..7cd8e3a8eb6 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/CompletionUtils.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/CompletionUtils.kt @@ -35,6 +35,7 @@ import com.intellij.codeInsight.lookup.LookupElementDecorator import com.intellij.codeInsight.completion.InsertionContext import org.jetbrains.jet.plugin.completion.handlers.CastReceiverInsertHandler import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor +import org.jetbrains.jet.lang.descriptors.CallableDescriptor enum class ItemPriority { MULTIPLE_ARGUMENTS_ITEM @@ -113,3 +114,23 @@ enum class CallableWeight { } val CALLABLE_WEIGHT_KEY = Key("CALLABLE_WEIGHT_KEY") + +fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, descriptor2: DeclarationDescriptor?): Boolean { + if (descriptor1 == descriptor2) return true + if (descriptor1 == null || descriptor2 == null) return false + if (descriptor1.getOriginal() != descriptor2.getOriginal()) return false + if (descriptor1 !is CallableDescriptor) return true + descriptor2 as CallableDescriptor + + // optimization: + if (descriptor1 == descriptor1.getOriginal() && descriptor2 == descriptor2.getOriginal()) return true + + if (descriptor1.getReturnType() != descriptor2.getReturnType()) return false + val parameters1 = descriptor1.getValueParameters() + val parameters2 = descriptor2.getValueParameters() + if (parameters1.size() != parameters2.size()) return false + for (i in parameters1.indices) { + if (parameters1[i].getType() != parameters2[i].getType()) return false + } + return true +} diff --git a/idea/src/org/jetbrains/jet/plugin/completion/DeclarationDescriptorLookupObject.kt b/idea/src/org/jetbrains/jet/plugin/completion/DeclarationDescriptorLookupObject.kt index f19d840a8ae..62e23dfc501 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/DeclarationDescriptorLookupObject.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/DeclarationDescriptorLookupObject.kt @@ -20,7 +20,6 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.psi.PsiElement import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade -import org.jetbrains.jet.lang.descriptors.CallableDescriptor /** * Stores information about resolved descriptor and position of that descriptor. @@ -50,22 +49,7 @@ public class DeclarationDescriptorLookupObject( return false } - if (lookupObject.descriptor.getOriginal() != descriptor.getOriginal()) return false - if (descriptor !is CallableDescriptor) return true - // optimization: - if (descriptor == (descriptor as CallableDescriptor).getOriginal() && lookupObject.descriptor == lookupObject.descriptor.getOriginal()) return true - return substitutionsEqual(descriptor as CallableDescriptor, lookupObject.descriptor as CallableDescriptor) - } - - private fun substitutionsEqual(callable1: CallableDescriptor, callable2: CallableDescriptor): Boolean { - if (callable1.getReturnType() != callable2.getReturnType()) return false - val parameters1 = callable1.getValueParameters() - val parameters2 = callable2.getValueParameters() - if (parameters1.size() != parameters2.size()) return false - for (i in parameters1.indices) { - if (parameters1[i].getType() != parameters2[i].getType()) return false - } - return true + return descriptorsEqualWithSubstitution(descriptor, lookupObject.descriptor) } class object { diff --git a/idea/src/org/jetbrains/jet/plugin/completion/ExpectedInfos.kt b/idea/src/org/jetbrains/jet/plugin/completion/ExpectedInfos.kt index 80550eb08fc..301717c1b4b 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/ExpectedInfos.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/ExpectedInfos.kt @@ -40,7 +40,6 @@ import org.jetbrains.jet.lexer.JetTokens import org.jetbrains.jet.lang.psi.JetIfExpression import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns import org.jetbrains.jet.lang.psi.JetContainerNode -import org.jetbrains.jet.plugin.completion.smart.isSubtypeOf import org.jetbrains.jet.lang.resolve.calls.callUtil.noErrorsInValueArguments import org.jetbrains.jet.lang.descriptors.Visibilities import org.jetbrains.jet.lang.psi.JetBlockExpression @@ -68,6 +67,7 @@ import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor import org.jetbrains.jet.lang.descriptors.VariableDescriptor import org.jetbrains.jet.lang.resolve.calls.results.ResolutionStatus import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade +import org.jetbrains.jet.lang.types.typeUtil.isSubtypeOf enum class Tail { COMMA diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/FuzzyType.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/FuzzyType.kt new file mode 100644 index 00000000000..386655edec2 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/FuzzyType.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.completion.smart + +import org.jetbrains.jet.lang.types.JetType +import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor +import org.jetbrains.jet.lang.descriptors.CallableDescriptor +import org.jetbrains.jet.utils.addIfNotNull +import java.util.HashSet +import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintSystemImpl +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 + +fun CallableDescriptor.fuzzyReturnType(): FuzzyType? { + val returnType = getReturnType() ?: return null + 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 +} + +fun FuzzyType.makeNotNullable() = FuzzyType(type.makeNotNullable(), freeParameters) +fun FuzzyType.makeNullable() = FuzzyType(type.makeNullable(), freeParameters) +fun FuzzyType.isNullable() = type.isNullable() + +class FuzzyType( + val type: JetType, + val freeParameters: Collection +) { + private val usedTypeParameters: HashSet? = if (freeParameters.isNotEmpty()) HashSet() else null + + ;{ + usedTypeParameters?.addUsedTypeParameters(type) + } + + private fun MutableSet.addUsedTypeParameters(type: JetType) { + addIfNotNull(type.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor) + + for (argument in type.getArguments()) { + addUsedTypeParameters(argument.getType()) + } + } + + public fun matchedSubstitutor(expectedType: JetType): TypeSubstitutor? { + if (type.isError()) return null + if (usedTypeParameters == null || usedTypeParameters.isEmpty()) { + return if (type.isSubtypeOf(expectedType)) TypeSubstitutor.EMPTY else null + } + + val constraintSystem = ConstraintSystemImpl() + val typeVariables = LinkedHashMap() + for (typeParameter in freeParameters) { + if (typeParameter in usedTypeParameters) { + typeVariables[typeParameter] = Variance.INVARIANT + } + } + constraintSystem.registerTypeVariables(typeVariables) + + constraintSystem.addSubtypeConstraint(type, expectedType, ConstraintPosition.SPECIAL/*TODO?*/) + 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 + } + else { + return null + } + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/KeywordValues.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/KeywordValues.kt index 33a17d165f4..d585d719ad3 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/KeywordValues.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/KeywordValues.kt @@ -24,6 +24,7 @@ import org.jetbrains.jet.lang.psi.* import com.intellij.codeInsight.lookup.LookupElementDecorator import com.intellij.codeInsight.completion.InsertionContext import org.jetbrains.jet.plugin.completion.handlers.WithTailInsertHandler +import org.jetbrains.jet.lang.types.TypeSubstitutor object KeywordValues { public fun addToCollection(collection: MutableCollection, expectedInfos: Collection, expressionWithType: JetExpression) { @@ -48,14 +49,15 @@ object KeywordValues { if (!skipTrueFalse) { val booleanInfoClassifier = { (info: ExpectedInfo) -> - if (info.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.MATCHES else ExpectedInfoClassification.NOT_MATCHES + if (info.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches } - collection.addLookupElements(expectedInfos, booleanInfoClassifier, { LookupElementBuilder.create("true").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.TRUE) }) - collection.addLookupElements(expectedInfos, booleanInfoClassifier, { LookupElementBuilder.create("false").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.FALSE) }) + collection.addLookupElements(null, expectedInfos, booleanInfoClassifier, { LookupElementBuilder.create("true").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.TRUE) }) + collection.addLookupElements(null, expectedInfos, booleanInfoClassifier, { LookupElementBuilder.create("false").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.FALSE) }) } - collection.addLookupElements(expectedInfos, - { info -> if (info.type.isNullable()) ExpectedInfoClassification.MATCHES else ExpectedInfoClassification.NOT_MATCHES }, + collection.addLookupElements(null, + expectedInfos, + { info -> if (info.type.isNullable()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches }, { LookupElementBuilder.create("null").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.NULL) }) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/MultipleArgumentsItemProvider.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/MultipleArgumentsItemProvider.kt index afd11d3bba6..2f91ad83f98 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/MultipleArgumentsItemProvider.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/MultipleArgumentsItemProvider.kt @@ -19,13 +19,10 @@ package org.jetbrains.jet.plugin.completion.smart import org.jetbrains.jet.plugin.completion.ExpectedInfo import org.jetbrains.jet.lang.psi.JetExpression import com.intellij.codeInsight.lookup.LookupElement -import org.jetbrains.jet.lang.types.checker.JetTypeChecker import com.intellij.ui.LayeredIcon import com.intellij.codeInsight.lookup.LookupElementBuilder import org.jetbrains.jet.plugin.completion.Tail import org.jetbrains.jet.plugin.completion.ItemPriority -import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor -import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.lang.resolve.BindingContext import java.util.HashSet import org.jetbrains.jet.lang.resolve.scopes.JetScope @@ -37,9 +34,11 @@ import java.util.ArrayList import org.jetbrains.jet.plugin.util.IdeDescriptorRenderers import org.jetbrains.jet.plugin.completion.assignPriority import com.intellij.codeInsight.lookup.Lookup +import org.jetbrains.jet.lang.types.JetType +import org.jetbrains.jet.lang.types.checker.JetTypeChecker class MultipleArgumentsItemProvider(val bindingContext: BindingContext, - val typesWithAutoCasts: (DeclarationDescriptor) -> Iterable) { + val smartCastTypes: (VariableDescriptor) -> Collection) { public fun addToCollection(collection: MutableCollection, expectedInfos: Collection, @@ -95,7 +94,7 @@ class MultipleArgumentsItemProvider(val bindingContext: BindingContext, val name = parameter.getName() //TODO: there can be more than one property with such name in scope and we should be able to select one (but we need API for this) val variable = scope.getLocalVariable(name) ?: scope.getProperties(name).singleOrNull() ?: return null - return if (typesWithAutoCasts(variable).any { JetTypeChecker.DEFAULT.isSubtypeOf(it, parameter.getType()) }) + return if (smartCastTypes(variable).any { JetTypeChecker.DEFAULT.isSubtypeOf(it, parameter.getType()) }) variable else null diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt index 9aea4c41b99..e1d34dbbb2b 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt @@ -28,13 +28,13 @@ import org.jetbrains.jet.plugin.completion.* import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns import org.jetbrains.jet.plugin.util.makeNotNullable import org.jetbrains.jet.plugin.util.makeNullable -import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.jet.renderer.DescriptorRenderer import org.jetbrains.jet.lang.psi.psiUtil.getReceiverExpression import org.jetbrains.jet.plugin.util.IdeDescriptorRenderers import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade import org.jetbrains.jet.plugin.caches.resolve.resolveToDescriptor import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.jet.lang.types.typeUtil.isSubtypeOf trait InheritanceItemsSearcher { fun search(nameFilter: (String) -> Boolean, consumer: (LookupElement) -> Unit) @@ -115,30 +115,40 @@ class SmartCompletion(val expression: JetSimpleNameExpression, else filteredExpectedInfos - val typesWithSmartCasts: (DeclarationDescriptor) -> Iterable = TypesWithSmartCasts(bindingContext).calculate(expressionWithType, receiver) + val smartCastTypes: (VariableDescriptor) -> Collection = TypesWithSmartCasts(bindingContext).calculate(expressionWithType, receiver) val itemsToSkip = calcItemsToSkip(expressionWithType) val functionExpectedInfos = expectedInfos.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it.type) } fun filterDeclaration(descriptor: DeclarationDescriptor): Collection { + if (descriptor in itemsToSkip) return listOf() + val result = ArrayList() - if (!itemsToSkip.contains(descriptor)) { - val types = typesWithSmartCasts(descriptor) - val nonNullTypes = types.map { it.makeNotNullable() } - val classifier = { (expectedInfo: ExpectedInfo) -> - when { - types.any { it.isSubtypeOf(expectedInfo.type) } -> ExpectedInfoClassification.MATCHES - nonNullTypes.any { it.isSubtypeOf(expectedInfo.type) } -> ExpectedInfoClassification.MAKE_NOT_NULLABLE - else -> ExpectedInfoClassification.NOT_MATCHES + val types = descriptor.fuzzyTypes(smartCastTypes) + val classifier = { (expectedInfo: ExpectedInfo) -> + val substitutor = types.stream().map { it.matchedSubstitutor(expectedInfo.type) }.firstOrNull() + if (substitutor != null) { + ExpectedInfoClassification.matches(substitutor) + } + else { + val substitutor2 = types.stream().map { it.makeNotNullable().matchedSubstitutor(expectedInfo.type) }.firstOrNull() + if (substitutor2 != null) { + ExpectedInfoClassification.matchesIfNotNullable(substitutor2) + } + else { + ExpectedInfoClassification.notMatches } } - result.addLookupElements(expectedInfos, classifier, { lookupElementFactory.createLookupElement(descriptor, resolutionFacade, bindingContext, true) }) - - if (receiver == null) { - toFunctionReferenceLookupElement(descriptor, functionExpectedInfos)?.let { result.add(it) } - } } + result.addLookupElements(descriptor, expectedInfos, classifier) { descriptor -> + lookupElementFactory.createLookupElement(descriptor, resolutionFacade, bindingContext, true) + } + + if (receiver == null) { + toFunctionReferenceLookupElement(descriptor, functionExpectedInfos)?.let { result.add(it) } + } + return result } @@ -156,7 +166,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression, KeywordValues.addToCollection(additionalItems, filteredExpectedInfos/* use filteredExpectedInfos to not include null after == */, expressionWithType) - MultipleArgumentsItemProvider(bindingContext, typesWithSmartCasts).addToCollection(additionalItems, expectedInfos, expression) + MultipleArgumentsItemProvider(bindingContext, smartCastTypes).addToCollection(additionalItems, expectedInfos, expression) } val inheritanceSearcher = if (inheritanceSearchers.isNotEmpty()) @@ -170,6 +180,37 @@ class SmartCompletion(val expression: JetSimpleNameExpression, return Result(::filterDeclaration, additionalItems, inheritanceSearcher) } + private fun DeclarationDescriptor.fuzzyTypes(smartCastTypes: (VariableDescriptor) -> Collection): Collection { + if (this is CallableDescriptor) { + var returnType = fuzzyReturnType() ?: return listOf() + //TODO: maybe we should include them on the second press? + if (shouldSkipDeclarationsOfType(returnType)) return listOf() + + if (this is VariableDescriptor) { + return smartCastTypes(this).map { FuzzyType(it, listOf()) } + } + else { + return listOf(fuzzyReturnType()!!) + } + } + else if (this is ClassDescriptor && getKind().isSingleton()) { + return listOf(FuzzyType(getDefaultType(), listOf())) + } + else { + return listOf() + } + } + + // skip declarations of type Nothing or of generic parameter type which has no real bounds + private fun shouldSkipDeclarationsOfType(type: FuzzyType): Boolean { + if (KotlinBuiltIns.getInstance().isNothing(type.type)) return true + if (type.freeParameters.isEmpty()) return false + val typeParameter = type.type.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor ?: return false + if (!type.freeParameters.contains(typeParameter)) return false + return KotlinBuiltIns.getInstance().isAnyOrNullableAny(typeParameter.getUpperBoundsAsType()) + //TODO: check for class object constraint when there will be supported + } + private fun calcExpectedInfos(expression: JetExpression): Collection? { // if our expression is initializer of implicitly typed variable - take type of variable from original file (+ the same for function) val declaration = implicitlyTypedDeclarationFromInitializer(expression) diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt index 43c5d6b2f12..106f1f4fd3d 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt @@ -28,12 +28,12 @@ import com.intellij.codeInsight.completion.InsertionContext import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.psi.JetExpression import org.jetbrains.jet.plugin.completion.ExpectedInfo -import org.jetbrains.jet.plugin.util.makeNotNullable import org.jetbrains.jet.plugin.completion.qualifiedNameForSourceCode import org.jetbrains.jet.lang.resolve.descriptorUtil.isExtension import org.jetbrains.jet.plugin.util.IdeDescriptorRenderers import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade import org.jetbrains.jet.plugin.completion.LookupElementFactory +import org.jetbrains.jet.lang.types.TypeSubstitutor // adds java static members, enum members and members from class object class StaticMembers( @@ -68,24 +68,19 @@ class StaticMembers( val classifier: (ExpectedInfo) -> ExpectedInfoClassification if (descriptor is CallableDescriptor) { - val returnType = descriptor.getReturnType() ?: return - classifier = { - expectedInfo -> - when { - returnType.isSubtypeOf(expectedInfo.type) -> ExpectedInfoClassification.MATCHES - returnType.isNullable() && returnType.makeNotNullable().isSubtypeOf(expectedInfo.type) -> ExpectedInfoClassification.MAKE_NOT_NULLABLE - else -> ExpectedInfoClassification.NOT_MATCHES - } - } + val returnType = descriptor.fuzzyReturnType() ?: return + classifier = { expectedInfo -> returnType.classifyExpectedInfo(expectedInfo) } } else if (DescriptorUtils.isEnumEntry(descriptor) && !enumEntriesToSkip.contains(descriptor)) { - classifier = { ExpectedInfoClassification.MATCHES } /* we do not need to check type of enum entry because it's taken from proper enum */ + classifier = { ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) } /* we do not need to check type of enum entry because it's taken from proper enum */ } else { return } - collection.addLookupElements(expectedInfos, classifier, { createLookupElement(descriptor, classDescriptor) }) + collection.addLookupElements(descriptor, expectedInfos, classifier) { + descriptor -> createLookupElement(descriptor, classDescriptor) + } } classDescriptor.getStaticScope().getAllDescriptors().forEach(::processMember) diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt index 61171e21730..9855d337691 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt @@ -28,27 +28,21 @@ import org.jetbrains.jet.lang.psi.JetCallExpression import org.jetbrains.jet.lang.psi.JetSimpleNameExpression import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.plugin.completion.ExpectedInfo -import org.jetbrains.jet.plugin.util.makeNotNullable import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor class ThisItems(val bindingContext: BindingContext) { public fun addToCollection(collection: MutableCollection, context: JetExpression, expectedInfos: Collection) { - val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] - if (scope == null) return + val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] ?: return val receivers: List = scope.getImplicitReceiversHierarchy() for (i in 0..receivers.size - 1) { val receiver = receivers[i] val thisType = receiver.getType() - val classifier = { (expectedInfo: ExpectedInfo) -> - when { - thisType.isSubtypeOf(expectedInfo.type) -> ExpectedInfoClassification.MATCHES - thisType.isNullable() && thisType.makeNotNullable().isSubtypeOf(expectedInfo.type) -> ExpectedInfoClassification.MAKE_NOT_NULLABLE - else -> ExpectedInfoClassification.NOT_MATCHES - } - } - fun lookupElementFactory(): LookupElement? { + val fuzzyType = FuzzyType(thisType, listOf()) + val classifier = { (expectedInfo: ExpectedInfo) -> fuzzyType.classifyExpectedInfo(expectedInfo) } + fun createLookupElement(): LookupElement? { //TODO: use this code when KT-4258 fixed //val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: return null) val qualifier = if (i == 0) null else (thisQualifierName(receiver) ?: return null) @@ -57,7 +51,7 @@ class ThisItems(val bindingContext: BindingContext) { .withTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(thisType)) .assignSmartCompletionPriority(SmartCompletionItemPriority.THIS) } - collection.addLookupElements(expectedInfos, classifier, ::lookupElementFactory) + collection.addLookupElements(null, expectedInfos, classifier) { createLookupElement() } } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt index fdb812de2da..731f1b737db 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt @@ -60,6 +60,7 @@ import org.jetbrains.jet.lang.resolve.resolveTopLevelClass import org.jetbrains.jet.lang.types.TypeProjectionImpl import org.jetbrains.jet.lang.types.Variance import org.jetbrains.jet.lang.psi.JetDeclaration +import org.jetbrains.jet.lang.types.typeUtil.isSubtypeOf class TypeInstantiationItems( val resolutionFacade: ResolutionFacade, diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/TypesWithSmartCasts.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypesWithSmartCasts.kt index 1988dd9dff4..c1a807b910e 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/TypesWithSmartCasts.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypesWithSmartCasts.kt @@ -17,13 +17,8 @@ package org.jetbrains.jet.plugin.completion.smart import org.jetbrains.jet.lang.psi.JetExpression -import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.lang.descriptors.VariableDescriptor -import org.jetbrains.jet.lang.descriptors.CallableDescriptor -import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns -import org.jetbrains.jet.lang.descriptors.ClassDescriptor -import org.jetbrains.jet.lang.descriptors.ClassKind import java.util.Collections import org.jetbrains.jet.lang.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.jet.lang.resolve.calls.smartcasts.DataFlowValue @@ -34,40 +29,23 @@ import org.jetbrains.jet.lang.resolve.calls.smartcasts.Nullability import java.util.HashSet import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver -import org.jetbrains.jet.plugin.util.makeNotNullable import org.jetbrains.jet.lang.resolve.bindingContextUtil.getDataFlowInfo +import org.jetbrains.jet.plugin.util.makeNotNullable class TypesWithSmartCasts(val bindingContext: BindingContext) { - public fun calculate(expression: JetExpression, receiver: JetExpression?): (DeclarationDescriptor) -> Iterable { + public fun calculate(expression: JetExpression, receiver: JetExpression?): (VariableDescriptor) -> Collection { val dataFlowInfo = bindingContext.getDataFlowInfo(expression) - val (variableToTypes: Map>, notNullVariables: Set) - = processDataFlowInfo(dataFlowInfo, receiver) + val (variableToTypes, notNullVariables) = processDataFlowInfo(dataFlowInfo, receiver) - fun typesOf(descriptor: DeclarationDescriptor): Iterable { - if (descriptor is CallableDescriptor) { - var returnType = descriptor.getReturnType() - if (returnType != null && KotlinBuiltIns.isNothing(returnType!!)) { - //TODO: maybe we should include them on the second press? - return listOf() - } - if (descriptor is VariableDescriptor) { - if (notNullVariables.contains(descriptor)) { - returnType = returnType?.makeNotNullable() - } + fun typesOf(descriptor: VariableDescriptor): Collection { + var type = descriptor.getReturnType() ?: return listOf() + if (notNullVariables.contains(descriptor)) { + type = type.makeNotNullable() + } - val smartCastTypes = variableToTypes[descriptor] - if (smartCastTypes != null && !smartCastTypes.isEmpty()) { - return smartCastTypes + returnType.toList() - } - } - return returnType.toList() - } - else if (descriptor is ClassDescriptor && descriptor.getKind().isSingleton()) { - return listOf(descriptor.getDefaultType()) - } - else { - return listOf() - } + val smartCastTypes = variableToTypes[descriptor] + if (smartCastTypes == null || smartCastTypes.isEmpty()) return type.toList() + return smartCastTypes + type.toList() } return ::typesOf diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt index 602be5b4ef1..d3e592c284b 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt @@ -27,9 +27,7 @@ import com.intellij.codeInsight.lookup.LookupElementDecorator import org.jetbrains.jet.lang.descriptors.FunctionDescriptor import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns -import org.jetbrains.jet.lang.types.checker.JetTypeChecker import com.intellij.codeInsight.lookup.LookupElementPresentation -import java.util.ArrayList import org.jetbrains.jet.plugin.completion.* import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor import org.jetbrains.jet.plugin.completion.handlers.WithTailInsertHandler @@ -39,6 +37,9 @@ import com.intellij.openapi.util.Key import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade +import org.jetbrains.jet.lang.types.TypeSubstitutor +import java.util.ArrayList +import java.util.HashMap class ArtificialElementInsertHandler( val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler{ @@ -98,36 +99,71 @@ fun LookupElement.addTailAndNameSimilarity(matchedExpectedInfos: Collection.addLookupElements(expectedInfos: Collection, - infoClassifier: (ExpectedInfo) -> ExpectedInfoClassification, - lookupElementFactory: () -> LookupElement?) { - val matchedInfos = ArrayList() - val matchedInfosNotNullable = ArrayList() +fun FuzzyType.classifyExpectedInfo(expectedInfo: ExpectedInfo): ExpectedInfoClassification { + val substitutor = matchedSubstitutor(expectedInfo.type) + if (substitutor != null) { + return ExpectedInfoClassification.matches(substitutor) + } + + if (isNullable()) { + val substitutor2 = makeNotNullable().matchedSubstitutor(expectedInfo.type) + if (substitutor2 != null) { + return ExpectedInfoClassification.matchesIfNotNullable(substitutor2) + } + } + + return ExpectedInfoClassification.notMatches +} + +fun MutableCollection.addLookupElements( + descriptor: TDescriptor, + expectedInfos: Collection, + infoClassifier: (ExpectedInfo) -> ExpectedInfoClassification, + lookupElementFactory: (TDescriptor) -> LookupElement? +) { + class DescriptorWrapper(val descriptor: TDescriptor) { + override fun equals(other: Any?) = other is DescriptorWrapper && descriptorsEqualWithSubstitution(this.descriptor, other.descriptor) + override fun hashCode() = if (this.descriptor != null) this.descriptor.getOriginal().hashCode() else 0 + } + fun TDescriptor.wrap() = DescriptorWrapper(this) + fun DescriptorWrapper.unwrap() = this.descriptor + + val matchedInfos = HashMap>() + val makeNullableInfos = HashMap>() for (info in expectedInfos) { - when (infoClassifier(info)) { - ExpectedInfoClassification.MATCHES -> matchedInfos.add(info) - ExpectedInfoClassification.MAKE_NOT_NULLABLE -> matchedInfosNotNullable.add(info) + val classification = infoClassifier(info) + if (classification.substitutor != null) { + [suppress("UNCHECKED_CAST")] + val substitutedDescriptor = descriptor?.substitute(classification.substitutor) as TDescriptor + val map = if (classification.makeNotNullable) makeNullableInfos else matchedInfos + map.getOrPut(substitutedDescriptor.wrap()) { ArrayList() }.add(info) } } - if (matchedInfos.isNotEmpty()) { - val lookupElement = lookupElementFactory() - if (lookupElement != null) { - add(lookupElement.addTailAndNameSimilarity(matchedInfos)) + if (!matchedInfos.isEmpty()) { + for ((substitutedDescriptor, infos) in matchedInfos) { + val lookupElement = lookupElementFactory(substitutedDescriptor.unwrap()) + if (lookupElement != null) { + add(lookupElement.addTailAndNameSimilarity(infos)) + } } } - else if (matchedInfosNotNullable.isNotEmpty()) { - addLookupElementsForNullable(lookupElementFactory, matchedInfosNotNullable) + else { + for ((substitutedDescriptor, infos) in makeNullableInfos) { + addLookupElementsForNullable({ lookupElementFactory(substitutedDescriptor.unwrap()) }, infos) + } } } -fun MutableCollection.addLookupElementsForNullable(factory: () -> LookupElement?, matchedInfos: Collection) { +private fun MutableCollection.addLookupElementsForNullable(factory: () -> LookupElement?, matchedInfos: Collection) { var lookupElement = factory() if (lookupElement != null) { lookupElement = object: LookupElementDecorator(lookupElement!!) { @@ -205,8 +241,6 @@ fun LookupElementFactory.createLookupElement( return element } -fun JetType.isSubtypeOf(expectedType: JetType) = !isError() && JetTypeChecker.DEFAULT.isSubtypeOf(this, expectedType) - fun T?.toList(): List = if (this != null) listOf(this) else listOf() fun T?.toSet(): Set = if (this != null) setOf(this) else setOf() @@ -235,4 +269,3 @@ fun LookupElement.assignSmartCompletionPriority(priority: SmartCompletionItemPri putUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY, priority) return this } - diff --git a/idea/testData/completion/smart/GenericFunction1.kt b/idea/testData/completion/smart/GenericFunction1.kt new file mode 100644 index 00000000000..972a71b8785 --- /dev/null +++ b/idea/testData/completion/smart/GenericFunction1.kt @@ -0,0 +1,8 @@ +fun foo(array: Array){} + +fun f(){ + foo() +} + +// EXIST: { lookupString: "array", tailText: "(vararg t: String) (kotlin)", typeText: "Array" } +// ABSENT: arrayOfNulls diff --git a/idea/testData/completion/smart/GenericFunction2.kt b/idea/testData/completion/smart/GenericFunction2.kt new file mode 100644 index 00000000000..3c9ad1390fe --- /dev/null +++ b/idea/testData/completion/smart/GenericFunction2.kt @@ -0,0 +1,8 @@ +fun foo(array: Array){} + +fun f(){ + foo() +} + +// EXIST: { lookupString: "array", tailText: "(vararg t: String?) (kotlin)", typeText: "Array" } +// EXIST: { lookupString: "arrayOfNulls", tailText: "(size: Int) (kotlin)", typeText: "Array" } diff --git a/idea/testData/completion/smart/GenericFunction3.kt b/idea/testData/completion/smart/GenericFunction3.kt new file mode 100644 index 00000000000..02509c6ccfc --- /dev/null +++ b/idea/testData/completion/smart/GenericFunction3.kt @@ -0,0 +1,9 @@ +fun foo(list: List){} + +fun f(){ + foo() +} + +// EXIST: { lookupString: "listOf", tailText: "() (kotlin)", typeText: "List" } +// EXIST: { lookupString: "listOf", tailText: "(vararg values: String) (kotlin)", typeText: "List" } +// EXIST: { lookupString: "arrayListOf", tailText: "(vararg values: String!) (kotlin)", typeText: "ArrayList" } diff --git a/idea/testData/completion/smart/GenericFunction4.kt b/idea/testData/completion/smart/GenericFunction4.kt new file mode 100644 index 00000000000..6b2f65c5274 --- /dev/null +++ b/idea/testData/completion/smart/GenericFunction4.kt @@ -0,0 +1,6 @@ +fun foo(list: List): Collection { + return list. +} + +// EXIST: { lookupString: "map", tailText: "(transform: (String) -> Int) for Iterable in kotlin", typeText: "List" } +// ABSENT: filter diff --git a/idea/testData/completion/smart/GenericFunction5.kt b/idea/testData/completion/smart/GenericFunction5.kt new file mode 100644 index 00000000000..a9eef6b1664 --- /dev/null +++ b/idea/testData/completion/smart/GenericFunction5.kt @@ -0,0 +1,13 @@ +class C { + class object { + fun create(t: T): C{} + } +} + +fun foo(c: C){} + +fun f(){ + foo() +} + +// EXIST: { lookupString: "create", itemText: "C.create", tailText: "(t: String) ()", typeText: "C" } diff --git a/idea/testData/completion/smart/GroupBySubstitutor.kt b/idea/testData/completion/smart/GroupBySubstitutor.kt new file mode 100644 index 00000000000..5a01195369f --- /dev/null +++ b/idea/testData/completion/smart/GroupBySubstitutor.kt @@ -0,0 +1,14 @@ +fun emptyList(): List = listOf() + +fun foo(list: List){} +fun foo(list: List, i: Int){} +fun foo(list: List, b: Boolean){} +fun foo(list: List, c: Char){} + +fun f(){ + foo(empty) +} + +// EXIST: { lookupString: "emptyList", typeText: "List" } +// EXIST: { lookupString: "emptyList", typeText: "List" } +// NUMBER: 2 diff --git a/idea/testData/completion/smart/SkipDeclarationsOfType.kt b/idea/testData/completion/smart/SkipDeclarationsOfType.kt new file mode 100644 index 00000000000..abf88a3c842 --- /dev/null +++ b/idea/testData/completion/smart/SkipDeclarationsOfType.kt @@ -0,0 +1,16 @@ +fun nothingFoo() = throw Exception() + +fun genericFoo1(t: T): T = t + +fun genericFoo2(t: T): T? = t + +fun genericFoo3(t: T): T = t + +fun foo(): Runnable { + return +} + +// ABSENT: nothingFoo +// ABSENT: genericFoo1 +// ABSENT: genericFoo2 +// EXIST: { itemText: "genericFoo3", tailText: "(t: Runnable) ()", typeText: "Runnable" } diff --git a/idea/testData/completion/weighers/smart/FunctionExpected.kt b/idea/testData/completion/weighers/smart/FunctionExpected.kt index f165b099643..f9767fcc6e7 100644 --- a/idea/testData/completion/weighers/smart/FunctionExpected.kt +++ b/idea/testData/completion/weighers/smart/FunctionExpected.kt @@ -9,6 +9,7 @@ fun bar(p: (String) -> Boolean) { // ORDER: ::doFilter // ORDER: p +// ORDER: countTo // ORDER: {...} // ORDER: { String -> ... } // ORDER: ::error diff --git a/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java index 18bf237dae1..650882cbb51 100644 --- a/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java @@ -19,7 +19,6 @@ 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; @@ -258,12 +257,48 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest(fileName); } + @TestMetadata("GenericFunction1.kt") + public void testGenericFunction1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/GenericFunction1.kt"); + doTest(fileName); + } + + @TestMetadata("GenericFunction2.kt") + public void testGenericFunction2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/GenericFunction2.kt"); + doTest(fileName); + } + + @TestMetadata("GenericFunction3.kt") + public void testGenericFunction3() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/GenericFunction3.kt"); + doTest(fileName); + } + + @TestMetadata("GenericFunction4.kt") + public void testGenericFunction4() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/GenericFunction4.kt"); + doTest(fileName); + } + + @TestMetadata("GenericFunction5.kt") + public void testGenericFunction5() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/GenericFunction5.kt"); + doTest(fileName); + } + @TestMetadata("GenericMethodArgument.kt") public void testGenericMethodArgument() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/GenericMethodArgument.kt"); doTest(fileName); } + @TestMetadata("GroupBySubstitutor.kt") + public void testGroupBySubstitutor() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/GroupBySubstitutor.kt"); + doTest(fileName); + } + @TestMetadata("IfCondition.kt") public void testIfCondition() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/IfCondition.kt"); @@ -732,6 +767,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest(fileName); } + @TestMetadata("SkipDeclarationsOfType.kt") + public void testSkipDeclarationsOfType() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/SkipDeclarationsOfType.kt"); + doTest(fileName); + } + @TestMetadata("SkipUnresolvedTypes.kt") public void testSkipUnresolvedTypes() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/SkipUnresolvedTypes.kt");