Create Function From Usage: Refactor the function builder
This commit is contained in:
+4
-4
@@ -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))
|
||||
}
|
||||
}
|
||||
+30
-651
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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))
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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))
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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))
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -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))
|
||||
}
|
||||
}
|
||||
+63
@@ -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()
|
||||
)
|
||||
+466
@@ -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)
|
||||
+142
@@ -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)
|
||||
Reference in New Issue
Block a user