Create Function From Usage: Refactor the function builder

This commit is contained in:
Alexey Sedunov
2014-09-23 16:28:35 +04:00
parent d9318fdc57
commit ae69bea543
11 changed files with 730 additions and 675 deletions
@@ -22,17 +22,17 @@ object CreateComponentFunctionActionFactory : JetSingleIntentionActionFactory()
val ownerType = if (multiDeclaration == null) {
val forExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass<JetForExpression>())!!
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))
}
}
@@ -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<TypeParameterDescriptor>
public var renderedType: String? = null
private set
public var typeParameterNames: Array<String>? = null
private set
public fun render(typeParameterNameMap: Map<TypeParameterDescriptor, String>) {
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<TypeCandidate>? = null
private set
public val possibleNamesFromExpression: Array<String> 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<JetType> {
val types = ArrayList<JetType>()
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<JetTypeSubstitution>, scope: JetScope) {
val types = getPossibleTypes(context).reverse()
val newTypes = LinkedHashSet<JetType>(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<TypeParameterDescriptor, String>) {
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 <code>Expression</code> for parameter names based on its type.
*/
private class ParameterNameExpression(
private val names: Array<String>,
private val parameterTypeToNamesMap: Map<String, Array<String>>) : 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<LookupElement>? {
context!!
val names = LinkedHashSet<String>(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<JetFunction>()) ?: return array<LookupElement>()
val parameterList = func.getValueParameterList()!!
// add names based on selected type
val parameter = PsiTreeUtil.getParentOfType(elementAt, javaClass<JetParameter>())
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<String>())
// 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 <code>Expression</code> for type references.
*/
private class TypeExpression(public val theType: TypeOrExpressionThereof) : Expression() {
private val cachedLookupElements: Array<LookupElement> = 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 <code>Expression</code> for parameter lists, to allow us to update the parameter list as the user makes selections.
*/
private class TypeParameterListExpression(private val typeParameterNamesFromReceiverType: Array<String>,
private val parameterTypeToTypeParameterNamesMap: Map<String, Array<String>>) : 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<JetFunction>()) ?: return TextResult("")
val parameters = func.getValueParameters()
val typeParameterNames = LinkedHashSet<String>()
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<LookupElement>()
}
public class CreateFunctionFromUsageFix internal (
element: PsiElement,
private val ownerType: TypeOrExpressionThereof,
private val functionName: String,
private val returnType: TypeOrExpressionThereof,
private val parameters: List<Parameter> = Collections.emptyList()
) : CreateFromUsageFixBase(element) {
private class ClassCandidateListCellRenderer : PsiElementListCellRenderer<JetClass>() {
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<TypeParameterDescriptor, String> 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<JetNamedFunction>(), false)!!
val typeRefsToShorten = ArrayList<JetTypeReference>()
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<TypeParameterDescriptor, String> {
val allTypeParametersNotInScope = LinkedHashSet<TypeParameterDescriptor>()
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<TypeParameterDescriptor, String>()
for ((key, value) in allTypeParametersNotInScope.zip(typeParameterNames)) {
typeParameterNameMap[key] = value
}
return typeParameterNameMap
}
private fun setupTypeReferencesForShortening(
func: JetNamedFunction,
typeRefsToShorten: MutableList<JetTypeReference>, parameterTypeExpressions: List<TypeExpression>,
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<Int>()
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<String, Array<String>>()
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<TypeExpression> {
val jetParameters = parameterList.getParameters()
assert(jetParameters.size == parameters.size)
val typeParameters = ArrayList<TypeExpression>()
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<String, Array<String>>()
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)
}
}
@@ -11,13 +11,13 @@ object CreateGetFunctionActionFactory : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val accessExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass<JetArrayAccessExpression>()) ?: 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))
}
}
@@ -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<JetForExpression>()) ?: 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))
}
}
@@ -20,7 +20,7 @@ object CreateIteratorFunctionActionFactory : JetSingleIntentionActionFactory() {
val forExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass<JetForExpression>()) ?: 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))
}
}
@@ -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<JetForExpression>()) ?: 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))
}
}
@@ -14,18 +14,18 @@ object CreateSetFunctionActionFactory : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val accessExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass<JetArrayAccessExpression>()) ?: 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>()) {
Parameter(TypeOrExpressionThereof(it, Variance.IN_VARIANCE))
val parameters = accessExpr.getIndexExpressions().mapTo(ArrayList<ParameterInfo>()) {
ParameterInfo(TypeInfo(it, Variance.IN_VARIANCE))
}
val assignmentExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass<JetBinaryExpression>()) ?: 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))
}
}
@@ -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<String> by Delegates.lazy {
JetNameSuggester.suggestNamesForExpression(expression, EmptyValidator)
}
override fun getPossibleTypes(context: BindingContext): List<JetType> =
expression.guessTypes(context).flatMap { it.getPossibleSupertypes(variance) }
}
class ByType(val theType: JetType, variance: Variance): TypeInfo(variance) {
override val possibleNamesFromExpression: Array<String> =
ArrayUtil.EMPTY_STRING_ARRAY
override fun getPossibleTypes(context: BindingContext): List<JetType> =
theType.getPossibleSupertypes(variance)
}
abstract val possibleNamesFromExpression: Array<String>
abstract fun getPossibleTypes(context: BindingContext): List<JetType>
protected fun JetType.getPossibleSupertypes(variance: Variance): List<JetType> {
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<ParameterInfo> = Collections.emptyList()
)
@@ -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<TypeParameterDescriptor>
var renderedType: String? = null
private set
var typeParameterNames: Array<String>? = null
private set
fun render(typeParameterNameMap: Map<TypeParameterDescriptor, String>) {
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<TypeInfo, List<TypeCandidate>>();
{
val exhaust = config.currentFile.getAnalysisResults()
currentFileContext = exhaust.getBindingContext()
currentFileModule = exhaust.getModuleDescriptor()
}
fun computeTypeCandidates(typeInfo: TypeInfo): List<TypeCandidate> =
typeCandidates.getOrPut(typeInfo) { typeInfo.getPossibleTypes(currentFileContext).map { TypeCandidate(it) } }
fun computeTypeCandidates(
typeInfo: TypeInfo,
substitutions: Array<JetTypeSubstitution>,
scope: JetScope): List<TypeCandidate> {
return typeCandidates.getOrPut(typeInfo) {
val types = typeInfo.getPossibleTypes(currentFileContext).reverse()
val newTypes = LinkedHashSet<JetType>(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<TypeParameterDescriptor, String>
{
// 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<TypeParameterDescriptor, String>
) {
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<TypeParameterDescriptor, String> {
val allTypeParametersNotInScope = LinkedHashSet<TypeParameterDescriptor>()
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<JetTypeReference>, parameterTypeExpressions: List<TypeExpression>,
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<Int>()
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<String, Array<String>>()
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<TypeExpression> {
val jetParameters = parameterList.getParameters()
assert(jetParameters.size == config.functionInfo.parameterInfos.size)
val typeParameters = ArrayList<TypeExpression>()
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<String, Array<String>>()
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<JetNamedFunction>(), false)!!
val typeRefsToShorten = ArrayList<JetTypeReference>()
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)
@@ -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 <code>Expression</code> for parameter names based on its type.
*/
private class ParameterNameExpression(
private val names: Array<String>,
private val parameterTypeToNamesMap: Map<String, Array<String>>) : 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<LookupElement>? {
context!!
val names = LinkedHashSet<String>(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<JetFunction>()) ?: return array<LookupElement>()
val parameterList = func.getValueParameterList()!!
// add names based on selected type
val parameter = PsiTreeUtil.getParentOfType(elementAt, javaClass<JetParameter>())
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<String>())
// 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 <code>Expression</code> for type references.
*/
private class TypeExpression(public val typeCandidates: List<TypeCandidate>) : Expression() {
private val cachedLookupElements: Array<LookupElement> =
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 <code>Expression</code> for parameter lists, to allow us to update the parameter list as the user makes selections.
*/
private class TypeParameterListExpression(private val typeParameterNamesFromReceiverType: Array<String>,
private val parameterTypeToTypeParameterNamesMap: Map<String, Array<String>>) : 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<JetFunction>()) ?: return TextResult("")
val parameters = func.getValueParameters()
val typeParameterNames = LinkedHashSet<String>()
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<LookupElement>()
}
@@ -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<JetType> = TypeUtils.getAllSupertypes(this)
fun JetType.isUnit(): Boolean = KotlinBuiltIns.getInstance().isUnit(this)