diff --git a/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java b/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java index 95567f38907..944fc0df7f3 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java @@ -39,6 +39,7 @@ import org.jetbrains.jet.lang.resolve.scopes.JetScope; import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.plugin.caches.JetShortNamesCache; import org.jetbrains.jet.plugin.codeInsight.TipsManager; +import org.jetbrains.jet.plugin.completion.smart.SmartCompletion; import org.jetbrains.jet.plugin.completion.weigher.WeigherPackage; import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache; import org.jetbrains.jet.plugin.project.ResolveSessionForBodies; diff --git a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt deleted file mode 100644 index dbc20e40bed..00000000000 --- a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt +++ /dev/null @@ -1,651 +0,0 @@ -package org.jetbrains.jet.plugin.completion - -import org.jetbrains.jet.lang.descriptors.* -import org.jetbrains.jet.lang.resolve.* -import org.jetbrains.jet.lang.psi.* -import org.jetbrains.jet.lexer.JetTokens -import org.jetbrains.jet.plugin.project.ResolveSessionForBodies -import org.jetbrains.jet.lang.types.* -import org.jetbrains.jet.lang.types.checker.JetTypeChecker -import com.intellij.codeInsight.lookup.* -import org.jetbrains.jet.renderer.DescriptorRenderer -import com.intellij.codeInsight.completion.* -import org.jetbrains.jet.plugin.completion.handlers.* -import com.google.common.collect.SetMultimap -import java.util.* -import org.jetbrains.jet.lang.resolve.calls.autocasts.* -import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns -import org.jetbrains.jet.lang.resolve.name.Name -import org.jetbrains.jet.lang.resolve.java.descriptor.JavaClassDescriptor -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler -import com.intellij.psi.PsiDocumentManager -import org.jetbrains.jet.plugin.codeInsight.ShortenReferences -import org.jetbrains.jet.di.InjectorForMacros -import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext -import org.jetbrains.jet.lang.resolve.calls.context.ContextDependency -import org.jetbrains.jet.lang.resolve.calls.context.CheckValueArgumentsMode -import org.jetbrains.jet.lang.resolve.calls.CompositeExtension -import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults -import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall -import org.jetbrains.jet.lang.resolve.calls.util.CallMaker -import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver -import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue -import com.intellij.lang.ASTNode -import org.jetbrains.jet.lang.resolve.scopes.JetScope -import org.jetbrains.jet.plugin.refactoring.JetNameSuggester -import org.jetbrains.jet.plugin.refactoring.JetNameValidator - -class SmartCompletion(val expression: JetSimpleNameExpression, - val resolveSession: ResolveSessionForBodies, - val visibilityFilter: (DeclarationDescriptor) -> Boolean) { - - private val bindingContext = resolveSession.resolveToElement(expression) - private val moduleDescriptor = resolveSession.getModuleDescriptor() - private val project = expression.getProject() - - private enum class Tail { - COMMA - PARENTHESIS - } - - private data class ExpectedTypeInfo(val `type`: JetType, val tail: Tail?) - - public fun buildLookupElements(referenceVariants: Iterable): Collection? { - val parent = expression.getParent() - val expressionWithType: JetExpression - val receiver: JetExpression? - if (parent is JetQualifiedExpression) { - expressionWithType = parent - receiver = parent.getReceiverExpression() - } - else { - expressionWithType = expression - receiver = null - } - - val allExpectedTypes = calcExpectedTypes(expressionWithType) ?: return null - val expectedTypes = allExpectedTypes.filter { !it.`type`.isError() } - if (expectedTypes.isEmpty()) return null - - val result = ArrayList() - - val typesOf: (DeclarationDescriptor) -> Iterable = typesWithAutoCasts(expressionWithType, receiver) - - val itemsToSkip = calcItemsToSkip(expressionWithType) - - val functionExpectedTypes = expectedTypes.filter { KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(it.`type`) } - - for (descriptor in referenceVariants) { - if (itemsToSkip.contains(descriptor)) continue - - val matchedExpectedTypes = expectedTypes.filter { expectedType -> - typesOf(descriptor).any { it.isSubtypeOf(expectedType.`type`) } - } - if (matchedExpectedTypes.isNotEmpty()) { - val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) - result.add(addTailToLookupElement(lookupElement, matchedExpectedTypes)) - } - - if (receiver == null) { - toFunctionReferenceLookupElement(descriptor, functionExpectedTypes)?.let { result.add(it) } - } - } - - if (receiver == null) { - result.addTypeInstantiationItems(expectedTypes) - - result.addStaticMembers(expressionWithType, expectedTypes) - - result.addThisItems(expressionWithType, expectedTypes) - - result.addLambdaItems(functionExpectedTypes) - } - - return result - } - - private fun calcExpectedTypes(expressionWithType: JetExpression): Collection? { - val expectedTypes = calcArgumentExpectedTypes(expressionWithType) - if (expectedTypes != null) { - return expectedTypes - } - else { - val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null - return listOf(ExpectedTypeInfo(expectedType, null)) - } - } - - private fun calcArgumentExpectedTypes(expressionWithType: JetExpression): Collection? { - val argument = expressionWithType.getParent() as? JetValueArgument ?: return null - if (argument.isNamed()) return null //TODO - support named arguments (also do not forget to check for presence of named arguments before) - val argumentList = argument.getParent() as JetValueArgumentList - val argumentIndex = argumentList.getArguments().indexOf(argument) - val callExpression = argumentList.getParent() as? JetCallExpression ?: return null - val calleeExpression = callExpression.getCalleeExpression() - - val parent = callExpression.getParent() - val receiver: ReceiverValue - val callOperationNode: ASTNode? - if (parent is JetQualifiedExpression && callExpression == parent.getSelectorExpression()) { - val receiverExpression = parent.getReceiverExpression() - val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE, receiverExpression] ?: return null - receiver = ExpressionReceiver(receiverExpression, expressionType) - callOperationNode = parent.getOperationTokenNode() - } - else { - receiver = ReceiverValue.NO_RECEIVER - callOperationNode = null - } - val call = CallMaker.makeCall(receiver, callOperationNode, callExpression) - - val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return null //TODO: discuss it - - val callResolutionContext = BasicCallResolutionContext.create( - DelegatingBindingTrace(bindingContext, "Temporary trace for smart completion"), - resolutionScope, - call, - bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE, - bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, callExpression] ?: DataFlowInfo.EMPTY, - ContextDependency.INDEPENDENT, - CheckValueArgumentsMode.ENABLED, - CompositeExtension(listOf()), - false).replaceCollectAllCandidates(true) - val callResolver = InjectorForMacros(expressionWithType.getProject(), moduleDescriptor).getCallResolver()!! - val results: OverloadResolutionResults = callResolver.resolveFunctionCall(callResolutionContext) - - val expectedTypes = HashSet() - for (candidate: ResolvedCall in results.getAllCandidates()!!) { - val parameters = candidate.getResultingDescriptor().getValueParameters() - if (parameters.size <= argumentIndex) continue - val parameterDescriptor = parameters[argumentIndex] - val tail = if (argumentIndex == parameters.size - 1) Tail.PARENTHESIS else Tail.COMMA - expectedTypes.add(ExpectedTypeInfo(parameterDescriptor.getType(), tail)) - } - return expectedTypes - } - - private fun calcItemsToSkip(expression: JetExpression): Collection { - val parent = expression.getParent() - when(parent) { - is JetProperty -> { - //TODO: this can be filtered out by ordinary completion - if (expression == parent.getInitializer()) { - return resolveSession.resolveToElement(parent)[BindingContext.DECLARATION_TO_DESCRIPTOR, parent].toList() - } - } - - is JetBinaryExpression -> { - if (parent.getRight() == expression && parent.getOperationToken() == JetTokens.EQ) { - val left = parent.getLeft() - if (left is JetReferenceExpression) { - return resolveSession.resolveToElement(left)[BindingContext.REFERENCE_TARGET, left].toList() - } - } - } - } - return listOf() - } - - private fun toFunctionReferenceLookupElement(descriptor: DeclarationDescriptor, - functionExpectedTypes: Collection): LookupElement? { - if (functionExpectedTypes.isEmpty()) return null - - fun toLookupElement(descriptor: FunctionDescriptor): LookupElement? { - val functionType = functionType(descriptor) - if (functionType == null) return null - - val matchedExpectedTypes = functionExpectedTypes.filter { functionType.isSubtypeOf(it.`type`) } - if (matchedExpectedTypes.isEmpty()) return null - - var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) - val text = "::" + (if (descriptor is ConstructorDescriptor) descriptor.getContainingDeclaration().getName() else descriptor.getName()) - lookupElement = object: LookupElementDecorator(lookupElement) { - override fun getLookupString() = text - - override fun renderElement(presentation: LookupElementPresentation) { - super.renderElement(presentation) - presentation.setItemText(text) - presentation.setTypeText("") - } - - override fun handleInsert(context: InsertionContext) { - } - } - - return addTailToLookupElement(lookupElement, matchedExpectedTypes) - } - - if (descriptor is SimpleFunctionDescriptor) { - return toLookupElement(descriptor) - } - else if (descriptor is ClassDescriptor && descriptor.getModality() != Modality.ABSTRACT) { - val constructors = descriptor.getConstructors().filter(visibilityFilter) - if (constructors.size == 1) { - //TODO: this code is to be changed if overloads to start work after :: - return toLookupElement(constructors.single()) - } - } - - return null - } - - private fun MutableCollection.addTypeInstantiationItems(expectedTypes: Collection) { - val expectedTypesGrouped: Map> = expectedTypes.groupBy { TypeUtils.makeNotNullable(it.`type`) } - for ((jetType, types) in expectedTypesGrouped) { - val tail = mergeTails(types.map { it.tail }) - addTypeInstantiationItems(jetType, tail) - } - } - - private fun MutableCollection.addTypeInstantiationItems(jetType: JetType, tail: Tail?) { - if (KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(jetType)) return // do not show "object: ..." for function types - - val classifier = jetType.getConstructor().getDeclarationDescriptor() - if (!(classifier is ClassDescriptor)) return - //TODO: check for constructor's visibility - - var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier) - - var lookupString = lookupElement.getLookupString() - - val typeArgs = jetType.getArguments() - var itemText = lookupString + DescriptorRenderer.TEXT.renderTypeArguments(typeArgs) - - val insertHandler: InsertHandler - val typeText = DescriptorUtils.getFqName(classifier).toString() + DescriptorRenderer.SOURCE_CODE.renderTypeArguments(typeArgs) - if (classifier.getModality() == Modality.ABSTRACT) { - val constructorParenthesis = if (classifier.getKind() != ClassKind.TRAIT) "()" else "" - itemText += constructorParenthesis - itemText = "object: " + itemText + "{...}" - lookupString = "object" //? - insertHandler = InsertHandler {(context, item) -> - val editor = context.getEditor() - val startOffset = context.getStartOffset() - val text = "object: $typeText$constructorParenthesis {}" - editor.getDocument().replaceString(startOffset, context.getTailOffset(), text) - editor.getCaretModel().moveToOffset(startOffset + text.length - 1) - - shortenReferences(context, startOffset, startOffset + text.length) - - ImplementMethodsHandler().invoke(context.getProject(), editor, context.getFile(), true) - } - lookupElement = lookupElement.suppressAutoInsertion() - } - else { - itemText += "()" - val constructors: Collection = classifier.getConstructors() - val caretPosition = - if (constructors.size == 0) - CaretPosition.AFTER_BRACKETS - else if (constructors.size == 1) - if (constructors.first().getValueParameters().isEmpty()) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS - else - CaretPosition.IN_BRACKETS - insertHandler = InsertHandler {(context, item) -> - val editor = context.getEditor() - val startOffset = context.getStartOffset() - val text = typeText + "()" - editor.getDocument().replaceString(startOffset, context.getTailOffset(), text) - val endOffset = startOffset + text.length - editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset) - - //TODO: auto-popup parameter info and other functionality from JetFunctionInsertHandler - - shortenReferences(context, startOffset, endOffset) - } - } - - lookupElement = object: LookupElementDecorator(lookupElement) { - override fun getLookupString() = lookupString - - override fun renderElement(presentation: LookupElementPresentation) { - getDelegate().renderElement(presentation) - presentation.setItemText(itemText) - } - - override fun handleInsert(context: InsertionContext) { - insertHandler.handleInsert(context, getDelegate()) - } - } - - add(addTailToLookupElement(lookupElement, tail)) - } - - private fun MutableCollection.addThisItems(context: JetExpression, expectedTypes: Collection) { - val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] - if (scope == null) return - - val receivers: List = scope.getImplicitReceiversHierarchy() - for (i in 0..receivers.size - 1) { - val receiver = receivers[i] - val thisType = receiver.getType() - val matchedExpectedTypes = expectedTypes.filter { thisType.isSubtypeOf(it.`type`) } - if (matchedExpectedTypes.notEmpty) { - //TODO: use this code when KT-4258 fixed - //val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: continue) - val qualifier = if (i == 0) null else thisQualifierName(receiver) ?: continue - val expressionText = if (qualifier == null) "this" else "this@" + qualifier - val lookupElement = LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.TEXT.renderType(thisType)) - add(addTailToLookupElement(lookupElement, matchedExpectedTypes)) - } - } - } - - private fun thisQualifierName(receiver: ReceiverParameterDescriptor): String? { - val descriptor: DeclarationDescriptor = receiver.getContainingDeclaration() - val name: Name = descriptor.getName() - if (!name.isSpecial()) return name.asString() - - val psiElement = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor) - val expression: JetExpression? = when (psiElement) { - is JetFunctionLiteral -> psiElement.getParent() as? JetFunctionLiteralExpression - is JetObjectDeclaration -> psiElement.getParent() as? JetObjectLiteralExpression - else -> null - } - return ((((expression?.getParent() as? JetValueArgument) - ?.getParent() as? JetValueArgumentList) - ?.getParent() as? JetCallExpression) - ?.getCalleeExpression() as? JetSimpleNameExpression) - ?.getReferencedName() - } - - private fun MutableCollection.addLambdaItems(functionExpectedTypes: Collection) { - val distinctTypes = functionExpectedTypes.map { it.`type` }.toSet() - - fun createLookupElement(lookupString: String, textBeforeCaret: String, textAfterCaret: String, shortenRefs: Boolean) - = LookupElementBuilder.create(lookupString) - .withInsertHandler(ArtificialElementInsertHandler(textBeforeCaret, textAfterCaret, shortenRefs)) - .suppressAutoInsertion() - - val singleType = if (distinctTypes.size == 1) distinctTypes.single() else null - val singleSignatureLength = singleType?.let { KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(it).size } - val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1 - if (offerNoParametersLambda) { - val lookupElement = createLookupElement("{...}", "{ ", " }", shortenRefs = false) - add(addTailToLookupElement(lookupElement, functionExpectedTypes)) - } - - if (singleSignatureLength != 0) { - fun functionParameterTypes(functionType: JetType) - = KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(functionType).map { it.getType() } - - for (functionType in distinctTypes) { - val parameterTypes = functionParameterTypes(functionType) - val parametersPresentation = parameterTypes.map { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it) }.makeString(", ") - - val useExplicitTypes = distinctTypes.stream().any { it != functionType && functionParameterTypes(it).size == parameterTypes.size } - val nameValidator = JetNameValidator.getEmptyValidator(project) - - fun parameterName(parameterType: JetType) = JetNameSuggester.suggestNames(parameterType, nameValidator, "p")[0] - - fun parameterText(parameterType: JetType): String { - return if (useExplicitTypes) - parameterName(parameterType) + ": " + DescriptorRenderer.SOURCE_CODE.renderType(parameterType) - else - parameterName(parameterType) - } - - val parametersText = parameterTypes.map(::parameterText).makeString(", ") - - val useParenthesis = parameterTypes.size != 1 - fun wrap(s: String) = if (useParenthesis) "($s)" else s - - val lookupString = "{ ${wrap(parametersPresentation)} -> ... }" - val lookupElement = createLookupElement(lookupString, "{ ${wrap(parametersText)} -> ", " }", shortenRefs = true) - add(addTailToLookupElement(lookupElement, functionExpectedTypes.filter { it.`type` == functionType })) - } - } - } - - private fun typesWithAutoCasts(expression: JetExpression, receiver: JetExpression?): (DeclarationDescriptor) -> Iterable { - val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expression] - val (variableToTypes: Map>, notNullVariables: Set) - = processDataFlowInfo(dataFlowInfo, receiver) - - fun typesOf(descriptor: DeclarationDescriptor): Iterable { - if (descriptor is CallableDescriptor) { - var returnType = descriptor.getReturnType() - if (returnType != null && KotlinBuiltIns.getInstance().isNothing(returnType!!)) { - //TODO: maybe we should include them on the second press? - return listOf() - } - if (descriptor is VariableDescriptor) { - if (notNullVariables.contains(descriptor) && returnType != null) { - returnType = TypeUtils.makeNotNullable(returnType!!) - } - - val autoCastTypes = variableToTypes[descriptor] - if (autoCastTypes != null && !autoCastTypes.isEmpty()) { - return autoCastTypes + returnType.toList() - } - } - return returnType.toList() - } - else if (descriptor is ClassDescriptor && descriptor.getKind() == ClassKind.ENUM_ENTRY) { - return listOf(descriptor.getDefaultType()) - } - else { - return listOf() - } - } - - return ::typesOf - } - - private data class ProcessDataFlowInfoResult( - val variableToTypes: Map> = Collections.emptyMap(), - val notNullVariables: Set = Collections.emptySet() - ) - - private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpression?): ProcessDataFlowInfoResult { - if (dataFlowInfo != null) { - val dataFlowValueToVariable: (DataFlowValue) -> VariableDescriptor? - if (receiver != null) { - val receiverType = bindingContext[BindingContext.EXPRESSION_TYPE, receiver] - if (receiverType != null) { - val receiverId = DataFlowValueFactory.createDataFlowValue(receiver, receiverType, bindingContext).getId() - dataFlowValueToVariable = {(value) -> - val id = value.getId() - if (id is com.intellij.openapi.util.Pair<*, *> && id.first == receiverId) id.second as? VariableDescriptor else null - } - } - else { - return ProcessDataFlowInfoResult() - } - } - else { - dataFlowValueToVariable = {(value) -> value.getId() as? VariableDescriptor } - } - - val variableToType = HashMap>() - val typeInfo: SetMultimap = dataFlowInfo.getCompleteTypeInfo() - for ((dataFlowValue, types) in typeInfo.asMap().entrySet()) { - val variable = dataFlowValueToVariable.invoke(dataFlowValue) - if (variable != null) { - variableToType[variable] = types - } - } - - val nullabilityInfo: Map = dataFlowInfo.getCompleteNullabilityInfo() - val notNullVariables = nullabilityInfo - .filter { it.getValue() == Nullability.NOT_NULL } - .map { dataFlowValueToVariable(it.getKey()) } - .filterNotNullTo(HashSet()) - - return ProcessDataFlowInfoResult(variableToType, notNullVariables) - } - - return ProcessDataFlowInfoResult() - } - - // adds java static members, enum members and members from class object - private fun MutableCollection.addStaticMembers(context: JetExpression, expectedTypes: Collection) { - val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] - if (scope == null) return - - val expectedTypesByClass = expectedTypes.groupBy { TypeUtils.getClassDescriptor(it.`type`) } - for ((classDescriptor, expectedTypesForClass) in expectedTypesByClass) { - if (classDescriptor != null && !classDescriptor.getName().isSpecial()) { - addStaticMembers(classDescriptor, expectedTypesForClass, scope) - } - } - } - - private fun MutableCollection.addStaticMembers(classDescriptor: ClassDescriptor, - expectedTypes: Collection, - scope: JetScope) { - - fun processMember(descriptor: DeclarationDescriptor) { - if (descriptor is DeclarationDescriptorWithVisibility && !Visibilities.isVisible(descriptor, scope.getContainingDeclaration())) return - - val matchedExpectedTypes = expectedTypes.filter { - expectedType -> - descriptor is CallableDescriptor && descriptor.getReturnType()?.let { it.isSubtypeOf(expectedType.`type`) } ?: false - || descriptor is ClassDescriptor && descriptor.getKind() == ClassKind.ENUM_ENTRY - } - if (matchedExpectedTypes.isEmpty()) return - - val lookupElement = createStaticMemberLookupElement(descriptor, classDescriptor) - add(addTailToLookupElement(lookupElement, matchedExpectedTypes)) - } - - if (classDescriptor is JavaClassDescriptor) { - val pseudoPackage = classDescriptor.getCorrespondingPackageFragment() - if (pseudoPackage != null) { - pseudoPackage.getMemberScope().getAllDescriptors().forEach(::processMember) - } - } - - val classObject = classDescriptor.getClassObjectDescriptor() - if (classObject != null) { - classObject.getDefaultType().getMemberScope().getAllDescriptors().forEach(::processMember) - } - - if (classDescriptor.getKind() == ClassKind.ENUM_CLASS) { - classDescriptor.getDefaultType().getMemberScope().getAllDescriptors().forEach(::processMember) - } - } - - - private fun createStaticMemberLookupElement(memberDescriptor: DeclarationDescriptor, classDescriptor: ClassDescriptor): LookupElement { - val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, memberDescriptor) - val qualifierPresentation = classDescriptor.getName().asString() - val lookupString = qualifierPresentation + "." + lookupElement.getLookupString() - val qualifierText = DescriptorUtils.getFqName(classDescriptor).asString() //TODO: escape keywords - - val caretPosition: CaretPosition? - if (memberDescriptor is FunctionDescriptor) { - caretPosition = if (memberDescriptor.getValueParameters().empty) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS - } - else { - caretPosition = null - } - - return object: LookupElementDecorator(lookupElement) { - override fun getLookupString() = lookupString - - override fun renderElement(presentation: LookupElementPresentation) { - getDelegate().renderElement(presentation) - - presentation.setItemText(qualifierPresentation + "." + presentation.getItemText()) - - val tailText = " (" + DescriptorUtils.getFqName(classDescriptor.getContainingDeclaration()) + ")" - if (memberDescriptor is FunctionDescriptor) { - presentation.appendTailText(tailText, true) - } - else { - presentation.setTailText(tailText, true) - } - - if (presentation.getTypeText().isNullOrEmpty()) { - presentation.setTypeText(DescriptorRenderer.TEXT.renderType(classDescriptor.getDefaultType())) - } - } - - override fun handleInsert(context: InsertionContext) { - val editor = context.getEditor() - val startOffset = context.getStartOffset() - var text = qualifierText + "." + memberDescriptor.getName().asString() //TODO: escape - if (memberDescriptor is FunctionDescriptor) { - text += "()" - //TODO: auto-popup parameter info and other functionality from JetFunctionInsertHandler - } - - editor.getDocument().replaceString(startOffset, context.getTailOffset(), text) - val endOffset = startOffset + text.length - editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset) - - shortenReferences(context, startOffset, startOffset + qualifierText.length) - } - } - } - - class object { - private class ArtificialElementInsertHandler( - val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler{ - override fun handleInsert(context: InsertionContext, item: LookupElement) { - val offset = context.getEditor().getCaretModel().getOffset() - val startOffset = offset - item.getLookupString().length - context.getDocument().deleteString(startOffset, offset) // delete inserted lookup string - context.getDocument().insertString(startOffset, textBeforeCaret + textAfterCaret) - context.getEditor().getCaretModel().moveToOffset(startOffset + textBeforeCaret.length) - - if (shortenRefs) { - shortenReferences(context, startOffset, startOffset + textBeforeCaret.length + textAfterCaret.length) - } - } - } - - private fun shortenReferences(context: InsertionContext, startOffset: Int, endOffset: Int) { - PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); - val file = context.getFile() as JetFile - val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, javaClass()) - if (element != null) { - ShortenReferences.process(element) - } - } - - private fun mergeTails(tails: Collection): Tail? { - if (tails.size == 1) return tails.single() - return if (HashSet(tails).size == 1) tails.first() else null - } - - private fun addTailToLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement { - return when (tail) { - null -> lookupElement - - Tail.COMMA -> object: LookupElementDecorator(lookupElement) { - override fun handleInsert(context: InsertionContext) { - WithTailInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, lookupElement) - } - } - - Tail.PARENTHESIS -> object: LookupElementDecorator(lookupElement) { - override fun handleInsert(context: InsertionContext) { - WithTailInsertHandler(')', false).handleInsert(context, lookupElement) - } - } - } - } - - private fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedTypes: Collection): LookupElement - = addTailToLookupElement(lookupElement, mergeTails(matchedExpectedTypes.map { it.tail })) - - private fun LookupElement.suppressAutoInsertion() = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(this) - - private fun functionType(function: FunctionDescriptor): JetType? { - return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(), - null, - function.getValueParameters().map { it.getType() }, - function.getReturnType() ?: return null, - function.getReceiverParameter() != null) - } - - private fun JetType.isSubtypeOf(expectedType: JetType) = !isError() && JetTypeChecker.INSTANCE.isSubtypeOf(this, expectedType) - - private fun T?.toList(): List = if (this != null) listOf(this) else listOf() - - private fun String?.isNullOrEmpty() = this == null || this.isEmpty() - } -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/ExpectedTypes.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/ExpectedTypes.kt new file mode 100644 index 00000000000..37d2214e88e --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/ExpectedTypes.kt @@ -0,0 +1,87 @@ +package org.jetbrains.jet.plugin.completion.smart + +import org.jetbrains.jet.lang.psi.JetExpression +import org.jetbrains.jet.lang.resolve.BindingContext +import org.jetbrains.jet.lang.psi.JetValueArgument +import org.jetbrains.jet.lang.psi.JetValueArgumentList +import org.jetbrains.jet.lang.psi.JetCallExpression +import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue +import com.intellij.lang.ASTNode +import org.jetbrains.jet.lang.psi.JetQualifiedExpression +import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver +import org.jetbrains.jet.lang.resolve.calls.util.CallMaker +import org.jetbrains.jet.lang.resolve.calls.context.BasicCallResolutionContext +import org.jetbrains.jet.lang.resolve.DelegatingBindingTrace +import org.jetbrains.jet.lang.types.TypeUtils +import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo +import org.jetbrains.jet.lang.resolve.calls.context.ContextDependency +import org.jetbrains.jet.lang.resolve.calls.context.CheckValueArgumentsMode +import org.jetbrains.jet.lang.resolve.calls.CompositeExtension +import org.jetbrains.jet.di.InjectorForMacros +import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor +import java.util.HashSet +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall +import org.jetbrains.jet.lang.descriptors.ModuleDescriptor + +class ExpectedTypes(val bindingContext: BindingContext, val moduleDescriptor: ModuleDescriptor) { + public fun calculate(expressionWithType: JetExpression): Collection? { + val expectedTypes = calculateForArgument(expressionWithType) + if (expectedTypes != null) { + return expectedTypes + } + else { + val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null + return listOf(ExpectedTypeInfo(expectedType, null)) + } + } + + private fun calculateForArgument(expressionWithType: JetExpression): Collection? { + val argument = expressionWithType.getParent() as? JetValueArgument ?: return null + if (argument.isNamed()) return null //TODO - support named arguments (also do not forget to check for presence of named arguments before) + val argumentList = argument.getParent() as JetValueArgumentList + val argumentIndex = argumentList.getArguments().indexOf(argument) + val callExpression = argumentList.getParent() as? JetCallExpression ?: return null + val calleeExpression = callExpression.getCalleeExpression() + + val parent = callExpression.getParent() + val receiver: ReceiverValue + val callOperationNode: ASTNode? + if (parent is JetQualifiedExpression && callExpression == parent.getSelectorExpression()) { + val receiverExpression = parent.getReceiverExpression() + val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE, receiverExpression] ?: return null + receiver = ExpressionReceiver(receiverExpression, expressionType) + callOperationNode = parent.getOperationTokenNode() + } + else { + receiver = ReceiverValue.NO_RECEIVER + callOperationNode = null + } + val call = CallMaker.makeCall(receiver, callOperationNode, callExpression) + + val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return null //TODO: discuss it + + val callResolutionContext = BasicCallResolutionContext.create( + DelegatingBindingTrace(bindingContext, "Temporary trace for smart completion"), + resolutionScope, + call, + bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE, + bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, callExpression] ?: DataFlowInfo.EMPTY, + ContextDependency.INDEPENDENT, + CheckValueArgumentsMode.ENABLED, + CompositeExtension(listOf()), + false).replaceCollectAllCandidates(true) + val callResolver = InjectorForMacros(expressionWithType.getProject(), moduleDescriptor).getCallResolver()!! + val results: OverloadResolutionResults = callResolver.resolveFunctionCall(callResolutionContext) + + val expectedTypes = HashSet() + for (candidate: ResolvedCall in results.getAllCandidates()!!) { + val parameters = candidate.getResultingDescriptor().getValueParameters() + if (parameters.size <= argumentIndex) continue + val parameterDescriptor = parameters[argumentIndex] + val tail = if (argumentIndex == parameters.size - 1) Tail.PARENTHESIS else Tail.COMMA + expectedTypes.add(ExpectedTypeInfo(parameterDescriptor.getType(), tail)) + } + return expectedTypes + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/LambdaItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/LambdaItems.kt new file mode 100644 index 00000000000..17ad8472100 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/LambdaItems.kt @@ -0,0 +1,61 @@ +package org.jetbrains.jet.plugin.completion.smart + +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns +import org.jetbrains.jet.lang.types.JetType +import org.jetbrains.jet.renderer.DescriptorRenderer +import org.jetbrains.jet.plugin.refactoring.JetNameValidator +import org.jetbrains.jet.plugin.refactoring.JetNameSuggester +import com.intellij.openapi.project.Project + +class LambdaItems(val project: Project) { + public fun addToCollection(collection: MutableCollection, functionExpectedTypes: Collection) { + val distinctTypes = functionExpectedTypes.map { it.`type` }.toSet() + + fun createLookupElement(lookupString: String, textBeforeCaret: String, textAfterCaret: String, shortenRefs: Boolean) + = LookupElementBuilder.create(lookupString) + .withInsertHandler(ArtificialElementInsertHandler(textBeforeCaret, textAfterCaret, shortenRefs)) + .suppressAutoInsertion() + + val singleType = if (distinctTypes.size == 1) distinctTypes.single() else null + val singleSignatureLength = singleType?.let { KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(it).size } + val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1 + if (offerNoParametersLambda) { + val lookupElement = createLookupElement("{...}", "{ ", " }", shortenRefs = false) + collection.add(addTailToLookupElement(lookupElement, functionExpectedTypes)) + } + + if (singleSignatureLength != 0) { + fun functionParameterTypes(functionType: JetType) + = KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(functionType).map { it.getType() } + + for (functionType in distinctTypes) { + val parameterTypes = functionParameterTypes(functionType) + val parametersPresentation = parameterTypes.map { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it) }.makeString(", ") + + val useExplicitTypes = distinctTypes.stream().any { it != functionType && functionParameterTypes(it).size == parameterTypes.size } + val nameValidator = JetNameValidator.getEmptyValidator(project) + + fun parameterName(parameterType: JetType) = JetNameSuggester.suggestNames(parameterType, nameValidator, "p")[0] + + fun parameterText(parameterType: JetType): String { + return if (useExplicitTypes) + parameterName(parameterType) + ": " + DescriptorRenderer.SOURCE_CODE.renderType(parameterType) + else + parameterName(parameterType) + } + + val parametersText = parameterTypes.map(::parameterText).makeString(", ") + + val useParenthesis = parameterTypes.size != 1 + fun wrap(s: String) = if (useParenthesis) "($s)" else s + + val lookupString = "{ ${wrap(parametersPresentation)} -> ... }" + val lookupElement = createLookupElement(lookupString, "{ ${wrap(parametersText)} -> ", " }", shortenRefs = true) + collection.add(addTailToLookupElement(lookupElement, functionExpectedTypes.filter { it.`type` == functionType })) + } + } + } + +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt new file mode 100644 index 00000000000..a6267ce7750 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/SmartCompletion.kt @@ -0,0 +1,148 @@ +package org.jetbrains.jet.plugin.completion.smart + +import org.jetbrains.jet.lang.descriptors.* +import org.jetbrains.jet.lang.resolve.* +import org.jetbrains.jet.lang.psi.* +import org.jetbrains.jet.lexer.JetTokens +import org.jetbrains.jet.plugin.project.ResolveSessionForBodies +import org.jetbrains.jet.lang.types.* +import com.intellij.codeInsight.lookup.* +import com.intellij.codeInsight.completion.* +import java.util.* +import org.jetbrains.jet.plugin.completion.DescriptorLookupConverter +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns + +enum class Tail { + COMMA + PARENTHESIS +} + +data class ExpectedTypeInfo(val `type`: JetType, val tail: Tail?) + +class SmartCompletion(val expression: JetSimpleNameExpression, + val resolveSession: ResolveSessionForBodies, + val visibilityFilter: (DeclarationDescriptor) -> Boolean) { + + private val bindingContext = resolveSession.resolveToElement(expression) + private val moduleDescriptor = resolveSession.getModuleDescriptor() + private val project = expression.getProject() + + public fun buildLookupElements(referenceVariants: Iterable): Collection? { + val parent = expression.getParent() + val expressionWithType: JetExpression + val receiver: JetExpression? + if (parent is JetQualifiedExpression) { + expressionWithType = parent + receiver = parent.getReceiverExpression() + } + else { + expressionWithType = expression + receiver = null + } + + val allExpectedTypes = ExpectedTypes(bindingContext, moduleDescriptor).calculate(expressionWithType) ?: return null + val expectedTypes = allExpectedTypes.filter { !it.`type`.isError() } + if (expectedTypes.isEmpty()) return null + + val result = ArrayList() + + val typesWithAutoCasts: (DeclarationDescriptor) -> Iterable = TypesWithAutoCasts(bindingContext).calculate(expressionWithType, receiver) + + val itemsToSkip = calcItemsToSkip(expressionWithType) + + val functionExpectedTypes = expectedTypes.filter { KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(it.`type`) } + + for (descriptor in referenceVariants) { + if (itemsToSkip.contains(descriptor)) continue + + val matchedExpectedTypes = expectedTypes.filter { expectedType -> + typesWithAutoCasts(descriptor).any { it.isSubtypeOf(expectedType.`type`) } + } + if (matchedExpectedTypes.isNotEmpty()) { + val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) + result.add(addTailToLookupElement(lookupElement, matchedExpectedTypes)) + } + + if (receiver == null) { + toFunctionReferenceLookupElement(descriptor, functionExpectedTypes)?.let { result.add(it) } + } + } + + if (receiver == null) { + TypeInstantiationItems(bindingContext, resolveSession).addToCollection(result, expectedTypes) + + StaticMembers(bindingContext, resolveSession).addToCollection(result, expectedTypes, expression) + + ThisItems(bindingContext).addToCollection(result, expressionWithType, expectedTypes) + + LambdaItems(project).addToCollection(result, functionExpectedTypes) + } + + return result + } + + private fun calcItemsToSkip(expression: JetExpression): Collection { + val parent = expression.getParent() + when(parent) { + is JetProperty -> { + //TODO: this can be filtered out by ordinary completion + if (expression == parent.getInitializer()) { + return resolveSession.resolveToElement(parent)[BindingContext.DECLARATION_TO_DESCRIPTOR, parent].toList() + } + } + + is JetBinaryExpression -> { + if (parent.getRight() == expression && parent.getOperationToken() == JetTokens.EQ) { + val left = parent.getLeft() + if (left is JetReferenceExpression) { + return resolveSession.resolveToElement(left)[BindingContext.REFERENCE_TARGET, left].toList() + } + } + } + } + return listOf() + } + + private fun toFunctionReferenceLookupElement(descriptor: DeclarationDescriptor, + functionExpectedTypes: Collection): LookupElement? { + if (functionExpectedTypes.isEmpty()) return null + + fun toLookupElement(descriptor: FunctionDescriptor): LookupElement? { + val functionType = functionType(descriptor) + if (functionType == null) return null + + val matchedExpectedTypes = functionExpectedTypes.filter { functionType.isSubtypeOf(it.`type`) } + if (matchedExpectedTypes.isEmpty()) return null + + var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) + val text = "::" + (if (descriptor is ConstructorDescriptor) descriptor.getContainingDeclaration().getName() else descriptor.getName()) + lookupElement = object: LookupElementDecorator(lookupElement) { + override fun getLookupString() = text + + override fun renderElement(presentation: LookupElementPresentation) { + super.renderElement(presentation) + presentation.setItemText(text) + presentation.setTypeText("") + } + + override fun handleInsert(context: InsertionContext) { + } + } + + return addTailToLookupElement(lookupElement, matchedExpectedTypes) + } + + if (descriptor is SimpleFunctionDescriptor) { + return toLookupElement(descriptor) + } + else if (descriptor is ClassDescriptor && descriptor.getModality() != Modality.ABSTRACT) { + val constructors = descriptor.getConstructors().filter(visibilityFilter) + if (constructors.size == 1) { + //TODO: this code is to be changed if overloads to start work after :: + return toLookupElement(constructors.single()) + } + } + + return null + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt new file mode 100644 index 00000000000..d8fb706f583 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/StaticMembers.kt @@ -0,0 +1,128 @@ +package org.jetbrains.jet.plugin.completion.smart + +import com.intellij.codeInsight.lookup.LookupElement +import org.jetbrains.jet.lang.types.TypeUtils +import org.jetbrains.jet.lang.descriptors.ClassDescriptor +import org.jetbrains.jet.lang.resolve.scopes.JetScope +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptorWithVisibility +import org.jetbrains.jet.lang.descriptors.Visibilities +import org.jetbrains.jet.lang.descriptors.CallableDescriptor +import org.jetbrains.jet.lang.descriptors.ClassKind +import org.jetbrains.jet.lang.resolve.java.descriptor.JavaClassDescriptor +import org.jetbrains.jet.plugin.completion.DescriptorLookupConverter +import org.jetbrains.jet.lang.resolve.DescriptorUtils +import org.jetbrains.jet.plugin.completion.handlers.CaretPosition +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor +import com.intellij.codeInsight.lookup.LookupElementDecorator +import com.intellij.codeInsight.lookup.LookupElementPresentation +import org.jetbrains.jet.renderer.DescriptorRenderer +import com.intellij.codeInsight.completion.InsertionContext +import org.jetbrains.jet.plugin.project.ResolveSessionForBodies +import org.jetbrains.jet.lang.resolve.BindingContext +import org.jetbrains.jet.lang.psi.JetExpression + +// adds java static members, enum members and members from class object +class StaticMembers(val bindingContext: BindingContext, val resolveSession: ResolveSessionForBodies) { + public fun addToCollection(collection: MutableCollection, expectedTypes: Collection, context: JetExpression) { + val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] + if (scope == null) return + + val expectedTypesByClass = expectedTypes.groupBy { TypeUtils.getClassDescriptor(it.`type`) } + for ((classDescriptor, expectedTypesForClass) in expectedTypesByClass) { + if (classDescriptor != null && !classDescriptor.getName().isSpecial()) { + addToCollection(collection, classDescriptor, expectedTypesForClass, scope) + } + } + } + + private fun addToCollection( + collection: MutableCollection, + classDescriptor: ClassDescriptor, + expectedTypes: Collection, + scope: JetScope) { + + fun processMember(descriptor: DeclarationDescriptor) { + if (descriptor is DeclarationDescriptorWithVisibility && !Visibilities.isVisible(descriptor, scope.getContainingDeclaration())) return + + val matchedExpectedTypes = expectedTypes.filter { + expectedType -> + descriptor is CallableDescriptor && descriptor.getReturnType()?.let { it.isSubtypeOf(expectedType.`type`) } ?: false + || descriptor is ClassDescriptor && descriptor.getKind() == ClassKind.ENUM_ENTRY + } + if (matchedExpectedTypes.isEmpty()) return + + val lookupElement = createLookupElement(descriptor, classDescriptor) + collection.add(addTailToLookupElement(lookupElement, matchedExpectedTypes)) + } + + if (classDescriptor is JavaClassDescriptor) { + val pseudoPackage = classDescriptor.getCorrespondingPackageFragment() + if (pseudoPackage != null) { + pseudoPackage.getMemberScope().getAllDescriptors().forEach(::processMember) + } + } + + val classObject = classDescriptor.getClassObjectDescriptor() + if (classObject != null) { + classObject.getDefaultType().getMemberScope().getAllDescriptors().forEach(::processMember) + } + + if (classDescriptor.getKind() == ClassKind.ENUM_CLASS) { + classDescriptor.getDefaultType().getMemberScope().getAllDescriptors().forEach(::processMember) + } + } + + private fun createLookupElement(memberDescriptor: DeclarationDescriptor, classDescriptor: ClassDescriptor): LookupElement { + val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, memberDescriptor) + val qualifierPresentation = classDescriptor.getName().asString() + val lookupString = qualifierPresentation + "." + lookupElement.getLookupString() + val qualifierText = DescriptorUtils.getFqName(classDescriptor).asString() //TODO: escape keywords + + val caretPosition: CaretPosition? + if (memberDescriptor is FunctionDescriptor) { + caretPosition = if (memberDescriptor.getValueParameters().empty) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS + } + else { + caretPosition = null + } + + return object: LookupElementDecorator(lookupElement) { + override fun getLookupString() = lookupString + + override fun renderElement(presentation: LookupElementPresentation) { + getDelegate().renderElement(presentation) + + presentation.setItemText(qualifierPresentation + "." + presentation.getItemText()) + + val tailText = " (" + DescriptorUtils.getFqName(classDescriptor.getContainingDeclaration()) + ")" + if (memberDescriptor is FunctionDescriptor) { + presentation.appendTailText(tailText, true) + } + else { + presentation.setTailText(tailText, true) + } + + if (presentation.getTypeText().isNullOrEmpty()) { + presentation.setTypeText(DescriptorRenderer.TEXT.renderType(classDescriptor.getDefaultType())) + } + } + + override fun handleInsert(context: InsertionContext) { + val editor = context.getEditor() + val startOffset = context.getStartOffset() + var text = qualifierText + "." + memberDescriptor.getName().asString() //TODO: escape + if (memberDescriptor is FunctionDescriptor) { + text += "()" + //TODO: auto-popup parameter info and other functionality from JetFunctionInsertHandler + } + + editor.getDocument().replaceString(startOffset, context.getTailOffset(), text) + val endOffset = startOffset + text.length + editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset) + + shortenReferences(context, startOffset, startOffset + qualifierText.length) + } + } + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt new file mode 100644 index 00000000000..a23c6f0aa6a --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/ThisItems.kt @@ -0,0 +1,60 @@ +package org.jetbrains.jet.plugin.completion.smart + +import com.intellij.codeInsight.lookup.LookupElement +import org.jetbrains.jet.lang.psi.JetExpression +import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor +import com.intellij.codeInsight.lookup.LookupElementBuilder +import org.jetbrains.jet.renderer.DescriptorRenderer +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor +import org.jetbrains.jet.lang.resolve.name.Name +import org.jetbrains.jet.lang.resolve.BindingContextUtils +import org.jetbrains.jet.lang.psi.JetFunctionLiteral +import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression +import org.jetbrains.jet.lang.psi.JetObjectDeclaration +import org.jetbrains.jet.lang.psi.JetObjectLiteralExpression +import org.jetbrains.jet.lang.psi.JetValueArgument +import org.jetbrains.jet.lang.psi.JetValueArgumentList +import org.jetbrains.jet.lang.psi.JetCallExpression +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression +import org.jetbrains.jet.lang.resolve.BindingContext + +class ThisItems(val bindingContext: BindingContext) { + public fun addToCollection(collection: MutableCollection, context: JetExpression, expectedTypes: Collection) { + val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] + if (scope == null) return + + val receivers: List = scope.getImplicitReceiversHierarchy() + for (i in 0..receivers.size - 1) { + val receiver = receivers[i] + val thisType = receiver.getType() + val matchedExpectedTypes = expectedTypes.filter { thisType.isSubtypeOf(it.`type`) } + if (matchedExpectedTypes.notEmpty) { + //TODO: use this code when KT-4258 fixed + //val expressionText = if (i == 0) "this" else "this@" + (thisQualifierName(receiver, bindingContext) ?: continue) + val qualifier = if (i == 0) null else thisQualifierName(receiver) ?: continue + val expressionText = if (qualifier == null) "this" else "this@" + qualifier + val lookupElement = LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.TEXT.renderType(thisType)) + collection.add(addTailToLookupElement(lookupElement, matchedExpectedTypes)) + } + } + } + + private fun thisQualifierName(receiver: ReceiverParameterDescriptor): String? { + val descriptor: DeclarationDescriptor = receiver.getContainingDeclaration() + val name: Name = descriptor.getName() + if (!name.isSpecial()) return name.asString() + + val psiElement = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor) + val expression: JetExpression? = when (psiElement) { + is JetFunctionLiteral -> psiElement.getParent() as? JetFunctionLiteralExpression + is JetObjectDeclaration -> psiElement.getParent() as? JetObjectLiteralExpression + else -> null + } + return ((((expression?.getParent() as? JetValueArgument) + ?.getParent() as? JetValueArgumentList) + ?.getParent() as? JetCallExpression) + ?.getCalleeExpression() as? JetSimpleNameExpression) + ?.getReferencedName() + } + +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt new file mode 100644 index 00000000000..56030556e21 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypeInstantiationItems.kt @@ -0,0 +1,105 @@ +package org.jetbrains.jet.plugin.completion.smart + +import com.intellij.codeInsight.lookup.LookupElement +import org.jetbrains.jet.lang.types.JetType +import org.jetbrains.jet.lang.types.TypeUtils +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns +import org.jetbrains.jet.lang.descriptors.ClassDescriptor +import org.jetbrains.jet.plugin.completion.DescriptorLookupConverter +import org.jetbrains.jet.renderer.DescriptorRenderer +import com.intellij.codeInsight.completion.InsertHandler +import org.jetbrains.jet.lang.resolve.DescriptorUtils +import org.jetbrains.jet.lang.descriptors.Modality +import org.jetbrains.jet.lang.descriptors.ClassKind +import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler +import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor +import org.jetbrains.jet.plugin.completion.handlers.CaretPosition +import com.intellij.codeInsight.lookup.LookupElementDecorator +import com.intellij.codeInsight.lookup.LookupElementPresentation +import com.intellij.codeInsight.completion.InsertionContext +import org.jetbrains.jet.lang.resolve.BindingContext +import org.jetbrains.jet.plugin.project.ResolveSessionForBodies + +class TypeInstantiationItems(val bindingContext: BindingContext, val resolveSession: ResolveSessionForBodies) { + public fun addToCollection(collection: MutableCollection, expectedTypes: Collection) { + val expectedTypesGrouped: Map> = expectedTypes.groupBy { TypeUtils.makeNotNullable(it.`type`) } + for ((jetType, types) in expectedTypesGrouped) { + val tail = mergeTails(types.map { it.tail }) + addToCollection(collection, jetType, tail) + } + } + + private fun addToCollection(collection: MutableCollection, jetType: JetType, tail: Tail?) { + if (KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(jetType)) return // do not show "object: ..." for function types + + val classifier = jetType.getConstructor().getDeclarationDescriptor() + if (!(classifier is ClassDescriptor)) return + //TODO: check for constructor's visibility + + var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier) + + var lookupString = lookupElement.getLookupString() + + val typeArgs = jetType.getArguments() + var itemText = lookupString + DescriptorRenderer.TEXT.renderTypeArguments(typeArgs) + + val insertHandler: InsertHandler + val typeText = DescriptorUtils.getFqName(classifier).toString() + DescriptorRenderer.SOURCE_CODE.renderTypeArguments(typeArgs) + if (classifier.getModality() == Modality.ABSTRACT) { + val constructorParenthesis = if (classifier.getKind() != ClassKind.TRAIT) "()" else "" + itemText += constructorParenthesis + itemText = "object: " + itemText + "{...}" + lookupString = "object" //? + insertHandler = InsertHandler {(context, item) -> + val editor = context.getEditor() + val startOffset = context.getStartOffset() + val text = "object: $typeText$constructorParenthesis {}" + editor.getDocument().replaceString(startOffset, context.getTailOffset(), text) + editor.getCaretModel().moveToOffset(startOffset + text.length - 1) + + shortenReferences(context, startOffset, startOffset + text.length) + + ImplementMethodsHandler().invoke(context.getProject(), editor, context.getFile(), true) + } + lookupElement = lookupElement.suppressAutoInsertion() + } + else { + itemText += "()" + val constructors: Collection = classifier.getConstructors() + val caretPosition = + if (constructors.size == 0) + CaretPosition.AFTER_BRACKETS + else if (constructors.size == 1) + if (constructors.first().getValueParameters().isEmpty()) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS + else + CaretPosition.IN_BRACKETS + insertHandler = InsertHandler {(context, item) -> + val editor = context.getEditor() + val startOffset = context.getStartOffset() + val text = typeText + "()" + editor.getDocument().replaceString(startOffset, context.getTailOffset(), text) + val endOffset = startOffset + text.length + editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset) + + //TODO: auto-popup parameter info and other functionality from JetFunctionInsertHandler + + shortenReferences(context, startOffset, endOffset) + } + } + + lookupElement = object: LookupElementDecorator(lookupElement) { + override fun getLookupString() = lookupString + + override fun renderElement(presentation: LookupElementPresentation) { + getDelegate().renderElement(presentation) + presentation.setItemText(itemText) + } + + override fun handleInsert(context: InsertionContext) { + insertHandler.handleInsert(context, getDelegate()) + } + } + + collection.add(addTailToLookupElement(lookupElement, tail)) + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/TypesWithAutoCasts.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypesWithAutoCasts.kt new file mode 100644 index 00000000000..0a0e815620b --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/TypesWithAutoCasts.kt @@ -0,0 +1,103 @@ +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.types.TypeUtils +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.autocasts.DataFlowInfo +import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowValue +import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowValueFactory +import java.util.HashMap +import com.google.common.collect.SetMultimap +import org.jetbrains.jet.lang.resolve.calls.autocasts.Nullability +import java.util.HashSet +import org.jetbrains.jet.lang.resolve.BindingContext + +class TypesWithAutoCasts(val bindingContext: BindingContext) { + public fun calculate(expression: JetExpression, receiver: JetExpression?): (DeclarationDescriptor) -> Iterable { + val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expression] + val (variableToTypes: Map>, notNullVariables: Set) + = processDataFlowInfo(dataFlowInfo, receiver) + + fun typesOf(descriptor: DeclarationDescriptor): Iterable { + if (descriptor is CallableDescriptor) { + var returnType = descriptor.getReturnType() + if (returnType != null && KotlinBuiltIns.getInstance().isNothing(returnType!!)) { + //TODO: maybe we should include them on the second press? + return listOf() + } + if (descriptor is VariableDescriptor) { + if (notNullVariables.contains(descriptor) && returnType != null) { + returnType = TypeUtils.makeNotNullable(returnType!!) + } + + val autoCastTypes = variableToTypes[descriptor] + if (autoCastTypes != null && !autoCastTypes.isEmpty()) { + return autoCastTypes + returnType.toList() + } + } + return returnType.toList() + } + else if (descriptor is ClassDescriptor && descriptor.getKind() == ClassKind.ENUM_ENTRY) { + return listOf(descriptor.getDefaultType()) + } + else { + return listOf() + } + } + + return ::typesOf + } + + private data class ProcessDataFlowInfoResult( + val variableToTypes: Map> = Collections.emptyMap(), + val notNullVariables: Set = Collections.emptySet() + ) + + private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpression?): ProcessDataFlowInfoResult { + if (dataFlowInfo != null) { + val dataFlowValueToVariable: (DataFlowValue) -> VariableDescriptor? + if (receiver != null) { + val receiverType = bindingContext[BindingContext.EXPRESSION_TYPE, receiver] + if (receiverType != null) { + val receiverId = DataFlowValueFactory.createDataFlowValue(receiver, receiverType, bindingContext).getId() + dataFlowValueToVariable = {(value) -> + val id = value.getId() + if (id is com.intellij.openapi.util.Pair<*, *> && id.first == receiverId) id.second as? VariableDescriptor else null + } + } + else { + return ProcessDataFlowInfoResult() + } + } + else { + dataFlowValueToVariable = {(value) -> value.getId() as? VariableDescriptor } + } + + val variableToType = HashMap>() + val typeInfo: SetMultimap = dataFlowInfo.getCompleteTypeInfo() + for ((dataFlowValue, types) in typeInfo.asMap().entrySet()) { + val variable = dataFlowValueToVariable.invoke(dataFlowValue) + if (variable != null) { + variableToType[variable] = types + } + } + + val nullabilityInfo: Map = dataFlowInfo.getCompleteNullabilityInfo() + val notNullVariables = nullabilityInfo + .filter { it.getValue() == Nullability.NOT_NULL } + .map { dataFlowValueToVariable(it.getKey()) } + .filterNotNullTo(HashSet()) + + return ProcessDataFlowInfoResult(variableToType, notNullVariables) + } + + return ProcessDataFlowInfoResult() + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt b/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt new file mode 100644 index 00000000000..52dd57851e7 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/smart/Utils.kt @@ -0,0 +1,84 @@ +package org.jetbrains.jet.plugin.completion.smart + +import com.intellij.codeInsight.completion.InsertHandler +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.psi.PsiDocumentManager +import org.jetbrains.jet.lang.psi.JetFile +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.jet.lang.psi.JetElement +import org.jetbrains.jet.plugin.codeInsight.ShortenReferences +import java.util.HashSet +import com.intellij.codeInsight.lookup.LookupElementDecorator +import org.jetbrains.jet.plugin.completion.handlers.WithTailInsertHandler +import com.intellij.codeInsight.lookup.AutoCompletionPolicy +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 + +class ArtificialElementInsertHandler( + val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler{ + override fun handleInsert(context: InsertionContext, item: LookupElement) { + val offset = context.getEditor().getCaretModel().getOffset() + val startOffset = offset - item.getLookupString().length + context.getDocument().deleteString(startOffset, offset) // delete inserted lookup string + context.getDocument().insertString(startOffset, textBeforeCaret + textAfterCaret) + context.getEditor().getCaretModel().moveToOffset(startOffset + textBeforeCaret.length) + + if (shortenRefs) { + shortenReferences(context, startOffset, startOffset + textBeforeCaret.length + textAfterCaret.length) + } + } +} + +fun shortenReferences(context: InsertionContext, startOffset: Int, endOffset: Int) { + PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); + val file = context.getFile() as JetFile + val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, javaClass()) + if (element != null) { + ShortenReferences.process(element) + } +} + +fun mergeTails(tails: Collection): Tail? { + if (tails.size == 1) return tails.single() + return if (HashSet(tails).size == 1) tails.first() else null +} + +fun addTailToLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement { + return when (tail) { + null -> lookupElement + + Tail.COMMA -> object: LookupElementDecorator(lookupElement) { + override fun handleInsert(context: InsertionContext) { + WithTailInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, lookupElement) + } + } + + Tail.PARENTHESIS -> object: LookupElementDecorator(lookupElement) { + override fun handleInsert(context: InsertionContext) { + WithTailInsertHandler(')', false).handleInsert(context, lookupElement) + } + } + } +} + +fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedTypes: Collection): LookupElement + = addTailToLookupElement(lookupElement, mergeTails(matchedExpectedTypes.map { it.tail })) + +fun LookupElement.suppressAutoInsertion() = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(this) + +fun functionType(function: FunctionDescriptor): JetType? { + return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(), + null, + function.getValueParameters().map { it.getType() }, + function.getReturnType() ?: return null, + function.getReceiverParameter() != null) +} + +fun JetType.isSubtypeOf(expectedType: JetType) = !isError() && JetTypeChecker.INSTANCE.isSubtypeOf(this, expectedType) + +fun T?.toList(): List = if (this != null) listOf(this) else listOf() + +fun String?.isNullOrEmpty() = this == null || this.isEmpty()