Smart completion: refactored one big class into a set of smaller ones

This commit is contained in:
Valentin Kipyatkov
2014-04-17 00:03:53 +04:00
parent c53da996c4
commit 9609a9fff1
10 changed files with 777 additions and 651 deletions
@@ -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;
@@ -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()