Smart completion: refactored one big class into a set of smaller ones
This commit is contained in:
@@ -39,6 +39,7 @@ import org.jetbrains.jet.lang.resolve.scopes.JetScope;
|
|||||||
import org.jetbrains.jet.lexer.JetTokens;
|
import org.jetbrains.jet.lexer.JetTokens;
|
||||||
import org.jetbrains.jet.plugin.caches.JetShortNamesCache;
|
import org.jetbrains.jet.plugin.caches.JetShortNamesCache;
|
||||||
import org.jetbrains.jet.plugin.codeInsight.TipsManager;
|
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.completion.weigher.WeigherPackage;
|
||||||
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache;
|
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache;
|
||||||
import org.jetbrains.jet.plugin.project.ResolveSessionForBodies;
|
import org.jetbrains.jet.plugin.project.ResolveSessionForBodies;
|
||||||
|
|||||||
@@ -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<DeclarationDescriptor>): Collection<LookupElement>? {
|
|
||||||
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<LookupElement>()
|
|
||||||
|
|
||||||
val typesOf: (DeclarationDescriptor) -> Iterable<JetType> = 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<ExpectedTypeInfo>? {
|
|
||||||
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<ExpectedTypeInfo>? {
|
|
||||||
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<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
|
|
||||||
|
|
||||||
val expectedTypes = HashSet<ExpectedTypeInfo>()
|
|
||||||
for (candidate: ResolvedCall<FunctionDescriptor> 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<DeclarationDescriptor> {
|
|
||||||
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<ExpectedTypeInfo>): 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>(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<LookupElement>.addTypeInstantiationItems(expectedTypes: Collection<ExpectedTypeInfo>) {
|
|
||||||
val expectedTypesGrouped: Map<JetType, List<ExpectedTypeInfo>> = expectedTypes.groupBy { TypeUtils.makeNotNullable(it.`type`) }
|
|
||||||
for ((jetType, types) in expectedTypesGrouped) {
|
|
||||||
val tail = mergeTails(types.map { it.tail })
|
|
||||||
addTypeInstantiationItems(jetType, tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MutableCollection<LookupElement>.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<LookupElement>
|
|
||||||
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<LookupElement> {(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<ConstructorDescriptor> = 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<LookupElement> {(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>(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<LookupElement>.addThisItems(context: JetExpression, expectedTypes: Collection<ExpectedTypeInfo>) {
|
|
||||||
val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context]
|
|
||||||
if (scope == null) return
|
|
||||||
|
|
||||||
val receivers: List<ReceiverParameterDescriptor> = 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<LookupElement>.addLambdaItems(functionExpectedTypes: Collection<ExpectedTypeInfo>) {
|
|
||||||
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<JetType> {
|
|
||||||
val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expression]
|
|
||||||
val (variableToTypes: Map<VariableDescriptor, Collection<JetType>>, notNullVariables: Set<VariableDescriptor>)
|
|
||||||
= processDataFlowInfo(dataFlowInfo, receiver)
|
|
||||||
|
|
||||||
fun typesOf(descriptor: DeclarationDescriptor): Iterable<JetType> {
|
|
||||||
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<VariableDescriptor, Collection<JetType>> = Collections.emptyMap(),
|
|
||||||
val notNullVariables: Set<VariableDescriptor> = 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<VariableDescriptor, Collection<JetType>>()
|
|
||||||
val typeInfo: SetMultimap<DataFlowValue, JetType> = dataFlowInfo.getCompleteTypeInfo()
|
|
||||||
for ((dataFlowValue, types) in typeInfo.asMap().entrySet()) {
|
|
||||||
val variable = dataFlowValueToVariable.invoke(dataFlowValue)
|
|
||||||
if (variable != null) {
|
|
||||||
variableToType[variable] = types
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val nullabilityInfo: Map<DataFlowValue, Nullability> = dataFlowInfo.getCompleteNullabilityInfo()
|
|
||||||
val notNullVariables = nullabilityInfo
|
|
||||||
.filter { it.getValue() == Nullability.NOT_NULL }
|
|
||||||
.map { dataFlowValueToVariable(it.getKey()) }
|
|
||||||
.filterNotNullTo(HashSet<VariableDescriptor>())
|
|
||||||
|
|
||||||
return ProcessDataFlowInfoResult(variableToType, notNullVariables)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ProcessDataFlowInfoResult()
|
|
||||||
}
|
|
||||||
|
|
||||||
// adds java static members, enum members and members from class object
|
|
||||||
private fun MutableCollection<LookupElement>.addStaticMembers(context: JetExpression, expectedTypes: Collection<ExpectedTypeInfo>) {
|
|
||||||
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<LookupElement>.addStaticMembers(classDescriptor: ClassDescriptor,
|
|
||||||
expectedTypes: Collection<ExpectedTypeInfo>,
|
|
||||||
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>(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<LookupElement>{
|
|
||||||
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<JetElement>())
|
|
||||||
if (element != null) {
|
|
||||||
ShortenReferences.process(element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mergeTails(tails: Collection<Tail?>): 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>(lookupElement) {
|
|
||||||
override fun handleInsert(context: InsertionContext) {
|
|
||||||
WithTailInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, lookupElement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Tail.PARENTHESIS -> object: LookupElementDecorator<LookupElement>(lookupElement) {
|
|
||||||
override fun handleInsert(context: InsertionContext) {
|
|
||||||
WithTailInsertHandler(')', false).handleInsert(context, lookupElement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedTypes: Collection<ExpectedTypeInfo>): 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 : Any> T?.toList(): List<T> = if (this != null) listOf(this) else listOf()
|
|
||||||
|
|
||||||
private fun String?.isNullOrEmpty() = this == null || this.isEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<ExpectedTypeInfo>? {
|
||||||
|
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<ExpectedTypeInfo>? {
|
||||||
|
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<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
|
||||||
|
|
||||||
|
val expectedTypes = HashSet<ExpectedTypeInfo>()
|
||||||
|
for (candidate: ResolvedCall<FunctionDescriptor> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LookupElement>, functionExpectedTypes: Collection<ExpectedTypeInfo>) {
|
||||||
|
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 }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<DeclarationDescriptor>): Collection<LookupElement>? {
|
||||||
|
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<LookupElement>()
|
||||||
|
|
||||||
|
val typesWithAutoCasts: (DeclarationDescriptor) -> Iterable<JetType> = 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<DeclarationDescriptor> {
|
||||||
|
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<ExpectedTypeInfo>): 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>(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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LookupElement>, expectedTypes: Collection<ExpectedTypeInfo>, 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<LookupElement>,
|
||||||
|
classDescriptor: ClassDescriptor,
|
||||||
|
expectedTypes: Collection<ExpectedTypeInfo>,
|
||||||
|
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>(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LookupElement>, context: JetExpression, expectedTypes: Collection<ExpectedTypeInfo>) {
|
||||||
|
val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, context]
|
||||||
|
if (scope == null) return
|
||||||
|
|
||||||
|
val receivers: List<ReceiverParameterDescriptor> = 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<LookupElement>, expectedTypes: Collection<ExpectedTypeInfo>) {
|
||||||
|
val expectedTypesGrouped: Map<JetType, List<ExpectedTypeInfo>> = 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<LookupElement>, 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<LookupElement>
|
||||||
|
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<LookupElement> {(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<ConstructorDescriptor> = 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<LookupElement> {(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>(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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<JetType> {
|
||||||
|
val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expression]
|
||||||
|
val (variableToTypes: Map<VariableDescriptor, Collection<JetType>>, notNullVariables: Set<VariableDescriptor>)
|
||||||
|
= processDataFlowInfo(dataFlowInfo, receiver)
|
||||||
|
|
||||||
|
fun typesOf(descriptor: DeclarationDescriptor): Iterable<JetType> {
|
||||||
|
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<VariableDescriptor, Collection<JetType>> = Collections.emptyMap(),
|
||||||
|
val notNullVariables: Set<VariableDescriptor> = 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<VariableDescriptor, Collection<JetType>>()
|
||||||
|
val typeInfo: SetMultimap<DataFlowValue, JetType> = dataFlowInfo.getCompleteTypeInfo()
|
||||||
|
for ((dataFlowValue, types) in typeInfo.asMap().entrySet()) {
|
||||||
|
val variable = dataFlowValueToVariable.invoke(dataFlowValue)
|
||||||
|
if (variable != null) {
|
||||||
|
variableToType[variable] = types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val nullabilityInfo: Map<DataFlowValue, Nullability> = dataFlowInfo.getCompleteNullabilityInfo()
|
||||||
|
val notNullVariables = nullabilityInfo
|
||||||
|
.filter { it.getValue() == Nullability.NOT_NULL }
|
||||||
|
.map { dataFlowValueToVariable(it.getKey()) }
|
||||||
|
.filterNotNullTo(HashSet<VariableDescriptor>())
|
||||||
|
|
||||||
|
return ProcessDataFlowInfoResult(variableToType, notNullVariables)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessDataFlowInfoResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LookupElement>{
|
||||||
|
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<JetElement>())
|
||||||
|
if (element != null) {
|
||||||
|
ShortenReferences.process(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mergeTails(tails: Collection<Tail?>): 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>(lookupElement) {
|
||||||
|
override fun handleInsert(context: InsertionContext) {
|
||||||
|
WithTailInsertHandler(',', true /*TODO: use code style option*/).handleInsert(context, lookupElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tail.PARENTHESIS -> object: LookupElementDecorator<LookupElement>(lookupElement) {
|
||||||
|
override fun handleInsert(context: InsertionContext) {
|
||||||
|
WithTailInsertHandler(')', false).handleInsert(context, lookupElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedTypes: Collection<ExpectedTypeInfo>): 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 : Any> T?.toList(): List<T> = if (this != null) listOf(this) else listOf()
|
||||||
|
|
||||||
|
fun String?.isNullOrEmpty() = this == null || this.isEmpty()
|
||||||
Reference in New Issue
Block a user