From ae69bea54369f28e7cebe8f2cb70c9bd993e591c Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 23 Sep 2014 16:28:35 +0400 Subject: [PATCH] Create Function From Usage: Refactor the function builder --- .../CreateComponentFunctionActionFactory.kt | 8 +- .../CreateFunctionFromUsageFix.kt | 681 +----------------- .../CreateGetFunctionActionFactory.kt | 8 +- .../CreateHasNextFunctionActionFactory.kt | 6 +- .../CreateIteratorFunctionActionFactory.kt | 6 +- .../CreateNextFunctionActionFactory.kt | 6 +- .../CreateSetFunctionActionFactory.kt | 14 +- .../createFunction/FunctionInfo.kt | 63 ++ .../createFunction/functionBuilder.kt | 466 ++++++++++++ .../createFunction/templateExpressions.kt | 142 ++++ .../jetbrains/jet/plugin/util/TypeUtils.kt | 5 + 11 files changed, 730 insertions(+), 675 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/FunctionInfo.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/functionBuilder.kt create mode 100644 idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/templateExpressions.kt diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateComponentFunctionActionFactory.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateComponentFunctionActionFactory.kt index 1e1e42e0221..66e72a3ef26 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateComponentFunctionActionFactory.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateComponentFunctionActionFactory.kt @@ -22,17 +22,17 @@ object CreateComponentFunctionActionFactory : JetSingleIntentionActionFactory() val ownerType = if (multiDeclaration == null) { val forExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass())!! multiDeclaration = forExpr.getMultiParameter()!! - TypeOrExpressionThereof(diagnosticWithParameters.getB(), Variance.IN_VARIANCE) + TypeInfo(diagnosticWithParameters.getB(), Variance.IN_VARIANCE) } else { val rhs = multiDeclaration!!.getInitializer() ?: return null - TypeOrExpressionThereof(rhs, Variance.IN_VARIANCE) + TypeInfo(rhs, Variance.IN_VARIANCE) } val entries = multiDeclaration!!.getEntries() val entry = entries[componentNumber] - val returnType = TypeOrExpressionThereof(entry, Variance.OUT_VARIANCE) + val returnType = TypeInfo(entry, Variance.OUT_VARIANCE) - return CreateFunctionFromUsageFix(multiDeclaration!!, ownerType, name.getIdentifier(), returnType) + return CreateFunctionFromUsageFix(multiDeclaration!!, FunctionInfo(name.getIdentifier(), ownerType, returnType)) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateFunctionFromUsageFix.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateFunctionFromUsageFix.kt index 4930333616b..1a54bf1af89 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateFunctionFromUsageFix.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateFunctionFromUsageFix.kt @@ -1,333 +1,34 @@ -/* - * Copyright 2010-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.jetbrains.jet.plugin.quickfix.createFromUsage.createFunction -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.codeInsight.lookup.LookupElementBuilder -import com.intellij.codeInsight.navigation.NavigationUtil -import com.intellij.codeInsight.template.* -import com.intellij.codeInsight.template.impl.TemplateImpl -import com.intellij.codeInsight.template.impl.Variable -import com.intellij.ide.fileTemplates.FileTemplate -import com.intellij.ide.fileTemplates.FileTemplateManager -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.command.CommandProcessor -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.progress.ProcessCanceledException -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.popup.PopupChooserBuilder -import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.refactoring.psi.SearchUtils -import com.intellij.ui.components.JBList -import com.intellij.util.ArrayUtil -import com.intellij.util.IncorrectOperationException -import org.jetbrains.jet.lang.descriptors.* -import org.jetbrains.jet.lang.psi.* -import org.jetbrains.jet.lang.resolve.BindingContext -import org.jetbrains.jet.lang.resolve.DescriptorUtils -import org.jetbrains.jet.lang.resolve.name.Name -import org.jetbrains.jet.lang.resolve.scopes.JetScope -import org.jetbrains.jet.lang.types.* -import org.jetbrains.jet.lang.types.checker.JetTypeChecker -import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns -import org.jetbrains.jet.plugin.JetBundle -import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil -import org.jetbrains.jet.plugin.codeInsight.ShortenReferences -import org.jetbrains.jet.plugin.refactoring.JetNameSuggester -import org.jetbrains.jet.plugin.refactoring.JetNameValidator -import org.jetbrains.jet.plugin.references.JetSimpleNameReference -import javax.swing.* -import kotlin.properties.Delegates -import java.util.LinkedHashSet -import java.util.Collections -import java.util.HashSet -import java.util.HashMap -import java.util.ArrayList -import java.util.Properties -import org.jetbrains.jet.plugin.caches.resolve.getAnalysisResults -import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils -import org.jetbrains.jet.plugin.quickfix.QuickFixUtil import org.jetbrains.jet.plugin.quickfix.createFromUsage.CreateFromUsageFixBase -import org.jetbrains.jet.plugin.refactoring.EmptyValidator -import org.jetbrains.jet.plugin.refactoring.CollectingValidator import com.intellij.ide.util.PsiElementListCellRenderer +import org.jetbrains.jet.lang.psi.JetClass import org.jetbrains.jet.plugin.presentation.JetClassPresenter +import javax.swing.JList import java.awt.Component +import org.jetbrains.jet.lang.psi.JetFile +import com.intellij.openapi.editor.Editor +import org.jetbrains.jet.plugin.JetBundle +import com.intellij.openapi.project.Project +import com.intellij.openapi.application.ApplicationManager +import com.intellij.ui.components.JBList +import javax.swing.ListSelectionModel +import com.intellij.openapi.ui.popup.PopupChooserBuilder +import com.intellij.openapi.command.CommandProcessor +import org.jetbrains.jet.lang.resolve.DescriptorUtils +import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil -private val TYPE_PARAMETER_LIST_VARIABLE_NAME = "typeParameterList" -private val TEMPLATE_FROM_USAGE_FUNCTION_BODY = "New Kotlin Function Body.kt" -private val ATTRIBUTE_FUNCTION_NAME = "FUNCTION_NAME" - -/** - * Represents a single choice for a type (e.g. parameter type or return type). - */ -private class TypeCandidate(public val theType: JetType, scope: JetScope? = null) { - public val typeParameters: Array - public var renderedType: String? = null - private set - public var typeParameterNames: Array? = null - private set - - public fun render(typeParameterNameMap: Map) { - renderedType = theType.renderShort(typeParameterNameMap); - typeParameterNames = typeParameters.map { typeParameterNameMap[it]!! }.copyToArray() +public class CreateFunctionFromUsageFix(element: PsiElement, val functionInfo: FunctionInfo) : CreateFromUsageFixBase(element) { + /** + * Represents an element in the class selection list. + */ + private class ClassCandidate(val typeCandidate: TypeCandidate, file: JetFile) { + val jetClass: JetClass = DescriptorToDeclarationUtil.getDeclaration( + file, DescriptorUtils.getClassDescriptorForType(typeCandidate.theType) + ) as JetClass } - { - val typeParametersInType = theType.getTypeParameters() - if (scope == null) { - typeParameters = typeParametersInType.copyToArray() - renderedType = theType.renderShort(Collections.emptyMap()); - } - else { - typeParameters = getTypeParameterNamesNotInScope(typeParametersInType, scope).copyToArray(); - } - } -} - -/** - * Represents an element in the class selection list. - */ -private class ClassCandidate(public val typeCandidate: TypeCandidate, file: JetFile) { - public val jetClass: JetClass = DescriptorToDeclarationUtil.getDeclaration( - file, DescriptorUtils.getClassDescriptorForType(typeCandidate.theType) - ) as JetClass -} - -/** - * Represents a concrete type or a set of types yet to be inferred from an expression. - */ -private fun TypeOrExpressionThereof(expressionOfType: JetExpression, variance: Variance): TypeOrExpressionThereof = - TypeOrExpressionThereof(variance, expressionOfType, null) -private fun TypeOrExpressionThereof(theType: JetType, variance: Variance): TypeOrExpressionThereof = - TypeOrExpressionThereof(variance, null, theType) - -private class TypeOrExpressionThereof(private val variance: Variance, - public val expressionOfType: JetExpression?, - public val theType: JetType?) { - public var typeCandidates: List? = null - private set - - public val possibleNamesFromExpression: Array by Delegates.lazy { - if (isType()) { - ArrayUtil.EMPTY_STRING_ARRAY - } - else { - JetNameSuggester.suggestNamesForExpression(expressionOfType!!, EmptyValidator) - } - } - - public fun isType(): Boolean = (theType != null) - - private fun getPossibleTypes(context: BindingContext): List { - val types = ArrayList() - if (theType != null) { - types.add(theType) - if (variance == Variance.IN_VARIANCE) { - types.addAll(TypeUtils.getAllSupertypes(theType)) - } - } - else { - for (theType in expressionOfType!!.guessTypes(context)) { - types.add(theType) - if (variance == Variance.IN_VARIANCE) { - types.addAll(TypeUtils.getAllSupertypes(theType)) - } - } - } - return types - } - - public fun computeTypeCandidates(context: BindingContext) { - typeCandidates = getPossibleTypes(context).map { TypeCandidate(it) } - } - - public fun computeTypeCandidates(context: BindingContext, substitutions: Array, scope: JetScope) { - val types = getPossibleTypes(context).reverse() - - val newTypes = LinkedHashSet(types) - for (substitution in substitutions) { - // each substitution can be applied or not, so we offer all options - val toAdd = newTypes.map { theType -> theType.substitute(substitution, variance) } - // substitution.byType are type arguments, but they cannot already occur in the type before substitution - val toRemove = newTypes.filter { theType -> substitution.byType in theType } - - newTypes.addAll(toAdd) - newTypes.removeAll(toRemove) - } - - if (newTypes.empty) { - newTypes.add(KotlinBuiltIns.getInstance().getAnyType()) - } - - typeCandidates = newTypes.map { TypeCandidate(it, scope) }.reverse() - } - - public fun renderTypeCandidates(typeParameterNameMap: Map) { - typeCandidates!!.forEach { it.render(typeParameterNameMap) } - } -} - -/** - * Encapsulates information about a function parameter that is going to be created. - */ -private class Parameter( - public val theType: TypeOrExpressionThereof, - public val preferredName: String? = null -) - -/** - * Special Expression for parameter names based on its type. - */ -private class ParameterNameExpression( - private val names: Array, - private val parameterTypeToNamesMap: Map>) : Expression() { - { - assert(names all { it.isNotEmpty() }) - } - - override fun calculateResult(context: ExpressionContext?): Result? { - val lookupItems = calculateLookupItems(context)!! - return TextResult(if (lookupItems.isEmpty()) "" else lookupItems.first().getLookupString()) - } - - override fun calculateQuickResult(context: ExpressionContext?) = calculateResult(context) - - override fun calculateLookupItems(context: ExpressionContext?): Array? { - context!! - val names = LinkedHashSet(this.names.toList()) - - // find the parameter list - val project = context.getProject()!! - val offset = context.getStartOffset() - PsiDocumentManager.getInstance(project).commitAllDocuments() - val editor = context.getEditor()!! - val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) as JetFile - val elementAt = file.findElementAt(offset) - val func = PsiTreeUtil.getParentOfType(elementAt, javaClass()) ?: return array() - val parameterList = func.getValueParameterList()!! - - // add names based on selected type - val parameter = PsiTreeUtil.getParentOfType(elementAt, javaClass()) - if (parameter != null) { - val parameterTypeRef = parameter.getTypeReference() - if (parameterTypeRef != null) { - val suggestedNamesBasedOnType = parameterTypeToNamesMap[parameterTypeRef.getText()] - if (suggestedNamesBasedOnType != null) { - names.addAll(suggestedNamesBasedOnType) - } - } - } - - // remember other parameter names for later use - val parameterNames = parameterList.getParameters().stream().map { jetParameter -> - if (jetParameter == parameter) null else jetParameter.getName() - }.filterNotNullTo(HashSet()) - - // add fallback parameter name - if (names.isEmpty()) { - names.add("arg") - } - - // ensure there are no conflicts - val validator = CollectingValidator(parameterNames) - return names.map { LookupElementBuilder.create(validator.validateName(it)) }.copyToArray() - } -} - -/** - * An Expression for type references. - */ -private class TypeExpression(public val theType: TypeOrExpressionThereof) : Expression() { - private val cachedLookupElements: Array = theType.typeCandidates!!.map { - LookupElementBuilder.create(it, it.renderedType!!) - }.copyToArray() - - override fun calculateResult(context: ExpressionContext?): Result { - val lookupItems = calculateLookupItems(context) - return TextResult(if (lookupItems.size == 0) "" else lookupItems[0].getLookupString()) - } - - override fun calculateQuickResult(context: ExpressionContext?) = calculateResult(context) - - override fun calculateLookupItems(context: ExpressionContext?) = cachedLookupElements - - public fun getTypeFromSelection(selection: String): JetType? { - return theType.typeCandidates!!.firstOrNull { it.renderedType == selection }?.theType - } -} - -/** - * A sort-of dummy Expression for parameter lists, to allow us to update the parameter list as the user makes selections. - */ -private class TypeParameterListExpression(private val typeParameterNamesFromReceiverType: Array, - private val parameterTypeToTypeParameterNamesMap: Map>) : Expression() { - - override fun calculateResult(context: ExpressionContext?): Result { - context!! - val project = context.getProject()!! - val offset = context.getStartOffset() - - PsiDocumentManager.getInstance(project).commitAllDocuments() - val editor = context.getEditor()!! - val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) as JetFile - val elementAt = file.findElementAt(offset) - val func = PsiTreeUtil.getParentOfType(elementAt, javaClass()) ?: return TextResult("") - val parameters = func.getValueParameters() - - val typeParameterNames = LinkedHashSet() - typeParameterNames.addAll(typeParameterNamesFromReceiverType) - for (parameter in parameters) { - val parameterTypeRef = parameter.getTypeReference() - if (parameterTypeRef != null) { - val typeParameterNamesFromParameter = parameterTypeToTypeParameterNamesMap[parameterTypeRef.getText()] - if (typeParameterNamesFromParameter != null) { - typeParameterNames.addAll(typeParameterNamesFromParameter) - } - } - } - val returnTypeRef = func.getReturnTypeRef() - if (returnTypeRef != null) { - val typeParameterNamesFromReturnType = parameterTypeToTypeParameterNamesMap[returnTypeRef.getText()] - if (typeParameterNamesFromReturnType != null) { - typeParameterNames.addAll(typeParameterNamesFromReturnType) - } - } - - return TextResult(if (typeParameterNames.empty) "" else typeParameterNames.makeString(", ", " <", ">")) - } - - override fun calculateQuickResult(context: ExpressionContext?): Result = calculateResult(context) - - // do not offer the user any choices - override fun calculateLookupItems(context: ExpressionContext?) = array() -} - -public class CreateFunctionFromUsageFix internal ( - element: PsiElement, - private val ownerType: TypeOrExpressionThereof, - private val functionName: String, - private val returnType: TypeOrExpressionThereof, - private val parameters: List = Collections.emptyList() -) : CreateFromUsageFixBase(element) { private class ClassCandidateListCellRenderer : PsiElementListCellRenderer() { private val presenter = JetClassPresenter() @@ -345,42 +46,23 @@ public class CreateFunctionFromUsageFix internal ( super.getListCellRendererComponent(list, (value as ClassCandidate).jetClass, index, isSelected, cellHasFocus) } - private var isUnit: Boolean = false - private var isExtension: Boolean = false - private var currentFile: JetFile by Delegates.notNull() - private var containingFile: JetFile by Delegates.notNull() - private var currentFileEditor: Editor by Delegates.notNull() - private var containingFileEditor: Editor by Delegates.notNull() - private var currentFileContext: BindingContext by Delegates.notNull() - private var currentFileModule: ModuleDescriptor by Delegates.notNull() - private var ownerClass: JetClass by Delegates.notNull() - private var ownerClassDescriptor: ClassDescriptor by Delegates.notNull() - private var selectedReceiverType: TypeCandidate by Delegates.notNull() - private var typeParameterNameMap: Map by Delegates.notNull() - override fun getText(): String { - return JetBundle.message("create.function.from.usage", functionName) + return JetBundle.message("create.function.from.usage", functionInfo.name) } override fun invoke(project: Project, editor: Editor?, file: JetFile?) { - currentFile = file!! - currentFileEditor = editor!! + val functionBuilder = FunctionBuilderConfiguration(functionInfo, file!!, editor!!).createBuilder() - val exhaust = currentFile.getAnalysisResults() - currentFileContext = exhaust.getBindingContext() - currentFileModule = exhaust.getModuleDescriptor() - - ownerType.computeTypeCandidates(currentFileContext) - val ownerTypeCandidates = ownerType.typeCandidates!! + val ownerTypeCandidates = functionBuilder.computeTypeCandidates(functionInfo.receiverTypeInfo) assert(!ownerTypeCandidates.empty) if (ownerTypeCandidates.size == 1 || ApplicationManager.getApplication()!!.isUnitTestMode()) { - selectedReceiverType = ownerTypeCandidates.first!! - addFunctionToSelectedOwner() + functionBuilder.receiverTypeCandidate = ownerTypeCandidates.first!! + functionBuilder.build() } else { // class selection - val list = JBList(ownerTypeCandidates.map { ClassCandidate(it, currentFile) }) + val list = JBList(ownerTypeCandidates.map { ClassCandidate(it, file) }) val renderer = ClassCandidateListCellRenderer() list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) list.setCellRenderer(renderer) @@ -391,315 +73,12 @@ public class CreateFunctionFromUsageFix internal ( .setItemChoosenCallback { val selectedCandidate = list.getSelectedValue() as ClassCandidate? if (selectedCandidate != null) { - selectedReceiverType = selectedCandidate.typeCandidate - CommandProcessor.getInstance().executeCommand(project, { addFunctionToSelectedOwner() }, getText(), null) + functionBuilder.receiverTypeCandidate = selectedCandidate.typeCandidate + CommandProcessor.getInstance().executeCommand(project, { functionBuilder.build() }, getText(), null) } } .createPopup() - .showInBestPositionFor(currentFileEditor) + .showInBestPositionFor(editor) } } - - private fun addFunctionToSelectedOwner() { - // gather relevant information - ownerClassDescriptor = DescriptorUtils.getClassDescriptorForType(selectedReceiverType.theType) - val receiverType = ownerClassDescriptor.getDefaultType() - val classDeclaration = DescriptorToSourceUtils.classDescriptorToDeclaration(ownerClassDescriptor) - if (classDeclaration is JetClass) { - ownerClass = classDeclaration - isExtension = !ownerClass.isWritable() - } - else { - isExtension = true - } - isUnit = returnType.isType() && KotlinBuiltIns.getInstance().isUnit(returnType.theType!!) - - val scope = if (isExtension) { - currentFileModule.getPackage(currentFile.getPackageFqName())!!.getMemberScope() - } - else { - (ownerClassDescriptor as ClassDescriptorWithResolutionScopes).getScopeForMemberDeclarationResolution() - } - - // figure out type substitutions for type parameters - val classTypeParameters = receiverType.getArguments() - val ownerTypeArguments = selectedReceiverType.theType.getArguments() - assert(ownerTypeArguments.size == classTypeParameters.size) - val substitutions = ownerTypeArguments.zip(classTypeParameters).map { - JetTypeSubstitution(it.first.getType(), it.second.getType()) - }.copyToArray() - parameters.forEach { parameter -> - parameter.theType.computeTypeCandidates(currentFileContext, substitutions, scope) - } - if (!isUnit) { - returnType.computeTypeCandidates(currentFileContext, substitutions, scope) - } - - // now that we have done substitutions, we can throw it away - selectedReceiverType = TypeCandidate(receiverType, scope) - - // figure out type parameter renames to avoid conflicts - typeParameterNameMap = getTypeParameterRenames(scope) - parameters.forEach { parameter -> - parameter.theType.renderTypeCandidates(typeParameterNameMap) - } - if (!isUnit) { - returnType.renderTypeCandidates(typeParameterNameMap) - } - selectedReceiverType.render(typeParameterNameMap) - - ApplicationManager.getApplication()!!.runWriteAction { - val func = createFunctionSkeleton() - buildAndRunTemplate(func) - } - } - - private fun createFunctionSkeleton(): JetNamedFunction { - val parametersString = parameters.indices.map { i -> "p$i: Any" }.makeString(", ") - val returnTypeString = if (isUnit) "" else ": Any" - val psiFactory = JetPsiFactory(currentFile) - if (isExtension) { - // create as extension function - val ownerTypeString = selectedReceiverType.renderedType!! - val func = psiFactory.createFunction("fun $ownerTypeString.$functionName($parametersString)$returnTypeString { }") - containingFile = currentFile - containingFileEditor = currentFileEditor - return currentFile.add(func) as JetNamedFunction - } - else { - // create as regular function - val func = psiFactory.createFunction("fun $functionName($parametersString)$returnTypeString { }") - containingFile = ownerClass.getContainingJetFile() - - NavigationUtil.activateFileWithPsiElement(containingFile) - containingFileEditor = FileEditorManager.getInstance(currentFile.getProject())!!.getSelectedTextEditor()!! - - var classBody = ownerClass.getBody() - if (classBody == null) { - classBody = ownerClass.add(psiFactory.createEmptyClassBody()) as JetClassBody - ownerClass.addBefore(psiFactory.createWhiteSpace(), classBody) - } - val rBrace = classBody!!.getRBrace() - //TODO: Assert rbrace not null? It can be if the class isn't closed. - return classBody!!.addBefore(func, rBrace) as JetNamedFunction - } - } - - private fun buildAndRunTemplate(func: JetNamedFunction) { - val project = func.getProject() - val parameterList = func.getValueParameterList()!! - - // build templates - PsiDocumentManager.getInstance(project).commitAllDocuments() - PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(containingFileEditor.getDocument()) - - val caretModel = containingFileEditor.getCaretModel() - caretModel.moveToOffset(containingFile.getNode()!!.getStartOffset()) - - val builder = TemplateBuilderImpl(containingFile) - val returnTypeExpression = if (isUnit) null else setupReturnTypeTemplate(builder, func) - val parameterTypeExpressions = setupParameterTypeTemplates(builder, parameterList) - - // add a segment for the parameter list - // Note: because TemplateBuilderImpl does not have a replaceElement overload that takes in both a TextRange and alwaysStopAt, we - // need to create the segment first and then hack the Expression into the template later. We use this template to update the type - // parameter list as the user makes selections in the parameter types, and we need alwaysStopAt to be false so the user can't tab to - // it. - val expression = setupTypeParameterListTemplate(builder, func) - - // the template built by TemplateBuilderImpl is ordered by element position, but we want types to be first, so hack it - val templateImpl = builder.buildInlineTemplate() as TemplateImpl - val variables = templateImpl.getVariables()!! - for (i in 0..(parameters.size - 1)) { - Collections.swap(variables, i * 2, i * 2 + 1) - } - - // fix up the template to include the expression for the type parameter list - variables.add(Variable(TYPE_PARAMETER_LIST_VARIABLE_NAME, expression, expression, false, true)) - - // TODO: Disabled shortening names because it causes some tests fail. Refactor code to use automatic reference shortening - templateImpl.setToShortenLongNames(false) - - // run the template - TemplateManager.getInstance(project).startTemplate(containingFileEditor, templateImpl, object : TemplateEditingAdapter() { - override fun templateFinished(template: Template?, brokenOff: Boolean) { - // file templates - val offset = templateImpl.getSegmentOffset(0) - val newFunc = PsiTreeUtil.findElementOfClassAtOffset(containingFile, offset, javaClass(), false)!! - val typeRefsToShorten = ArrayList() - - ApplicationManager.getApplication()!!.runWriteAction { - // file templates - setupFunctionBody(newFunc) - - // change short type names to fully qualified ones (to be shortened below) - setupTypeReferencesForShortening(newFunc, typeRefsToShorten, parameterTypeExpressions, returnTypeExpression) - ShortenReferences.process(typeRefsToShorten) - } - } - }) - } - - private fun getTypeParameterRenames(scope: JetScope): Map { - val allTypeParametersNotInScope = LinkedHashSet() - - allTypeParametersNotInScope.addAll(selectedReceiverType.typeParameters.toList()) - - parameters.stream() - .flatMap { it.theType.typeCandidates!!.stream() } - .flatMap { it.typeParameters.stream() } - .toCollection(allTypeParametersNotInScope) - - if (!isUnit) { - returnType.typeCandidates!!.stream().flatMapTo(allTypeParametersNotInScope) { it.typeParameters.stream() } - } - - val validator = CollectingValidator { scope.getClassifier(Name.identifier(it)) == null } - val typeParameterNames = allTypeParametersNotInScope.map { validator.validateName(it.getName().asString()) } - - val typeParameterNameMap = HashMap() - for ((key, value) in allTypeParametersNotInScope.zip(typeParameterNames)) { - typeParameterNameMap[key] = value - } - - return typeParameterNameMap - } - - private fun setupTypeReferencesForShortening( - func: JetNamedFunction, - typeRefsToShorten: MutableList, parameterTypeExpressions: List, - returnTypeExpression: TypeExpression?) { - if (isExtension) { - val receiverTypeRef = JetPsiFactory(func).createType(selectedReceiverType.theType.renderLong(typeParameterNameMap)) - replaceWithLongerName(receiverTypeRef, selectedReceiverType.theType) - - val funcReceiverTypeRef = func.getReceiverTypeRef() - if (funcReceiverTypeRef != null) { - typeRefsToShorten.add(funcReceiverTypeRef) - } - } - - if (!isUnit) { - returnTypeExpression!! - val returnTypeRef = func.getReturnTypeRef() - if (returnTypeRef != null) { - val returnType = returnTypeExpression.getTypeFromSelection(returnTypeRef.getText() ?: throw AssertionError("Expression for function return type shouldn't be empty: function = ${func.getText()}")) - if (returnType != null) { - // user selected a given type - replaceWithLongerName(returnTypeRef, returnType) - typeRefsToShorten.add(func.getReturnTypeRef()!!) - } - } - } - - val valueParameters = func.getValueParameters() - val parameterIndicesToShorten = ArrayList() - assert(valueParameters.size == parameterTypeExpressions.size) - for ((i, parameter) in valueParameters.stream().withIndices()) { - val parameterTypeRef = parameter.getTypeReference() - if (parameterTypeRef != null) { - val parameterType = parameterTypeExpressions[i].getTypeFromSelection(parameterTypeRef.getText() ?: throw AssertionError("Expression for function parameter type shouldn't be empty: function = ${func.getText()}")) - if (parameterType != null) { - replaceWithLongerName(parameterTypeRef, parameterType) - parameterIndicesToShorten.add(i) - } - } - } - - val expandedValueParameters = func.getValueParameters() - parameterIndicesToShorten.stream().map { expandedValueParameters[it].getTypeReference() }.filterNotNullTo(typeRefsToShorten) - } - - - private fun setupFunctionBody(func: JetNamedFunction) { - val fileTemplate = FileTemplateManager.getInstance()!!.getCodeTemplate(TEMPLATE_FROM_USAGE_FUNCTION_BODY) - val properties = Properties() - properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, if (isUnit) "Unit" else func.getReturnTypeRef()!!.getText()) - properties.setProperty(FileTemplate.ATTRIBUTE_CLASS_NAME, DescriptorUtils.getFqName(ownerClassDescriptor).asString()) - properties.setProperty(FileTemplate.ATTRIBUTE_SIMPLE_CLASS_NAME, ownerClassDescriptor.getName().asString()) - properties.setProperty(ATTRIBUTE_FUNCTION_NAME, functionName) - - val bodyText = try { - fileTemplate!!.getText(properties) - } - catch (e: ProcessCanceledException) { - throw e - } - catch (e: Exception) { - // TODO: This is dangerous. - // Is there any way to avoid catching all exceptions? - throw IncorrectOperationException("Failed to parse file template", e) - } - - val newBodyExpression = JetPsiFactory(func).createFunctionBody(bodyText) - func.getBodyExpression()!!.replace(newBodyExpression) - } - - private fun setupReturnTypeTemplate(builder: TemplateBuilder, func: JetNamedFunction): TypeExpression { - val returnTypeRef = func.getReturnTypeRef()!! - val returnTypeExpression = TypeExpression(returnType) - builder.replaceElement(returnTypeRef, returnTypeExpression) - return returnTypeExpression - } - - private fun setupTypeParameterListTemplate(builder: TemplateBuilderImpl, func: JetNamedFunction): TypeParameterListExpression { - val typeParameterMap = HashMap>() - val receiverTypeParameterNames = selectedReceiverType.typeParameterNames - - parameters.stream().flatMap { it.theType.typeCandidates!!.stream() }.forEach { - typeParameterMap[it.renderedType!!] = it.typeParameterNames!! - } - - if (func.getReturnTypeRef() != null) { - returnType.typeCandidates!!.forEach { - typeParameterMap[it.renderedType!!] = it.typeParameterNames!! - } - } - // ((3, 3) is after "fun") - builder.replaceElement(func, TextRange.create(3, 3), TYPE_PARAMETER_LIST_VARIABLE_NAME, null, false) - return TypeParameterListExpression(receiverTypeParameterNames!!, typeParameterMap) - } - - private fun setupParameterTypeTemplates(builder: TemplateBuilder, parameterList: JetParameterList): List { - val jetParameters = parameterList.getParameters() - assert(jetParameters.size == parameters.size) - - val typeParameters = ArrayList() - for ((parameter, jetParameter) in parameters.zip(jetParameters)) { - val parameterTypeExpression = TypeExpression(parameter.theType) - val parameterTypeRef = jetParameter.getTypeReference()!! - builder.replaceElement(parameterTypeRef, parameterTypeExpression) - - // add parameter name to the template - val possibleNamesFromExpression = parameter.theType.possibleNamesFromExpression - val preferredName = parameter.preferredName - val possibleNames = if (preferredName != null) { - array(preferredName, *possibleNamesFromExpression) - } - else { - possibleNamesFromExpression - } - - // figure out suggested names for each type option - val parameterTypeToNamesMap = HashMap>() - parameter.theType.typeCandidates!!.forEach { typeCandidate -> - val suggestedNames = JetNameSuggester.suggestNamesForType(typeCandidate.theType, EmptyValidator) - parameterTypeToNamesMap[typeCandidate.renderedType!!] = suggestedNames - } - - // add expression to builder - val parameterNameExpression = ParameterNameExpression(possibleNames, parameterTypeToNamesMap) - val parameterNameIdentifier = jetParameter.getNameIdentifier()!! - builder.replaceElement(parameterNameIdentifier, parameterNameExpression) - - typeParameters.add(parameterTypeExpression) - } - return typeParameters - } - - private fun replaceWithLongerName(typeRef: JetTypeReference, theType: JetType) { - val fullyQualifiedReceiverTypeRef = JetPsiFactory(typeRef).createType(theType.renderLong(typeParameterNameMap)) - typeRef.replace(fullyQualifiedReceiverTypeRef) - } } - diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateGetFunctionActionFactory.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateGetFunctionActionFactory.kt index b93dc7f37b9..eb9d6444510 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateGetFunctionActionFactory.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateGetFunctionActionFactory.kt @@ -11,13 +11,13 @@ object CreateGetFunctionActionFactory : JetSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val accessExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass()) ?: return null val arrayExpr = accessExpr.getArrayExpression() ?: return null - val arrayType = TypeOrExpressionThereof(arrayExpr, Variance.IN_VARIANCE) + val arrayType = TypeInfo(arrayExpr, Variance.IN_VARIANCE) val parameters = accessExpr.getIndexExpressions().map { - Parameter(TypeOrExpressionThereof(it, Variance.IN_VARIANCE)) + ParameterInfo(TypeInfo(it, Variance.IN_VARIANCE)) } - val returnType = TypeOrExpressionThereof(accessExpr, Variance.OUT_VARIANCE) - return CreateFunctionFromUsageFix(accessExpr, arrayType, "get", returnType, parameters) + val returnType = TypeInfo(accessExpr, Variance.OUT_VARIANCE) + return CreateFunctionFromUsageFix(accessExpr, FunctionInfo("get", arrayType, returnType, parameters)) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateHasNextFunctionActionFactory.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateHasNextFunctionActionFactory.kt index 412284597bd..85084d687e7 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateHasNextFunctionActionFactory.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateHasNextFunctionActionFactory.kt @@ -13,10 +13,10 @@ import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns object CreateHasNextFunctionActionFactory : JetSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val diagnosticWithParameters = DiagnosticFactory.cast(diagnostic, Errors.HAS_NEXT_MISSING, Errors.HAS_NEXT_FUNCTION_NONE_APPLICABLE) - val ownerType = TypeOrExpressionThereof(diagnosticWithParameters.getA(), Variance.IN_VARIANCE) + val ownerType = TypeInfo(diagnosticWithParameters.getA(), Variance.IN_VARIANCE) val forExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass()) ?: return null - val returnType = TypeOrExpressionThereof(KotlinBuiltIns.getInstance().getBooleanType(), Variance.OUT_VARIANCE) - return CreateFunctionFromUsageFix(forExpr, ownerType, "hasNext", returnType) + val returnType = TypeInfo(KotlinBuiltIns.getInstance().getBooleanType(), Variance.OUT_VARIANCE) + return CreateFunctionFromUsageFix(forExpr, FunctionInfo("hasNext", ownerType, returnType)) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateIteratorFunctionActionFactory.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateIteratorFunctionActionFactory.kt index fc3ec6b07ed..8a237d98751 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateIteratorFunctionActionFactory.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateIteratorFunctionActionFactory.kt @@ -20,7 +20,7 @@ object CreateIteratorFunctionActionFactory : JetSingleIntentionActionFactory() { val forExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass()) ?: return null val iterableExpr = forExpr.getLoopRange() ?: return null val variableExpr: JetExpression = ((forExpr.getLoopParameter() ?: forExpr.getMultiParameter()) ?: return null) as JetExpression - val iterableType = TypeOrExpressionThereof(iterableExpr, Variance.IN_VARIANCE) + val iterableType = TypeInfo(iterableExpr, Variance.IN_VARIANCE) val returnJetType = KotlinBuiltIns.getInstance().getIterator().getDefaultType() val context = file.getBindingContext() @@ -30,7 +30,7 @@ object CreateIteratorFunctionActionFactory : JetSingleIntentionActionFactory() { val returnJetTypeParameterType = TypeProjectionImpl(returnJetTypeParameterTypes[0]) val returnJetTypeArguments = Collections.singletonList(returnJetTypeParameterType) val newReturnJetType = JetTypeImpl(returnJetType.getAnnotations(), returnJetType.getConstructor(), returnJetType.isNullable(), returnJetTypeArguments, returnJetType.getMemberScope()) - val returnType = TypeOrExpressionThereof(newReturnJetType, Variance.OUT_VARIANCE) - return CreateFunctionFromUsageFix(forExpr, iterableType, "iterator", returnType) + val returnType = TypeInfo(newReturnJetType, Variance.OUT_VARIANCE) + return CreateFunctionFromUsageFix(forExpr, FunctionInfo("iterator", iterableType, returnType)) } } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateNextFunctionActionFactory.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateNextFunctionActionFactory.kt index c73dd197bd2..7bb225094d6 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateNextFunctionActionFactory.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateNextFunctionActionFactory.kt @@ -13,11 +13,11 @@ import org.jetbrains.jet.lang.psi.JetForExpression object CreateNextFunctionActionFactory : JetSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val diagnosticWithParameters = DiagnosticFactory.cast(diagnostic, Errors.NEXT_MISSING, Errors.NEXT_NONE_APPLICABLE) - val ownerType = TypeOrExpressionThereof(diagnosticWithParameters.getA(), Variance.IN_VARIANCE) + val ownerType = TypeInfo(diagnosticWithParameters.getA(), Variance.IN_VARIANCE) val forExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass()) ?: return null val variableExpr: JetExpression = ((forExpr.getLoopParameter() ?: forExpr.getMultiParameter()) ?: return null) as JetExpression - val returnType = TypeOrExpressionThereof(variableExpr, Variance.OUT_VARIANCE) - return CreateFunctionFromUsageFix(forExpr, ownerType, "next", returnType) + val returnType = TypeInfo(variableExpr, Variance.OUT_VARIANCE) + return CreateFunctionFromUsageFix(forExpr, FunctionInfo("next", ownerType, returnType)) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateSetFunctionActionFactory.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateSetFunctionActionFactory.kt index a23d2c23ae5..d459b01afbc 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateSetFunctionActionFactory.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateSetFunctionActionFactory.kt @@ -14,18 +14,18 @@ object CreateSetFunctionActionFactory : JetSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val accessExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass()) ?: return null val arrayExpr = accessExpr.getArrayExpression() ?: return null - val arrayType = TypeOrExpressionThereof(arrayExpr, Variance.IN_VARIANCE) + val arrayType = TypeInfo(arrayExpr, Variance.IN_VARIANCE) - val parameters = accessExpr.getIndexExpressions().mapTo(ArrayList()) { - Parameter(TypeOrExpressionThereof(it, Variance.IN_VARIANCE)) + val parameters = accessExpr.getIndexExpressions().mapTo(ArrayList()) { + ParameterInfo(TypeInfo(it, Variance.IN_VARIANCE)) } val assignmentExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass()) ?: return null val rhs = assignmentExpr.getRight() ?: return null - val valType = TypeOrExpressionThereof(rhs, Variance.IN_VARIANCE) - parameters.add(Parameter(valType, "value")) + val valType = TypeInfo(rhs, Variance.IN_VARIANCE) + parameters.add(ParameterInfo(valType, "value")) - val returnType = TypeOrExpressionThereof(KotlinBuiltIns.getInstance().getUnitType(), Variance.OUT_VARIANCE) - return CreateFunctionFromUsageFix(accessExpr, arrayType, "set", returnType, parameters) + val returnType = TypeInfo(KotlinBuiltIns.getInstance().getUnitType(), Variance.OUT_VARIANCE) + return CreateFunctionFromUsageFix(accessExpr, FunctionInfo("set", arrayType, returnType, parameters)) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/FunctionInfo.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/FunctionInfo.kt new file mode 100644 index 00000000000..3f68ab46c9d --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/FunctionInfo.kt @@ -0,0 +1,63 @@ +package org.jetbrains.jet.plugin.quickfix.createFromUsage.createFunction + +import java.util.Collections +import org.jetbrains.jet.lang.psi.JetExpression +import org.jetbrains.jet.lang.types.Variance +import org.jetbrains.jet.lang.types.JetType +import kotlin.properties.Delegates +import com.intellij.util.ArrayUtil +import org.jetbrains.jet.plugin.refactoring.JetNameSuggester +import org.jetbrains.jet.plugin.refactoring.EmptyValidator +import org.jetbrains.jet.lang.resolve.BindingContext +import org.jetbrains.jet.plugin.util.supertypes + +/** + * Represents a concrete type or a set of types yet to be inferred from an expression. + */ +abstract class TypeInfo(val variance: Variance) { + class ByExpression(val expression: JetExpression, variance: Variance): TypeInfo(variance) { + override val possibleNamesFromExpression: Array by Delegates.lazy { + JetNameSuggester.suggestNamesForExpression(expression, EmptyValidator) + } + + override fun getPossibleTypes(context: BindingContext): List = + expression.guessTypes(context).flatMap { it.getPossibleSupertypes(variance) } + } + + class ByType(val theType: JetType, variance: Variance): TypeInfo(variance) { + override val possibleNamesFromExpression: Array = + ArrayUtil.EMPTY_STRING_ARRAY + + override fun getPossibleTypes(context: BindingContext): List = + theType.getPossibleSupertypes(variance) + } + + abstract val possibleNamesFromExpression: Array + abstract fun getPossibleTypes(context: BindingContext): List + + protected fun JetType.getPossibleSupertypes(variance: Variance): List { + val single = Collections.singletonList(this) + return when (variance) { + Variance.IN_VARIANCE -> single + supertypes() + else -> single + } + } +} + +fun TypeInfo(expressionOfType: JetExpression, variance: Variance): TypeInfo = TypeInfo.ByExpression(expressionOfType, variance) +fun TypeInfo(theType: JetType, variance: Variance): TypeInfo = TypeInfo.ByType(theType, variance) + +/** + * Encapsulates information about a function parameter that is going to be created. + */ +class ParameterInfo( + val typeInfo: TypeInfo, + val preferredName: String? = null +) + +class FunctionInfo ( + val name: String, + val receiverTypeInfo: TypeInfo, + val returnTypeInfo: TypeInfo, + val parameterInfos: List = Collections.emptyList() +) \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/functionBuilder.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/functionBuilder.kt new file mode 100644 index 00000000000..f09fad6fb60 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/functionBuilder.kt @@ -0,0 +1,466 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.quickfix.createFromUsage.createFunction + +import com.intellij.codeInsight.navigation.NavigationUtil +import com.intellij.codeInsight.template.* +import com.intellij.codeInsight.template.impl.TemplateImpl +import com.intellij.codeInsight.template.impl.Variable +import com.intellij.ide.fileTemplates.FileTemplate +import com.intellij.ide.fileTemplates.FileTemplateManager +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.IncorrectOperationException +import org.jetbrains.jet.lang.descriptors.* +import org.jetbrains.jet.lang.psi.* +import org.jetbrains.jet.lang.resolve.BindingContext +import org.jetbrains.jet.lang.resolve.DescriptorUtils +import org.jetbrains.jet.lang.resolve.name.Name +import org.jetbrains.jet.lang.resolve.scopes.JetScope +import org.jetbrains.jet.lang.types.* +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns +import org.jetbrains.jet.plugin.codeInsight.ShortenReferences +import org.jetbrains.jet.plugin.refactoring.JetNameSuggester +import kotlin.properties.Delegates +import java.util.LinkedHashSet +import java.util.Collections +import java.util.HashMap +import java.util.ArrayList +import java.util.Properties +import org.jetbrains.jet.plugin.caches.resolve.getAnalysisResults +import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils +import org.jetbrains.jet.plugin.refactoring.EmptyValidator +import org.jetbrains.jet.plugin.refactoring.CollectingValidator +import org.jetbrains.jet.plugin.util.isUnit +import org.jetbrains.jet.plugin.refactoring.runWriteAction + +private val TYPE_PARAMETER_LIST_VARIABLE_NAME = "typeParameterList" +private val TEMPLATE_FROM_USAGE_FUNCTION_BODY = "New Kotlin Function Body.kt" +private val ATTRIBUTE_FUNCTION_NAME = "FUNCTION_NAME" + +/** + * Represents a single choice for a type (e.g. parameter type or return type). + */ +class TypeCandidate(val theType: JetType, scope: JetScope? = null) { + public val typeParameters: Array + var renderedType: String? = null + private set + var typeParameterNames: Array? = null + private set + + fun render(typeParameterNameMap: Map) { + renderedType = theType.renderShort(typeParameterNameMap); + typeParameterNames = typeParameters.map { typeParameterNameMap[it]!! }.copyToArray() + } + + { + val typeParametersInType = theType.getTypeParameters() + if (scope == null) { + typeParameters = typeParametersInType.copyToArray() + renderedType = theType.renderShort(Collections.emptyMap()); + } + else { + typeParameters = getTypeParameterNamesNotInScope(typeParametersInType, scope).copyToArray(); + } + } +} + +class FunctionBuilderConfiguration( + val functionInfo: FunctionInfo, + val currentFile: JetFile, + val currentEditor: Editor +) + +class FunctionBuilder(val config: FunctionBuilderConfiguration) { + private var finished: Boolean = false + + val currentFileContext: BindingContext + val currentFileModule: ModuleDescriptor + + public var receiverTypeCandidate: TypeCandidate by Delegates.notNull() + + private val typeCandidates = HashMap>(); + + { + val exhaust = config.currentFile.getAnalysisResults() + currentFileContext = exhaust.getBindingContext() + currentFileModule = exhaust.getModuleDescriptor() + } + + fun computeTypeCandidates(typeInfo: TypeInfo): List = + typeCandidates.getOrPut(typeInfo) { typeInfo.getPossibleTypes(currentFileContext).map { TypeCandidate(it) } } + + fun computeTypeCandidates( + typeInfo: TypeInfo, + substitutions: Array, + scope: JetScope): List { + return typeCandidates.getOrPut(typeInfo) { + val types = typeInfo.getPossibleTypes(currentFileContext).reverse() + + val newTypes = LinkedHashSet(types) + for (substitution in substitutions) { + // each substitution can be applied or not, so we offer all options + val toAdd = newTypes.map { theType -> theType.substitute(substitution, typeInfo.variance) } + // substitution.byType are type arguments, but they cannot already occur in the type before substitution + val toRemove = newTypes.filter { theType -> substitution.byType in theType } + + newTypes.addAll(toAdd) + newTypes.removeAll(toRemove) + } + + if (newTypes.empty) { + newTypes.add(KotlinBuiltIns.getInstance().getAnyType()) + } + + newTypes.map { TypeCandidate(it, scope) }.reverse() + } + } + + private inner class Context { + val isUnit: Boolean + val isExtension: Boolean + val containingFile: JetFile + val containingFileEditor: Editor + val receiverClass: JetClass? + val receiverClassDescriptor: ClassDescriptor + val typeParameterNameMap: Map + + { + // gather relevant information + + receiverClassDescriptor = DescriptorUtils.getClassDescriptorForType(receiverTypeCandidate.theType) + val receiverType = receiverClassDescriptor.getDefaultType() + val classDeclaration = DescriptorToSourceUtils.classDescriptorToDeclaration(receiverClassDescriptor) + if (classDeclaration is JetClass) { + receiverClass = classDeclaration + isExtension = !classDeclaration.isWritable() + } + else { + receiverClass = null + isExtension = true + + } + + if (isExtension) { + containingFile = config.currentFile + containingFileEditor = config.currentEditor + } + else { + containingFile = receiverClass!!.getContainingJetFile() + NavigationUtil.activateFileWithPsiElement(containingFile) + containingFileEditor = FileEditorManager.getInstance(config.currentFile.getProject())!!.getSelectedTextEditor()!! + } + + isUnit = config.functionInfo.returnTypeInfo.let { it is TypeInfo.ByType && it.theType.isUnit() } + + val scope = if (isExtension) { + currentFileModule.getPackage(config.currentFile.getPackageFqName())!!.getMemberScope() + } + else { + (receiverClassDescriptor as ClassDescriptorWithResolutionScopes).getScopeForMemberDeclarationResolution() + } + + // figure out type substitutions for type parameters + val classTypeParameters = receiverType.getArguments() + val ownerTypeArguments = receiverTypeCandidate.theType.getArguments() + assert(ownerTypeArguments.size == classTypeParameters.size) + val substitutions = ownerTypeArguments.zip(classTypeParameters).map { + JetTypeSubstitution(it.first.getType(), it.second.getType()) + }.copyToArray() + config.functionInfo.parameterInfos.forEach { parameter -> computeTypeCandidates(parameter.typeInfo, substitutions, scope) } + if (!isUnit) { + computeTypeCandidates(config.functionInfo.returnTypeInfo, substitutions, scope) + } + + // now that we have done substitutions, we can throw it away + receiverTypeCandidate = TypeCandidate(receiverType, scope) + + // figure out type parameter renames to avoid conflicts + typeParameterNameMap = getTypeParameterRenames(scope) + config.functionInfo.parameterInfos.forEach { renderTypeCandidates(it.typeInfo, typeParameterNameMap) } + if (!isUnit) { + renderTypeCandidates(config.functionInfo.returnTypeInfo, typeParameterNameMap) + } + receiverTypeCandidate.render(typeParameterNameMap) + } + + private fun renderTypeCandidates( + typeInfo: TypeInfo, + typeParameterNameMap: Map + ) { + typeCandidates[typeInfo]?.forEach { it.render(typeParameterNameMap) } + } + + private fun createFunctionSkeleton(): JetNamedFunction { + with (config) { + val parametersString = functionInfo.parameterInfos.indices.map { i -> "p$i: Any" }.joinToString(", ") + val returnTypeString = if (isUnit) "" else ": Any" + val psiFactory = JetPsiFactory(currentFile) + if (isExtension) { + // create as extension function + val ownerTypeString = receiverTypeCandidate.renderedType!! + val func = psiFactory.createFunction( + "fun $ownerTypeString.${functionInfo.name}($parametersString)$returnTypeString { }" + ) + return currentFile.add(func) as JetNamedFunction + } + else { + receiverClass!! + + // create as regular function + val func = psiFactory.createFunction("fun ${functionInfo.name}($parametersString)$returnTypeString { }") + var classBody = receiverClass.getBody() + if (classBody == null) { + classBody = receiverClass.add(psiFactory.createEmptyClassBody()) as JetClassBody + receiverClass.addBefore(psiFactory.createWhiteSpace(), classBody) + } + val rBrace = classBody!!.getRBrace() + //TODO: Assert rbrace not null? It can be if the class isn't closed. + return classBody!!.addBefore(func, rBrace) as JetNamedFunction + } + } + } + + private fun getTypeParameterRenames(scope: JetScope): Map { + val allTypeParametersNotInScope = LinkedHashSet() + + allTypeParametersNotInScope.addAll(receiverTypeCandidate.typeParameters.toList()) + + config.functionInfo.parameterInfos.stream() + .flatMap { typeCandidates[it.typeInfo]!!.stream() } + .flatMap { it.typeParameters.stream() } + .toCollection(allTypeParametersNotInScope) + + if (!isUnit) { + computeTypeCandidates(config.functionInfo.returnTypeInfo).stream().flatMapTo(allTypeParametersNotInScope) { it.typeParameters.stream() } + } + + val validator = CollectingValidator { scope.getClassifier(Name.identifier(it)) == null } + val typeParameterNames = allTypeParametersNotInScope.map { validator.validateName(it.getName().asString()) } + + return allTypeParametersNotInScope.zip(typeParameterNames).toMap() + } + + private fun setupTypeReferencesForShortening( + func: JetNamedFunction, + typeRefsToShorten: MutableList, parameterTypeExpressions: List, + returnTypeExpression: TypeExpression?) { + if (isExtension) { + val receiverTypeRef = JetPsiFactory(func).createType(receiverTypeCandidate.theType.renderLong(typeParameterNameMap)) + replaceWithLongerName(receiverTypeRef, receiverTypeCandidate.theType) + + val funcReceiverTypeRef = func.getReceiverTypeRef() + if (funcReceiverTypeRef != null) { + typeRefsToShorten.add(funcReceiverTypeRef) + } + } + + if (!isUnit) { + returnTypeExpression!! + val returnTypeRef = func.getReturnTypeRef() + if (returnTypeRef != null) { + val returnType = returnTypeExpression.getTypeFromSelection(returnTypeRef.getText() ?: throw AssertionError("Expression for function return type shouldn't be empty: function = ${func.getText()}")) + if (returnType != null) { + // user selected a given type + replaceWithLongerName(returnTypeRef, returnType) + typeRefsToShorten.add(func.getReturnTypeRef()!!) + } + } + } + + val valueParameters = func.getValueParameters() + val parameterIndicesToShorten = ArrayList() + assert(valueParameters.size == parameterTypeExpressions.size) + for ((i, parameter) in valueParameters.stream().withIndices()) { + val parameterTypeRef = parameter.getTypeReference() + if (parameterTypeRef != null) { + val parameterType = parameterTypeExpressions[i].getTypeFromSelection(parameterTypeRef.getText() ?: throw AssertionError("Expression for function parameter type shouldn't be empty: function = ${func.getText()}")) + if (parameterType != null) { + replaceWithLongerName(parameterTypeRef, parameterType) + parameterIndicesToShorten.add(i) + } + } + } + + val expandedValueParameters = func.getValueParameters() + parameterIndicesToShorten.stream().map { expandedValueParameters[it].getTypeReference() }.filterNotNullTo(typeRefsToShorten) + } + + private fun setupFunctionBody(func: JetNamedFunction) { + val fileTemplate = FileTemplateManager.getInstance()!!.getCodeTemplate(TEMPLATE_FROM_USAGE_FUNCTION_BODY) + val properties = Properties() + properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, if (isUnit) "Unit" else func.getReturnTypeRef()!!.getText()) + properties.setProperty(FileTemplate.ATTRIBUTE_CLASS_NAME, DescriptorUtils.getFqName(receiverClassDescriptor).asString()) + properties.setProperty(FileTemplate.ATTRIBUTE_SIMPLE_CLASS_NAME, receiverClassDescriptor.getName().asString()) + properties.setProperty(ATTRIBUTE_FUNCTION_NAME, config.functionInfo.name) + + val bodyText = try { + fileTemplate!!.getText(properties) + } + catch (e: ProcessCanceledException) { + throw e + } + catch (e: Exception) { + // TODO: This is dangerous. + // Is there any way to avoid catching all exceptions? + throw IncorrectOperationException("Failed to parse file template", e) + } + + val newBodyExpression = JetPsiFactory(func).createFunctionBody(bodyText) + func.getBodyExpression()!!.replace(newBodyExpression) + } + + private fun setupReturnTypeTemplate(builder: TemplateBuilder, func: JetNamedFunction): TypeExpression { + val returnTypeRef = func.getReturnTypeRef()!! + val returnTypeExpression = TypeExpression(typeCandidates[config.functionInfo.returnTypeInfo]!!) + builder.replaceElement(returnTypeRef, returnTypeExpression) + return returnTypeExpression + } + + private fun setupTypeParameterListTemplate(builder: TemplateBuilderImpl, func: JetNamedFunction): TypeParameterListExpression { + val typeParameterMap = HashMap>() + val receiverTypeParameterNames = receiverTypeCandidate.typeParameterNames + + config.functionInfo.parameterInfos.stream().flatMap { typeCandidates[it.typeInfo]!!.stream() }.forEach { + typeParameterMap[it.renderedType!!] = it.typeParameterNames!! + } + + if (func.getReturnTypeRef() != null) { + typeCandidates[config.functionInfo.returnTypeInfo]!!.forEach { + typeParameterMap[it.renderedType!!] = it.typeParameterNames!! + } + } + // ((3, 3) is after "fun") + builder.replaceElement(func, TextRange.create(3, 3), TYPE_PARAMETER_LIST_VARIABLE_NAME, null, false) + return TypeParameterListExpression(receiverTypeParameterNames!!, typeParameterMap) + } + + private fun setupParameterTypeTemplates(builder: TemplateBuilder, parameterList: JetParameterList): List { + val jetParameters = parameterList.getParameters() + assert(jetParameters.size == config.functionInfo.parameterInfos.size) + + val typeParameters = ArrayList() + for ((parameter, jetParameter) in config.functionInfo.parameterInfos.zip(jetParameters)) { + val parameterTypeExpression = TypeExpression(typeCandidates[parameter.typeInfo]!!) + val parameterTypeRef = jetParameter.getTypeReference()!! + builder.replaceElement(parameterTypeRef, parameterTypeExpression) + + // add parameter name to the template + val possibleNamesFromExpression = parameter.typeInfo.possibleNamesFromExpression + val preferredName = parameter.preferredName + val possibleNames = if (preferredName != null) { + array(preferredName, *possibleNamesFromExpression) + } + else { + possibleNamesFromExpression + } + + // figure out suggested names for each type option + val parameterTypeToNamesMap = HashMap>() + typeCandidates[parameter.typeInfo]!!.forEach { typeCandidate -> + val suggestedNames = JetNameSuggester.suggestNamesForType(typeCandidate.theType, EmptyValidator) + parameterTypeToNamesMap[typeCandidate.renderedType!!] = suggestedNames + } + + // add expression to builder + val parameterNameExpression = ParameterNameExpression(possibleNames, parameterTypeToNamesMap) + val parameterNameIdentifier = jetParameter.getNameIdentifier()!! + builder.replaceElement(parameterNameIdentifier, parameterNameExpression) + + typeParameters.add(parameterTypeExpression) + } + return typeParameters + } + + private fun replaceWithLongerName(typeRef: JetTypeReference, theType: JetType) { + val fullyQualifiedReceiverTypeRef = JetPsiFactory(typeRef).createType(theType.renderLong(typeParameterNameMap)) + typeRef.replace(fullyQualifiedReceiverTypeRef) + } + + fun buildAndRunTemplate() { + val func = createFunctionSkeleton() + val project = func.getProject() + val parameterList = func.getValueParameterList()!! + + // build templates + PsiDocumentManager.getInstance(project).commitAllDocuments() + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(containingFileEditor.getDocument()) + + val caretModel = containingFileEditor.getCaretModel() + caretModel.moveToOffset(containingFile.getNode().getStartOffset()) + + val builder = TemplateBuilderImpl(containingFile) + val returnTypeExpression = if (isUnit) null else setupReturnTypeTemplate(builder, func) + val parameterTypeExpressions = setupParameterTypeTemplates(builder, parameterList) + + // add a segment for the parameter list + // Note: because TemplateBuilderImpl does not have a replaceElement overload that takes in both a TextRange and alwaysStopAt, we + // need to create the segment first and then hack the Expression into the template later. We use this template to update the type + // parameter list as the user makes selections in the parameter types, and we need alwaysStopAt to be false so the user can't tab to + // it. + val expression = setupTypeParameterListTemplate(builder, func) + + // the template built by TemplateBuilderImpl is ordered by element position, but we want types to be first, so hack it + val templateImpl = builder.buildInlineTemplate() as TemplateImpl + val variables = templateImpl.getVariables()!! + for (i in 0..(config.functionInfo.parameterInfos.size - 1)) { + Collections.swap(variables, i * 2, i * 2 + 1) + } + + // fix up the template to include the expression for the type parameter list + variables.add(Variable(TYPE_PARAMETER_LIST_VARIABLE_NAME, expression, expression, false, true)) + + // TODO: Disabled shortening names because it causes some tests fail. Refactor code to use automatic reference shortening + templateImpl.setToShortenLongNames(false) + + // run the template + TemplateManager.getInstance(project).startTemplate(containingFileEditor, templateImpl, object : TemplateEditingAdapter() { + override fun templateFinished(template: Template?, brokenOff: Boolean) { + // file templates + val offset = templateImpl.getSegmentOffset(0) + val newFunc = PsiTreeUtil.findElementOfClassAtOffset(containingFile, offset, javaClass(), false)!! + val typeRefsToShorten = ArrayList() + + ApplicationManager.getApplication()!!.runWriteAction { + // file templates + setupFunctionBody(newFunc) + + // change short type names to fully qualified ones (to be shortened below) + setupTypeReferencesForShortening(newFunc, typeRefsToShorten, parameterTypeExpressions, returnTypeExpression) + ShortenReferences.process(typeRefsToShorten) + } + } + }) + } + } + + fun build() { + if (finished) throw IllegalStateException("Current builder has already finished") + + val context = Context() + runWriteAction { context.buildAndRunTemplate() } + + finished = true + } +} + +fun FunctionBuilderConfiguration.createBuilder(): FunctionBuilder = FunctionBuilder(this) \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/templateExpressions.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/templateExpressions.kt new file mode 100644 index 00000000000..01fd990387d --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/templateExpressions.kt @@ -0,0 +1,142 @@ +package org.jetbrains.jet.plugin.quickfix.createFromUsage.createFunction + +import com.intellij.codeInsight.template.Expression +import com.intellij.codeInsight.template.ExpressionContext +import com.intellij.codeInsight.template.Result +import com.intellij.codeInsight.template.TextResult +import com.intellij.codeInsight.lookup.LookupElement +import java.util.LinkedHashSet +import com.intellij.psi.PsiDocumentManager +import org.jetbrains.jet.lang.psi.JetFile +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.jet.lang.psi.JetFunction +import org.jetbrains.jet.lang.psi.JetParameter +import java.util.HashSet +import org.jetbrains.jet.plugin.refactoring.CollectingValidator +import com.intellij.codeInsight.lookup.LookupElementBuilder +import org.jetbrains.jet.lang.types.JetType + +/** + * Special Expression for parameter names based on its type. + */ +private class ParameterNameExpression( + private val names: Array, + private val parameterTypeToNamesMap: Map>) : Expression() { + { + assert(names all { it.isNotEmpty() }) + } + + override fun calculateResult(context: ExpressionContext?): Result? { + val lookupItems = calculateLookupItems(context)!! + return TextResult(if (lookupItems.isEmpty()) "" else lookupItems.first().getLookupString()) + } + + override fun calculateQuickResult(context: ExpressionContext?) = calculateResult(context) + + override fun calculateLookupItems(context: ExpressionContext?): Array? { + context!! + val names = LinkedHashSet(this.names.toList()) + + // find the parameter list + val project = context.getProject()!! + val offset = context.getStartOffset() + PsiDocumentManager.getInstance(project).commitAllDocuments() + val editor = context.getEditor()!! + val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) as JetFile + val elementAt = file.findElementAt(offset) + val func = PsiTreeUtil.getParentOfType(elementAt, javaClass()) ?: return array() + val parameterList = func.getValueParameterList()!! + + // add names based on selected type + val parameter = PsiTreeUtil.getParentOfType(elementAt, javaClass()) + if (parameter != null) { + val parameterTypeRef = parameter.getTypeReference() + if (parameterTypeRef != null) { + val suggestedNamesBasedOnType = parameterTypeToNamesMap[parameterTypeRef.getText()] + if (suggestedNamesBasedOnType != null) { + names.addAll(suggestedNamesBasedOnType) + } + } + } + + // remember other parameter names for later use + val parameterNames = parameterList.getParameters().stream().map { jetParameter -> + if (jetParameter == parameter) null else jetParameter.getName() + }.filterNotNullTo(HashSet()) + + // add fallback parameter name + if (names.isEmpty()) { + names.add("arg") + } + + // ensure there are no conflicts + val validator = CollectingValidator(parameterNames) + return names.map { LookupElementBuilder.create(validator.validateName(it)) }.copyToArray() + } +} + +/** + * An Expression for type references. + */ +private class TypeExpression(public val typeCandidates: List) : Expression() { + private val cachedLookupElements: Array = + typeCandidates.map { LookupElementBuilder.create(it, it.renderedType!!) }.copyToArray() + + override fun calculateResult(context: ExpressionContext?): Result { + val lookupItems = calculateLookupItems(context) + return TextResult(if (lookupItems.size == 0) "" else lookupItems[0].getLookupString()) + } + + override fun calculateQuickResult(context: ExpressionContext?) = calculateResult(context) + + override fun calculateLookupItems(context: ExpressionContext?) = cachedLookupElements + + public fun getTypeFromSelection(selection: String): JetType? = + typeCandidates.firstOrNull { it.renderedType == selection }?.theType +} + +/** + * A sort-of dummy Expression for parameter lists, to allow us to update the parameter list as the user makes selections. + */ +private class TypeParameterListExpression(private val typeParameterNamesFromReceiverType: Array, + private val parameterTypeToTypeParameterNamesMap: Map>) : Expression() { + + override fun calculateResult(context: ExpressionContext?): Result { + context!! + val project = context.getProject()!! + val offset = context.getStartOffset() + + PsiDocumentManager.getInstance(project).commitAllDocuments() + val editor = context.getEditor()!! + val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) as JetFile + val elementAt = file.findElementAt(offset) + val func = PsiTreeUtil.getParentOfType(elementAt, javaClass()) ?: return TextResult("") + val parameters = func.getValueParameters() + + val typeParameterNames = LinkedHashSet() + typeParameterNames.addAll(typeParameterNamesFromReceiverType) + for (parameter in parameters) { + val parameterTypeRef = parameter.getTypeReference() + if (parameterTypeRef != null) { + val typeParameterNamesFromParameter = parameterTypeToTypeParameterNamesMap[parameterTypeRef.getText()] + if (typeParameterNamesFromParameter != null) { + typeParameterNames.addAll(typeParameterNamesFromParameter) + } + } + } + val returnTypeRef = func.getReturnTypeRef() + if (returnTypeRef != null) { + val typeParameterNamesFromReturnType = parameterTypeToTypeParameterNamesMap[returnTypeRef.getText()] + if (typeParameterNamesFromReturnType != null) { + typeParameterNames.addAll(typeParameterNamesFromReturnType) + } + } + + return TextResult(if (typeParameterNames.empty) "" else typeParameterNames.joinToString(", ", " <", ">")) + } + + override fun calculateQuickResult(context: ExpressionContext?): Result = calculateResult(context) + + // do not offer the user any choices + override fun calculateLookupItems(context: ExpressionContext?) = array() +} diff --git a/idea/src/org/jetbrains/jet/plugin/util/TypeUtils.kt b/idea/src/org/jetbrains/jet/plugin/util/TypeUtils.kt index ed91b602313..d4403cde7d8 100644 --- a/idea/src/org/jetbrains/jet/plugin/util/TypeUtils.kt +++ b/idea/src/org/jetbrains/jet/plugin/util/TypeUtils.kt @@ -18,6 +18,11 @@ package org.jetbrains.jet.plugin.util import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.lang.types.TypeUtils +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns fun JetType.makeNullable() = TypeUtils.makeNullable(this) fun JetType.makeNotNullable() = TypeUtils.makeNotNullable(this) + +fun JetType.supertypes(): Set = TypeUtils.getAllSupertypes(this) + +fun JetType.isUnit(): Boolean = KotlinBuiltIns.getInstance().isUnit(this) \ No newline at end of file