From deb2ffb49f0e98119724fab3bd51437ee5723f27 Mon Sep 17 00:00:00 2001 From: Vyacheslav Gerasimov Date: Thu, 18 Jan 2018 02:16:51 +0300 Subject: [PATCH] 172: Revert "Refactoring: Use modifier list in CallableInfo where possible" This reverts commit 99be75c --- .../callableBuilder/CallableBuilder.kt.172 | 1149 +++++++++++++++++ .../callableBuilder/CallableInfo.kt.172 | 281 ++++ .../CreateBinaryOperationActionFactory.kt.172 | 62 + ...CreateCallableFromCallActionFactory.kt.172 | 342 +++++ ...reateComponentFunctionActionFactory.kt.172 | 57 + .../CreateGetFunctionActionFactory.kt.172 | 40 + .../CreateHasNextFunctionActionFactory.kt.172 | 43 + .../CreateInvokeFunctionActionFactory.kt.172 | 53 + ...CreateIteratorFunctionActionFactory.kt.172 | 62 + .../CreateNextFunctionActionFactory.kt.172 | 44 + ...pertyDelegateAccessorsActionFactory.kt.172 | 97 ++ .../CreateSetFunctionActionFactory.kt.172 | 64 + .../CreateUnaryOperationActionFactory.kt.172 | 44 + .../KotlinElementActionsFactory.kt.172 | 390 ++++++ 14 files changed, 2728 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateBinaryOperationActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromCallActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateGetFunctionActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateHasNextFunctionActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateInvokeFunctionActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateIteratorFunctionActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateNextFunctionActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreatePropertyDelegateAccessorsActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateSetFunctionActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateUnaryOperationActionFactory.kt.172 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt.172 diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt.172 new file mode 100644 index 00000000000..71be8ef21ab --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt.172 @@ -0,0 +1,1149 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.callableBuilder + +import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils +import com.intellij.codeInsight.navigation.NavigationUtil +import com.intellij.codeInsight.template.* +import com.intellij.codeInsight.template.impl.TemplateImpl +import com.intellij.codeInsight.template.impl.TemplateManagerImpl +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ScrollType +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.util.UnfairTextRange +import com.intellij.psi.* +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor +import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode +import org.jetbrains.kotlin.cfg.pseudocode.getContainingPseudocode +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl +import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks +import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor +import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.idea.core.* +import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.ClassKind +import org.jetbrains.kotlin.idea.refactoring.* +import org.jetbrains.kotlin.idea.util.DialogWithEditor +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny +import org.jetbrains.kotlin.resolve.scopes.HierarchicalScope +import org.jetbrains.kotlin.resolve.scopes.LexicalScopeImpl +import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind +import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier +import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeProjectionImpl +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.checker.KotlinTypeChecker +import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny +import org.jetbrains.kotlin.types.typeUtil.isUnit +import java.lang.AssertionError +import java.lang.IllegalArgumentException +import java.lang.IllegalStateException +import java.util.* + +/** + * Represents a single choice for a type (e.g. parameter type or return type). + */ +class TypeCandidate(val theType: KotlinType, scope: HierarchicalScope? = null) { + val typeParameters: Array + var renderedTypes: List = emptyList() + private set + var renderedTypeParameters: List? = null + private set + + fun render(typeParameterNameMap: Map, fakeFunction: FunctionDescriptor?) { + renderedTypes = theType.renderShort(typeParameterNameMap) + renderedTypeParameters = typeParameters.map { + RenderedTypeParameter(it, it.containingDeclaration == fakeFunction, typeParameterNameMap[it]!!) + } + } + + init { + val typeParametersInType = theType.getTypeParameters() + if (scope == null) { + typeParameters = typeParametersInType.toTypedArray() + renderedTypes = theType.renderShort(Collections.emptyMap()) + } + else { + typeParameters = getTypeParameterNamesNotInScope(typeParametersInType, scope).toTypedArray() + } + } + + override fun toString() = theType.toString() +} + +data class RenderedTypeParameter( + val typeParameter: TypeParameterDescriptor, + val fake: Boolean, + val text: String +) + +fun List.getTypeByRenderedType(renderedTypes: List): KotlinType? = + firstOrNull { it.renderedTypes == renderedTypes }?.theType + +class CallableBuilderConfiguration( + val callableInfos: List, + val originalElement: KtElement, + val currentFile: KtFile = originalElement.containingKtFile, + val currentEditor: Editor? = null, + val isExtension: Boolean = false, + val enableSubstitutions: Boolean = true +) + +sealed class CallablePlacement { + class WithReceiver(val receiverTypeCandidate: TypeCandidate): CallablePlacement() + class NoReceiver(val containingElement: PsiElement): CallablePlacement() +} + +class CallableBuilder(val config: CallableBuilderConfiguration) { + private var finished: Boolean = false + + val currentFileContext = config.currentFile.analyzeWithContent() + + private lateinit var _currentFileModule: ModuleDescriptor + val currentFileModule: ModuleDescriptor + get() { + if (!_currentFileModule.isValid) { + updateCurrentModule() + } + return _currentFileModule + } + + init { + updateCurrentModule() + } + + val pseudocode: Pseudocode? by lazy { config.originalElement.getContainingPseudocode(currentFileContext) } + + private val typeCandidates = HashMap>() + + var placement: CallablePlacement? = null + + private val elementsToShorten = ArrayList() + + private fun updateCurrentModule() { + _currentFileModule = config.currentFile.analyzeWithAllCompilerChecks().moduleDescriptor + } + + fun computeTypeCandidates(typeInfo: TypeInfo): List = + typeCandidates.getOrPut(typeInfo) { typeInfo.getPossibleTypes(this).map { TypeCandidate(it) } } + + private fun computeTypeCandidates( + typeInfo: TypeInfo, + substitutions: List, + scope: HierarchicalScope): List { + if (!typeInfo.substitutionsAllowed) return computeTypeCandidates(typeInfo) + return typeCandidates.getOrPut(typeInfo) { + val types = typeInfo.getPossibleTypes(this).asReversed() + + // We have to use semantic equality here + data class EqWrapper(val _type: KotlinType) { + override fun equals(other: Any?) = this === other + || other is EqWrapper && KotlinTypeChecker.DEFAULT.equalTypes(_type, other._type) + override fun hashCode() = 0 // no good way to compute hashCode() that would agree with our equals() + } + + val newTypes = LinkedHashSet(types.map(::EqWrapper)) + for (substitution in substitutions) { + // each substitution can be applied or not, so we offer all options + val toAdd = newTypes.map { it._type.substitute(substitution, typeInfo.variance) } + // substitution.byType are type arguments, but they cannot already occur in the type before substitution + val toRemove = newTypes.filter { substitution.byType in it._type } + + newTypes.addAll(toAdd.map(::EqWrapper)) + newTypes.removeAll(toRemove) + } + + if (newTypes.isEmpty()) { + newTypes.add(EqWrapper(currentFileModule.builtIns.anyType)) + } + + newTypes.map { TypeCandidate(it._type, scope) }.asReversed() + } + } + + private fun buildNext(iterator: Iterator) { + if (iterator.hasNext()) { + val context = Context(iterator.next()) + runWriteAction { context.buildAndRunTemplate { buildNext(iterator) } } + ApplicationManager.getApplication().invokeLater { context.showDialogIfNeeded() } + } + else { + runWriteAction { ShortenReferences.DEFAULT.process(elementsToShorten) } + } + } + + fun build(onFinish: () -> Unit = {}) { + try { + assert(config.currentEditor != null) { "Can't run build() without editor" } + if (finished) throw IllegalStateException("Current builder has already finished") + buildNext(config.callableInfos.iterator()) + } + finally { + finished = true + onFinish() + } + } + + private inner class Context(val callableInfo: CallableInfo) { + val skipReturnType: Boolean + val jetFileToEdit: KtFile + val containingFileEditor: Editor + val containingElement: PsiElement + val dialogWithEditor: DialogWithEditor? + val receiverClassDescriptor: ClassifierDescriptor? + val typeParameterNameMap: Map + val receiverTypeCandidate: TypeCandidate? + val mandatoryTypeParametersAsCandidates: List + val substitutions: List + var finished: Boolean = false + + init { + // gather relevant information + + val placement = placement + when (placement) { + is CallablePlacement.NoReceiver -> { + containingElement = placement.containingElement + receiverClassDescriptor = with (placement.containingElement) { + when (this) { + is KtClassOrObject -> currentFileContext[BindingContext.CLASS, this] + is PsiClass -> getJavaClassDescriptor() + else -> null + } + } + } + is CallablePlacement.WithReceiver -> { + receiverClassDescriptor = + placement.receiverTypeCandidate.theType.constructor.declarationDescriptor + val classDeclaration = receiverClassDescriptor?.let { DescriptorToSourceUtils.getSourceFromDescriptor(it) } + containingElement = if (!config.isExtension && classDeclaration != null) classDeclaration else config.currentFile + } + else -> throw IllegalArgumentException("Placement wan't initialized") + } + val receiverType = receiverClassDescriptor?.defaultType + + val project = config.currentFile.project + + if (containingElement.containingFile != config.currentFile) { + NavigationUtil.activateFileWithPsiElement(containingElement) + } + + dialogWithEditor = if (containingElement is KtElement) { + jetFileToEdit = containingElement.containingKtFile + containingFileEditor = if (jetFileToEdit != config.currentFile) { + FileEditorManager.getInstance(project).selectedTextEditor!! + } + else { + config.currentEditor!! + } + null + } else { + val dialog = object: DialogWithEditor(project, "Create from usage", "") { + override fun doOKAction() { + project.executeWriteCommand("Premature end of template") { + TemplateManagerImpl.getTemplateState(editor)?.gotoEnd(false) + } + super.doOKAction() + } + } + containingFileEditor = dialog.editor + with(containingFileEditor.settings) { + additionalColumnsCount = config.currentEditor!!.settings.getRightMargin(project) + additionalLinesCount = 5 + } + jetFileToEdit = PsiDocumentManager.getInstance(project).getPsiFile(containingFileEditor.document) as KtFile + jetFileToEdit.analysisContext = config.currentFile + dialog + } + + val scope = getDeclarationScope() + + receiverTypeCandidate = receiverType?.let { TypeCandidate(it, scope) } + + val fakeFunction: FunctionDescriptor? + // figure out type substitutions for type parameters + val substitutionMap = LinkedHashMap() + if (config.enableSubstitutions) { + collectSubstitutionsForReceiverTypeParameters(receiverType, substitutionMap) + val typeArgumentsForFakeFunction = callableInfo.typeParameterInfos + .map { + val typeCandidates = computeTypeCandidates(it) + assert (typeCandidates.size == 1) { "Ambiguous type candidates for type parameter $it: $typeCandidates" } + typeCandidates.first().theType + } + .subtract(substitutionMap.keys) + fakeFunction = createFakeFunctionDescriptor(scope, typeArgumentsForFakeFunction.size) + collectSubstitutionsForCallableTypeParameters(fakeFunction, typeArgumentsForFakeFunction, substitutionMap) + mandatoryTypeParametersAsCandidates = listOfNotNull(receiverTypeCandidate) + typeArgumentsForFakeFunction.map { TypeCandidate(substitutionMap[it]!!, scope) } + } + else { + fakeFunction = null + mandatoryTypeParametersAsCandidates = Collections.emptyList() + } + substitutions = substitutionMap.map { KotlinTypeSubstitution(it.key, it.value) } + + callableInfo.parameterInfos.forEach { + computeTypeCandidates(it.typeInfo, substitutions, scope) + } + + val returnTypeCandidate = computeTypeCandidates(callableInfo.returnTypeInfo, substitutions, scope).singleOrNull() + skipReturnType = when (callableInfo.kind) { + CallableKind.FUNCTION -> + returnTypeCandidate?.theType?.isUnit() ?: false + CallableKind.CLASS_WITH_PRIMARY_CONSTRUCTOR -> + callableInfo.returnTypeInfo == TypeInfo.Empty || returnTypeCandidate?.theType?.isAnyOrNullableAny() ?: false + CallableKind.CONSTRUCTOR -> true + CallableKind.PROPERTY -> containingElement is KtBlockExpression + } + + // figure out type parameter renames to avoid conflicts + typeParameterNameMap = getTypeParameterRenames(scope) + callableInfo.parameterInfos.forEach { renderTypeCandidates(it.typeInfo, typeParameterNameMap, fakeFunction) } + if (!skipReturnType) { + renderTypeCandidates(callableInfo.returnTypeInfo, typeParameterNameMap, fakeFunction) + } + receiverTypeCandidate?.render(typeParameterNameMap, fakeFunction) + mandatoryTypeParametersAsCandidates.forEach { it.render(typeParameterNameMap, fakeFunction) } + } + + private fun getDeclarationScope(): HierarchicalScope { + if (config.isExtension || receiverClassDescriptor == null) { + return currentFileModule.getPackage(config.currentFile.packageFqName).memberScope.memberScopeAsImportingScope() + } + + if (receiverClassDescriptor is ClassDescriptorWithResolutionScopes) { + return receiverClassDescriptor.scopeForMemberDeclarationResolution + } + + assert (receiverClassDescriptor is JavaClassDescriptor) { "Unexpected receiver class: $receiverClassDescriptor" } + + val projections = ((receiverClassDescriptor as JavaClassDescriptor).declaredTypeParameters) + .map { TypeProjectionImpl(it.defaultType) } + val memberScope = receiverClassDescriptor.getMemberScope(projections) + + return LexicalScopeImpl(memberScope.memberScopeAsImportingScope(), receiverClassDescriptor, false, null, + LexicalScopeKind.SYNTHETIC) { + receiverClassDescriptor.typeConstructor.parameters.forEach { addClassifierDescriptor(it) } + } + } + + private fun collectSubstitutionsForReceiverTypeParameters( + receiverType: KotlinType?, + result: MutableMap + ) { + if (placement is CallablePlacement.NoReceiver) return + + val classTypeParameters = receiverType?.arguments ?: Collections.emptyList() + val ownerTypeArguments = (placement as? CallablePlacement.WithReceiver)?.receiverTypeCandidate?.theType?.arguments + ?: Collections.emptyList() + assert(ownerTypeArguments.size == classTypeParameters.size) + ownerTypeArguments.zip(classTypeParameters).forEach { result[it.first.type] = it.second.type } + } + + private fun collectSubstitutionsForCallableTypeParameters( + fakeFunction: FunctionDescriptor, + typeArguments: Set, + result: MutableMap) { + for ((typeArgument, typeParameter) in typeArguments.zip(fakeFunction.typeParameters)) { + result[typeArgument] = typeParameter.defaultType + } + } + + private fun createFakeFunctionDescriptor(scope: HierarchicalScope, typeParameterCount: Int): FunctionDescriptor { + val fakeFunction = SimpleFunctionDescriptorImpl.create( + MutablePackageFragmentDescriptor(currentFileModule, FqName("fake")), + Annotations.EMPTY, + Name.identifier("fake"), + CallableMemberDescriptor.Kind.SYNTHESIZED, + SourceElement.NO_SOURCE + ) + + val validator = CollectingNameValidator { scope.findClassifier(Name.identifier(it), NoLookupLocation.FROM_IDE) == null } + val parameterNames = KotlinNameSuggester.suggestNamesForTypeParameters(typeParameterCount, validator) + val typeParameters = (0..typeParameterCount - 1).map { + TypeParameterDescriptorImpl.createWithDefaultBound( + fakeFunction, + Annotations.EMPTY, + false, + Variance.INVARIANT, + Name.identifier(parameterNames[it]), + it + ) + } + + return fakeFunction.initialize(null, null, typeParameters, Collections.emptyList(), null, + null, Visibilities.INTERNAL) + } + + private fun renderTypeCandidates( + typeInfo: TypeInfo, + typeParameterNameMap: Map, + fakeFunction: FunctionDescriptor? + ) { + typeCandidates[typeInfo]?.forEach { it.render(typeParameterNameMap, fakeFunction) } + } + + private fun isInsideInnerOrLocalClass(): Boolean { + val classOrObject = containingElement.getNonStrictParentOfType() + return classOrObject is KtClass && (classOrObject.isInner() || classOrObject.isLocal) + } + + private fun createDeclarationSkeleton(): KtNamedDeclaration { + with (config) { + val assignmentToReplace = + if (containingElement is KtBlockExpression && (callableInfo as? PropertyInfo)?.writable ?: false) { + originalElement as KtBinaryExpression + } + else null + + val ownerTypeString = if (isExtension) { + val renderedType = receiverTypeCandidate!!.renderedTypes.first() + val isFunctionType = receiverTypeCandidate.theType.constructor.declarationDescriptor is FunctionClassDescriptor + if (isFunctionType) "($renderedType)." else "$renderedType." + } else "" + + val classKind = (callableInfo as? ClassWithPrimaryConstructorInfo)?.classInfo?.kind + + fun renderParamList(): String { + val prefix = if (classKind == ClassKind.ANNOTATION_CLASS) "val " else "" + val list = callableInfo.parameterInfos.indices.joinToString(", ") { i -> "${prefix}p$i: Any" } + return if (callableInfo.parameterInfos.isNotEmpty() + || callableInfo.kind == CallableKind.FUNCTION + || callableInfo.kind == CallableKind.CONSTRUCTOR) "($list)" else list + } + + val paramList = when (callableInfo.kind) { + CallableKind.FUNCTION, CallableKind.CLASS_WITH_PRIMARY_CONSTRUCTOR, CallableKind.CONSTRUCTOR -> + renderParamList() + CallableKind.PROPERTY -> "" + } + val returnTypeString = if (skipReturnType || assignmentToReplace != null) "" else ": Any" + val header = "$ownerTypeString${callableInfo.name.quoteIfNeeded()}$paramList$returnTypeString" + + val psiFactory = KtPsiFactory(currentFile) + + val modifiers = buildString { + val modifierList = callableInfo.modifierList?.copied() ?: psiFactory.createEmptyModifierList() + val visibilityKeyword = modifierList.visibilityModifierType() + if (visibilityKeyword == null) { + val defaultVisibility = + if (callableInfo.isAbstract) "" + else if (containingElement is KtClassOrObject + && !(containingElement is KtClass && containingElement.isInterface()) + && containingElement.isAncestor(config.originalElement) + && callableInfo.kind != CallableKind.CONSTRUCTOR) "private " + else if (isExtension) "private " + else "" + append(defaultVisibility) + } + + // TODO: Get rid of isAbstract + if (callableInfo.isAbstract + && containingElement is KtClass + && !containingElement.isInterface() + && !modifierList.hasModifier(KtTokens.ABSTRACT_KEYWORD)) { + modifierList.appendModifier(KtTokens.ABSTRACT_KEYWORD) + } + + val text = modifierList.normalize().text + if (text.isNotEmpty()) { + append("$text ") + } + } + + val isExpectClassMember by lazy { + containingElement is KtClassOrObject && containingElement.resolveToDescriptorIfAny()?.isExpect ?: false + } + + val declaration: KtNamedDeclaration = when (callableInfo.kind) { + CallableKind.FUNCTION, CallableKind.CONSTRUCTOR -> { + val body = when { + callableInfo is ConstructorInfo -> if (callableInfo.withBody) "{\n\n}" else "" + callableInfo.isAbstract -> "" + containingElement is KtClass && containingElement.hasModifier(KtTokens.EXTERNAL_KEYWORD) -> "" + containingElement is KtObjectDeclaration && containingElement.hasModifier(KtTokens.EXTERNAL_KEYWORD) -> "" + containingElement is KtObjectDeclaration && containingElement.isCompanion() + && containingElement.parent.parent is KtClass + && (containingElement.parent.parent as KtClass).hasModifier(KtTokens.EXTERNAL_KEYWORD) -> "" + isExpectClassMember -> "" + else -> "{\n\n}" + + } + @Suppress("USELESS_CAST") // KT-10755 + if (callableInfo is FunctionInfo) { + val operatorModifier = if (callableInfo.isOperator) "operator " else "" + val infixModifier = if (callableInfo.isInfix) "infix " else "" + psiFactory.createFunction("$modifiers$infixModifier${operatorModifier}fun<> $header $body") as KtNamedDeclaration + } + else if ((callableInfo as ConstructorInfo).isPrimary) { + val constructorText = if (modifiers.isNotEmpty()) "${modifiers}constructor$paramList" else paramList + psiFactory.createPrimaryConstructor(constructorText) as KtNamedDeclaration + } + else { + psiFactory.createSecondaryConstructor("${modifiers}constructor$paramList $body") as KtNamedDeclaration + } + } + CallableKind.CLASS_WITH_PRIMARY_CONSTRUCTOR -> { + with((callableInfo as ClassWithPrimaryConstructorInfo).classInfo) { + val classBody = when (kind) { + ClassKind.ANNOTATION_CLASS, ClassKind.ENUM_ENTRY -> "" + else -> "{\n\n}" + } + val safeName = name.quoteIfNeeded() + when (kind) { + ClassKind.ENUM_ENTRY -> { + val targetParent = applicableParents.singleOrNull() + if (!(targetParent is KtClass && targetParent.isEnum())) throw AssertionError("Enum class expected: ${targetParent?.text}") + val hasParameters = targetParent.primaryConstructorParameters.isNotEmpty() + psiFactory.createEnumEntry("$safeName${if (hasParameters) "()" else " "}") + } + else -> { + val openMod = if (open) "open " else "" + val innerMod = if (inner || isInsideInnerOrLocalClass()) "inner " else "" + val typeParamList = when (kind) { + ClassKind.PLAIN_CLASS, ClassKind.INTERFACE -> "<>" + else -> "" + } + psiFactory.createDeclaration( + "$openMod$innerMod${kind.keyword} $safeName$typeParamList$paramList$returnTypeString $classBody" + ) + } + } + } + } + CallableKind.PROPERTY -> { + val isVar = (callableInfo as PropertyInfo).writable + val valVar = if (isVar) "var" else "val" + val accessors = if (isExtension && !isExpectClassMember) { + buildString { + append("\nget() {}") + if (isVar) { + append("\nset() {}") + } + } + } + else "" + psiFactory.createProperty("$modifiers$valVar<> $header$accessors") + } + } + + if (assignmentToReplace != null) { + (declaration as KtProperty).initializer = assignmentToReplace.right + return assignmentToReplace.replace(declaration) as KtCallableDeclaration + } + + val container = if (containingElement is KtClass && callableInfo.isForCompanion) { + containingElement.getOrCreateCompanionObject() + } + else containingElement + val declarationInPlace = placeDeclarationInContainer(declaration, container, config.originalElement, jetFileToEdit) + + if (declarationInPlace is KtSecondaryConstructor) { + val containingClass = declarationInPlace.containingClassOrObject!! + if (containingClass.primaryConstructorParameters.isNotEmpty()) { + declarationInPlace.replaceImplicitDelegationCallWithExplicit(true) + } + else if ((receiverClassDescriptor as ClassDescriptor).getSuperClassOrAny().constructors.all { it.valueParameters.isNotEmpty() }) { + declarationInPlace.replaceImplicitDelegationCallWithExplicit(false) + } + } + + return declarationInPlace + } + } + + private fun getTypeParameterRenames(scope: HierarchicalScope): Map { + val allTypeParametersNotInScope = LinkedHashSet() + + mandatoryTypeParametersAsCandidates.asSequence() + .plus(callableInfo.parameterInfos.asSequence().flatMap { typeCandidates[it.typeInfo]!!.asSequence() }) + .flatMap { it.typeParameters.asSequence() } + .toCollection(allTypeParametersNotInScope) + + if (!skipReturnType) { + computeTypeCandidates(callableInfo.returnTypeInfo).asSequence().flatMapTo(allTypeParametersNotInScope) { + it.typeParameters.asSequence() + } + } + + val validator = CollectingNameValidator { scope.findClassifier(Name.identifier(it), NoLookupLocation.FROM_IDE) == null } + val typeParameterNames = allTypeParametersNotInScope.map { KotlinNameSuggester.suggestNameByName(it.name.asString(), validator) } + + return allTypeParametersNotInScope.zip(typeParameterNames).toMap() + } + + private fun setupTypeReferencesForShortening(declaration: KtNamedDeclaration, + parameterTypeExpressions: List) { + if (config.isExtension) { + val receiverTypeText = receiverTypeCandidate!!.theType.renderLong(typeParameterNameMap).first() + val replacingTypeRef = KtPsiFactory(declaration).createType(receiverTypeText) + (declaration as KtCallableDeclaration).setReceiverTypeReference(replacingTypeRef)!! + } + + val returnTypeRefs = declaration.getReturnTypeReferences() + if (returnTypeRefs.isNotEmpty()) { + val returnType = typeCandidates[callableInfo.returnTypeInfo]!!.getTypeByRenderedType( + returnTypeRefs.map { it.text } + ) + if (returnType != null) { + // user selected a given type + replaceWithLongerName(returnTypeRefs, returnType) + } + } + + val valueParameters = declaration.getValueParameters() + val parameterIndicesToShorten = ArrayList() + assert(valueParameters.size == parameterTypeExpressions.size) + for ((i, parameter) in valueParameters.asSequence().withIndex()) { + val parameterTypeRef = parameter.typeReference + if (parameterTypeRef != null) { + val parameterType = parameterTypeExpressions[i].typeCandidates.getTypeByRenderedType( + listOf(parameterTypeRef.text) + ) + if (parameterType != null) { + replaceWithLongerName(listOf(parameterTypeRef), parameterType) + parameterIndicesToShorten.add(i) + } + } + } + } + + private fun postprocessDeclaration(declaration: KtNamedDeclaration) { + if (callableInfo is PropertyInfo && callableInfo.isLateinitPreferred) { + if (declaration.containingClassOrObject == null) return + val propertyDescriptor = declaration.resolveToDescriptorIfAny() as? PropertyDescriptor ?: return + val returnType = propertyDescriptor.returnType ?: return + if (TypeUtils.isNullableType(returnType) || KotlinBuiltIns.isPrimitiveType(returnType)) return + declaration.addModifier(KtTokens.LATEINIT_KEYWORD) + } + } + + private fun setupDeclarationBody(func: KtDeclarationWithBody) { + if (func !is KtNamedFunction && func !is KtPropertyAccessor) return + if (skipReturnType && callableInfo is FunctionInfo && callableInfo.preferEmptyBody) return + val oldBody = func.bodyExpression ?: return + val bodyText = getFunctionBodyTextFromTemplate( + func.project, + TemplateKind.FUNCTION, + if (callableInfo.name.isNotEmpty()) callableInfo.name else null, + if (skipReturnType) "Unit" else (func as? KtFunction)?.typeReference?.text ?: "", + receiverClassDescriptor?.importableFqName ?: receiverClassDescriptor?.name?.let { FqName.topLevel(it) } + ) + oldBody.replace(KtPsiFactory(func).createBlock(bodyText)) + } + + private fun setupCallTypeArguments(callElement: KtCallElement, typeParameters: List) { + val oldTypeArgumentList = callElement.typeArgumentList ?: return + val renderedTypeArgs = typeParameters.map { typeParameter -> + val type = substitutions.first { it.byType.constructor.declarationDescriptor == typeParameter }.forType + IdeDescriptorRenderers.SOURCE_CODE.renderType(type) + } + if (renderedTypeArgs.isEmpty()) { + oldTypeArgumentList.delete() + } + else { + oldTypeArgumentList.replace(KtPsiFactory(callElement).createTypeArguments(renderedTypeArgs.joinToString(", ", "<", ">"))) + elementsToShorten.add(callElement.typeArgumentList!!) + } + } + + private fun setupReturnTypeTemplate(builder: TemplateBuilder, declaration: KtNamedDeclaration): TypeExpression? { + val candidates = typeCandidates[callableInfo.returnTypeInfo]!! + if (candidates.isEmpty()) return null + + val elementToReplace: KtElement? + val expression: TypeExpression = when (declaration) { + is KtCallableDeclaration -> { + elementToReplace = declaration.typeReference + TypeExpression.ForTypeReference(candidates) + } + is KtClassOrObject -> { + elementToReplace = declaration.superTypeListEntries.firstOrNull() + TypeExpression.ForDelegationSpecifier(candidates) + } + else -> throw AssertionError("Unexpected declaration kind: ${declaration.text}") + } + if (elementToReplace == null) return null + + if (candidates.size == 1) { + builder.replaceElement(elementToReplace, (expression.calculateResult(null) as TextResult).text) + return null + } + + builder.replaceElement(elementToReplace, expression) + return expression + } + + private fun setupValVarTemplate(builder: TemplateBuilder, property: KtProperty) { + if (!(callableInfo as PropertyInfo).writable) { + builder.replaceElement(property.valOrVarKeyword, ValVarExpression) + } + } + + private fun setupTypeParameterListTemplate( + builder: TemplateBuilderImpl, + declaration: KtNamedDeclaration + ): TypeParameterListExpression? { + when (declaration) { + is KtObjectDeclaration -> return null + !is KtTypeParameterListOwner -> throw AssertionError("Unexpected declaration kind: ${declaration.text}") + } + + val typeParameterList = (declaration as KtTypeParameterListOwner).typeParameterList ?: return null + + val typeParameterMap = HashMap>() + + val mandatoryTypeParameters = ArrayList() + //receiverTypeCandidate?.let { mandatoryTypeParameters.addAll(it.renderedTypeParameters!!) } + mandatoryTypeParametersAsCandidates.asSequence().flatMapTo(mandatoryTypeParameters) { it.renderedTypeParameters!!.asSequence() } + + callableInfo.parameterInfos.asSequence() + .flatMap { typeCandidates[it.typeInfo]!!.asSequence() } + .forEach { typeParameterMap[it.renderedTypes.first()] = it.renderedTypeParameters!! } + + if (declaration.getReturnTypeReference() != null) { + typeCandidates[callableInfo.returnTypeInfo]!!.forEach { + typeParameterMap[it.renderedTypes.first()] = it.renderedTypeParameters!! + } + } + + val expression = TypeParameterListExpression( + mandatoryTypeParameters, + typeParameterMap, + callableInfo.kind != CallableKind.CLASS_WITH_PRIMARY_CONSTRUCTOR + ) + val leftSpace = typeParameterList.prevSibling as? PsiWhiteSpace + val rangeStart = if (leftSpace != null) leftSpace.startOffset else typeParameterList.startOffset + val offset = typeParameterList.startOffset + val range = UnfairTextRange(rangeStart - offset, typeParameterList.endOffset - offset) + builder.replaceElement(typeParameterList, range, "TYPE_PARAMETER_LIST", expression, false) + return expression + } + + private fun setupParameterTypeTemplates(builder: TemplateBuilder, parameterList: List): List { + assert(parameterList.size == callableInfo.parameterInfos.size) + + val typeParameters = ArrayList() + for ((parameter, jetParameter) in callableInfo.parameterInfos.zip(parameterList)) { + val parameterTypeExpression = TypeExpression.ForTypeReference(typeCandidates[parameter.typeInfo]!!) + val parameterTypeRef = jetParameter.typeReference!! + builder.replaceElement(parameterTypeRef, parameterTypeExpression) + + // add parameter name to the template + val possibleNamesFromExpression = parameter.typeInfo.getPossibleNamesFromExpression(currentFileContext) + val possibleNames = arrayOf(*parameter.nameSuggestions.toTypedArray(), *possibleNamesFromExpression) + + // figure out suggested names for each type option + val parameterTypeToNamesMap = HashMap>() + typeCandidates[parameter.typeInfo]!!.forEach { typeCandidate -> + val suggestedNames = KotlinNameSuggester.suggestNamesByType(typeCandidate.theType, { true }) + parameterTypeToNamesMap[typeCandidate.renderedTypes.first()] = suggestedNames.toTypedArray() + } + + // add expression to builder + val parameterNameExpression = ParameterNameExpression(possibleNames, parameterTypeToNamesMap) + val parameterNameIdentifier = jetParameter.nameIdentifier!! + builder.replaceElement(parameterNameIdentifier, parameterNameExpression) + + typeParameters.add(parameterTypeExpression) + } + return typeParameters + } + + private fun replaceWithLongerName(typeRefs: List, theType: KotlinType) { + val psiFactory = KtPsiFactory(jetFileToEdit.project) + val fullyQualifiedReceiverTypeRefs = theType.renderLong(typeParameterNameMap).map { psiFactory.createType(it) } + (typeRefs zip fullyQualifiedReceiverTypeRefs).forEach { (shortRef, longRef) -> shortRef.replace(longRef) } + } + + private fun transformToJavaMemberIfApplicable(declaration: KtNamedDeclaration): Boolean { + fun convertToJava(targetClass: PsiClass): PsiMember? { + val psiFactory = KtPsiFactory(declaration) + + psiFactory.createPackageDirectiveIfNeeded(config.currentFile.packageFqName)?.let { + declaration.containingFile.addBefore(it, null) + } + + val adjustedDeclaration = when (declaration) { + is KtNamedFunction, is KtProperty -> { + val klass = psiFactory.createClass("class Foo {}") + klass.getBody()!!.add(declaration) + (declaration.replace(klass) as KtClass).getBody()!!.declarations.first() + } + else -> declaration + } + + return when (adjustedDeclaration) { + is KtNamedFunction, is KtSecondaryConstructor -> { + createJavaMethod(adjustedDeclaration as KtFunction, targetClass) + } + is KtProperty -> { + createJavaField(adjustedDeclaration, targetClass) + } + is KtClass -> { + createJavaClass(adjustedDeclaration, targetClass) + } + else -> null + } + } + + if (config.isExtension || receiverClassDescriptor !is JavaClassDescriptor) return false + + val targetClass = DescriptorToSourceUtils.getSourceFromDescriptor(receiverClassDescriptor) as? PsiClass + if (targetClass == null || !targetClass.canRefactor()) return false + + val project = declaration.project + + val newJavaMember = convertToJava(targetClass) ?: return false + + val modifierList = newJavaMember.modifierList!! + if (newJavaMember is PsiMethod || newJavaMember is PsiClass) { + modifierList.setModifierProperty(PsiModifier.FINAL, false) + } + + val needStatic = when (callableInfo) { + is ClassWithPrimaryConstructorInfo -> with(callableInfo.classInfo) { + !inner && kind != ClassKind.ENUM_ENTRY && kind != ClassKind.ENUM_CLASS + } + else -> callableInfo.receiverTypeInfo.staticContextRequired + } + modifierList.setModifierProperty(PsiModifier.STATIC, needStatic) + + JavaCodeStyleManager.getInstance(project).shortenClassReferences(newJavaMember) + + val descriptor = OpenFileDescriptor(project, targetClass.containingFile.virtualFile) + val targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true)!! + + when (newJavaMember) { + is PsiMethod -> CreateFromUsageUtils.setupEditor(newJavaMember, targetEditor) + is PsiField -> targetEditor.caretModel.moveToOffset(newJavaMember.endOffset - 1) + is PsiClass -> { + val constructor = newJavaMember.constructors.firstOrNull() + val superStatement = constructor?.body?.statements?.firstOrNull() as? PsiExpressionStatement + val superCall = superStatement?.expression as? PsiMethodCallExpression + if (superCall != null) { + val lParen = superCall.argumentList.firstChild + targetEditor.caretModel.moveToOffset(lParen.endOffset) + } + else { + targetEditor.caretModel.moveToOffset(newJavaMember.startOffset) + } + } + } + targetEditor.scrollingModel.scrollToCaret(ScrollType.RELATIVE) + + return true + } + + private fun setupEditor(declaration: KtNamedDeclaration) { + if (declaration is KtProperty && !declaration.hasInitializer() && containingElement is KtBlockExpression) { + val defaultValueType = typeCandidates[callableInfo.returnTypeInfo]!!.firstOrNull()?.theType + val defaultValue = defaultValueType?.let { CodeInsightUtils.defaultInitializer(it) } ?: "null" + val initializer = declaration.setInitializer(KtPsiFactory(declaration).createExpression(defaultValue))!! + val range = initializer.textRange + containingFileEditor.selectionModel.setSelection(range.startOffset, range.endOffset) + containingFileEditor.caretModel.moveToOffset(range.endOffset) + return + } + if (declaration is KtSecondaryConstructor && !declaration.hasImplicitDelegationCall()) { + containingFileEditor.caretModel.moveToOffset(declaration.getDelegationCall().valueArgumentList!!.startOffset + 1) + return + } + setupEditorSelection(containingFileEditor, declaration) + } + + // build templates + fun buildAndRunTemplate(onFinish: () -> Unit) { + val declarationSkeleton = createDeclarationSkeleton() + val project = declarationSkeleton.project + val declarationPointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(declarationSkeleton) + + // build templates + PsiDocumentManager.getInstance(project).commitAllDocuments() + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(containingFileEditor.document) + + val caretModel = containingFileEditor.caretModel + caretModel.moveToOffset(jetFileToEdit.node.startOffset) + + val declaration = declarationPointer.element!! + + val declarationMarker = containingFileEditor.document.createRangeMarker(declaration.textRange) + + val builder = TemplateBuilderImpl(jetFileToEdit) + if (declaration is KtProperty) { + setupValVarTemplate(builder, declaration) + } + if (!skipReturnType) { + setupReturnTypeTemplate(builder, declaration) + } + + val parameterTypeExpressions = setupParameterTypeTemplates(builder, declaration.getValueParameters()) + + // 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, declaration) + + // 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.variables!! + if (variables.isNotEmpty()) { + val typeParametersVar = if (expression != null) variables.removeAt(0) else null + for (i in 0..(callableInfo.parameterInfos.size - 1)) { + Collections.swap(variables, i * 2, i * 2 + 1) + } + typeParametersVar?.let { variables.add(it) } + } + + // TODO: Disabled shortening names because it causes some tests fail. Refactor code to use automatic reference shortening + templateImpl.isToShortenLongNames = false + + // run the template + TemplateManager.getInstance(project).startTemplate(containingFileEditor, templateImpl, object : TemplateEditingAdapter() { + private fun finishTemplate(brokenOff: Boolean) { + try { + PsiDocumentManager.getInstance(project).commitDocument(containingFileEditor.document) + + dialogWithEditor?.close(DialogWrapper.OK_EXIT_CODE) + if (brokenOff && !ApplicationManager.getApplication().isUnitTestMode) return + + // file templates + val newDeclaration = PsiTreeUtil.findElementOfClassAtOffset(jetFileToEdit, + declarationMarker.startOffset, + declaration::class.java, + false) ?: return + + runWriteAction { + postprocessDeclaration(newDeclaration) + + // file templates + if (newDeclaration is KtNamedFunction || newDeclaration is KtSecondaryConstructor) { + setupDeclarationBody(newDeclaration as KtFunction) + } + + if (newDeclaration is KtProperty) { + newDeclaration.getter?.let { setupDeclarationBody(it) } + + if (newDeclaration.getter == null + && newDeclaration.initializer == null + && callableInfo is PropertyInfo + && callableInfo.withInitializer + && !callableInfo.isLateinitPreferred) { + newDeclaration.initializer = KtPsiFactory(newDeclaration).createExpression("TODO(\"initialize me\")") + } + } + + val callElement = config.originalElement as? KtCallElement + if (callElement != null) { + setupCallTypeArguments(callElement, expression?.currentTypeParameters ?: Collections.emptyList()) + } + + CodeStyleManager.getInstance(project).reformat(newDeclaration) + + // change short type names to fully qualified ones (to be shortened below) + setupTypeReferencesForShortening(newDeclaration, parameterTypeExpressions) + if (!transformToJavaMemberIfApplicable(newDeclaration)) { + elementsToShorten.add(newDeclaration) + setupEditor(newDeclaration) + } + } + } + finally { + declarationMarker.dispose() + finished = true + onFinish() + } + } + + override fun templateCancelled(template: Template?) { + finishTemplate(true) + } + + override fun templateFinished(template: Template?, brokenOff: Boolean) { + finishTemplate(brokenOff) + } + }) + } + + fun showDialogIfNeeded() { + if (!ApplicationManager.getApplication().isUnitTestMode && dialogWithEditor != null && !finished) { + dialogWithEditor.show() + } + } + } +} + +// TODO: Simplify and use formatter as much as possible +@Suppress("UNCHECKED_CAST") +internal fun placeDeclarationInContainer( + declaration: D, + container: PsiElement, + anchor: PsiElement, + fileToEdit: KtFile = container.containingFile as KtFile +): D { + val psiFactory = KtPsiFactory(container) + val newLine = psiFactory.createNewLine() + + fun calcNecessaryEmptyLines(decl: KtDeclaration, after: Boolean): Int { + var lineBreaksPresent = 0 + var neighbor: PsiElement? = null + + siblingsLoop@ + for (sibling in decl.siblings(forward = after, withItself = false)) { + when (sibling) { + is PsiWhiteSpace -> lineBreaksPresent += (sibling.text ?: "").count { it == '\n' } + else -> { + neighbor = sibling + break@siblingsLoop + } + } + } + + val neighborType = neighbor?.node?.elementType + val lineBreaksNeeded = when { + neighborType == KtTokens.LBRACE || neighborType == KtTokens.RBRACE -> 1 + neighbor is KtDeclaration && (neighbor !is KtProperty || decl !is KtProperty) -> 2 + else -> 1 + } + + return Math.max(lineBreaksNeeded - lineBreaksPresent, 0) + } + + val actualContainer = (container as? KtClassOrObject)?.getOrCreateBody() ?: container + + fun addDeclarationToClassOrObject(classOrObject: KtClassOrObject, + declaration: KtNamedDeclaration): KtNamedDeclaration { + val classBody = classOrObject.getOrCreateBody() + return if (declaration is KtNamedFunction) { + val neighbor = PsiTreeUtil.skipSiblingsBackward( + classBody.rBrace ?: classBody.lastChild!!, + PsiWhiteSpace::class.java + ) + classBody.addAfter(declaration, neighbor) as KtNamedDeclaration + } + else classBody.addAfter(declaration, classBody.lBrace!!) as KtNamedDeclaration + } + + + fun addNextToOriginalElementContainer(addBefore: Boolean): D { + val sibling = anchor.parentsWithSelf.first { it.parent == actualContainer } + return if (addBefore) { + actualContainer.addBefore(declaration, sibling) + } + else { + actualContainer.addAfter(declaration, sibling) + } as D + } + + val declarationInPlace = when { + declaration is KtPrimaryConstructor -> { + (container as KtClass).createPrimaryConstructorIfAbsent().replaced(declaration) as D + } + + actualContainer.isAncestor(anchor, true) -> { + val insertToBlock = container is KtBlockExpression + if (insertToBlock) { + val parent = container.parent + if (parent is KtFunctionLiteral) { + if (!parent.isMultiLine()) { + parent.addBefore(newLine, container) + parent.addAfter(newLine, container) + } + } + } + addNextToOriginalElementContainer(insertToBlock + || (declaration is KtProperty && actualContainer !is KtFile) + || declaration is KtTypeAlias) + } + + container is KtFile -> container.add(declaration) as D + + container is PsiClass -> { + if (declaration is KtSecondaryConstructor) { + val wrappingClass = psiFactory.createClass("class ${container.name} {\n}") + addDeclarationToClassOrObject(wrappingClass, declaration) + (fileToEdit.add(wrappingClass) as KtClass).declarations.first() as D + } + else { + fileToEdit.add(declaration) as D + } + } + + container is KtClassOrObject -> { + var sibling: PsiElement? = container.declarations.lastOrNull { it::class == declaration::class } + if (sibling == null && declaration is KtProperty) { + sibling = container.getBody()?.lBrace + } + + insertMember(null, container, declaration, sibling) + } + else -> throw AssertionError("Invalid containing element: ${container.text}") + } + + if (declaration !is KtPrimaryConstructor) { + val parent = declarationInPlace.parent + calcNecessaryEmptyLines(declarationInPlace, false).let { + if (it > 0) parent.addBefore(psiFactory.createNewLine(it), declarationInPlace) + } + calcNecessaryEmptyLines(declarationInPlace, true).let { + if (it > 0) parent.addAfter(psiFactory.createNewLine(it), declarationInPlace) + } + } + return declarationInPlace +} + +internal fun KtNamedDeclaration.getReturnTypeReference() = getReturnTypeReferences().singleOrNull() + +internal fun KtNamedDeclaration.getReturnTypeReferences(): List { + return when (this) { + is KtCallableDeclaration -> listOfNotNull(typeReference) + is KtClassOrObject -> superTypeListEntries.mapNotNull { it.typeReference } + else -> throw AssertionError("Unexpected declaration kind: $text") + } +} + +fun CallableBuilderConfiguration.createBuilder(): CallableBuilder = CallableBuilder(this) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt.172 new file mode 100644 index 00000000000..8e2258f286a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableInfo.kt.172 @@ -0,0 +1,281 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.callableBuilder + +import com.intellij.psi.PsiElement +import com.intellij.util.ArrayUtil +import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.ClassInfo +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.idea.util.getResolvableApproximations +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.scopes.LexicalScope +import org.jetbrains.kotlin.types.ErrorUtils +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.typeUtil.makeNotNullable +import java.util.* + +/** + * Represents a concrete type or a set of types yet to be inferred from an expression. + */ +abstract class TypeInfo(val variance: Variance) { + object Empty: TypeInfo(Variance.INVARIANT) { + override fun getPossibleTypes(builder: CallableBuilder): List = Collections.emptyList() + } + + class ByExpression(val expression: KtExpression, variance: Variance): TypeInfo(variance) { + override fun getPossibleNamesFromExpression(bindingContext: BindingContext): Array { + return KotlinNameSuggester.suggestNamesByExpressionOnly(expression, bindingContext, { true }).toTypedArray() + } + + override fun getPossibleTypes(builder: CallableBuilder): List = + expression.guessTypes( + context = builder.currentFileContext, + module = builder.currentFileModule, + pseudocode = builder.pseudocode + ).flatMap { it.getPossibleSupertypes(variance, builder) } + } + + class ByTypeReference(val typeReference: KtTypeReference, variance: Variance): TypeInfo(variance) { + override fun getPossibleTypes(builder: CallableBuilder): List = + builder.currentFileContext[BindingContext.TYPE, typeReference].getPossibleSupertypes(variance, builder) + } + + class ByType(val theType: KotlinType, variance: Variance): TypeInfo(variance) { + override fun getPossibleTypes(builder: CallableBuilder): List = + theType.getPossibleSupertypes(variance, builder) + } + + class ByReceiverType(variance: Variance): TypeInfo(variance) { + override fun getPossibleTypes(builder: CallableBuilder): List = + (builder.placement as CallablePlacement.WithReceiver).receiverTypeCandidate.theType.getPossibleSupertypes(variance, builder) + } + + class ByExplicitCandidateTypes(val types: List) : TypeInfo(Variance.INVARIANT) { + override fun getPossibleTypes(builder: CallableBuilder) = types + } + + abstract class DelegatingTypeInfo(val delegate: TypeInfo): TypeInfo(delegate.variance) { + override val substitutionsAllowed: Boolean = delegate.substitutionsAllowed + override fun getPossibleNamesFromExpression(bindingContext: BindingContext) = delegate.getPossibleNamesFromExpression(bindingContext) + override fun getPossibleTypes(builder: CallableBuilder): List = delegate.getPossibleTypes(builder) + } + + class NoSubstitutions(delegate: TypeInfo): DelegatingTypeInfo(delegate) { + override val substitutionsAllowed: Boolean = false + } + + class StaticContextRequired(delegate: TypeInfo): DelegatingTypeInfo(delegate) { + override val staticContextRequired: Boolean = true + } + + class OfThis(delegate: TypeInfo) : DelegatingTypeInfo(delegate) + + val isOfThis: Boolean + get() = when (this) { + is OfThis -> true + is DelegatingTypeInfo -> delegate.isOfThis + else -> false + } + + open val substitutionsAllowed: Boolean = true + open val staticContextRequired: Boolean = false + open fun getPossibleNamesFromExpression(bindingContext: BindingContext): Array = ArrayUtil.EMPTY_STRING_ARRAY + abstract fun getPossibleTypes(builder: CallableBuilder): List + + private fun getScopeForTypeApproximation(config: CallableBuilderConfiguration, placement: CallablePlacement?): LexicalScope? { + if (placement == null) return config.originalElement.getResolutionScope() + + val containingElement = when (placement) { + is CallablePlacement.NoReceiver -> { + placement.containingElement + } + is CallablePlacement.WithReceiver -> { + val receiverClassDescriptor = + placement.receiverTypeCandidate.theType.constructor.declarationDescriptor + val classDeclaration = receiverClassDescriptor?.let { DescriptorToSourceUtils.getSourceFromDescriptor(it) } + if (!config.isExtension && classDeclaration != null) classDeclaration else config.currentFile + } + } + return when (containingElement) { + is KtClassOrObject -> (containingElement.resolveToDescriptorIfAny() as? ClassDescriptorWithResolutionScopes)?.scopeForMemberDeclarationResolution + is KtBlockExpression -> (containingElement.statements.firstOrNull() ?: containingElement).getResolutionScope() + is KtElement -> containingElement.containingKtFile.getResolutionScope() + else -> null + } + } + + protected fun KotlinType?.getPossibleSupertypes(variance: Variance, callableBuilder: CallableBuilder): List { + if (this == null || ErrorUtils.containsErrorType(this)) { + return Collections.singletonList(callableBuilder.currentFileModule.builtIns.anyType) + } + val scope = getScopeForTypeApproximation(callableBuilder.config, callableBuilder.placement) + val approximations = getResolvableApproximations(scope, false, true) + return when (variance) { + Variance.IN_VARIANCE -> approximations.toList() + else -> listOf(approximations.firstOrNull() ?: this) + } + } +} + +fun TypeInfo(expressionOfType: KtExpression, variance: Variance): TypeInfo = TypeInfo.ByExpression(expressionOfType, variance) +fun TypeInfo(typeReference: KtTypeReference, variance: Variance): TypeInfo = TypeInfo.ByTypeReference(typeReference, variance) +fun TypeInfo(theType: KotlinType, variance: Variance): TypeInfo = TypeInfo.ByType(theType, variance) + +fun TypeInfo.noSubstitutions(): TypeInfo = (this as? TypeInfo.NoSubstitutions) ?: TypeInfo.NoSubstitutions(this) + +fun TypeInfo.forceNotNull(): TypeInfo { + class ForcedNotNull(delegate: TypeInfo): TypeInfo.DelegatingTypeInfo(delegate) { + override fun getPossibleTypes(builder: CallableBuilder): List = + super.getPossibleTypes(builder).map { it.makeNotNullable() } + } + + return (this as? ForcedNotNull) ?: ForcedNotNull(this) +} + +fun TypeInfo.ofThis() = TypeInfo.OfThis(this) + +/** + * Encapsulates information about a function parameter that is going to be created. + */ +class ParameterInfo( + val typeInfo: TypeInfo, + val nameSuggestions: List +) { + constructor(typeInfo: TypeInfo, preferredName: String? = null): this(typeInfo, listOfNotNull(preferredName)) +} + +enum class CallableKind { + FUNCTION, + CLASS_WITH_PRIMARY_CONSTRUCTOR, + CONSTRUCTOR, + PROPERTY +} + +abstract class CallableInfo ( + val name: String, + val receiverTypeInfo: TypeInfo, + val returnTypeInfo: TypeInfo, + val possibleContainers: List, + val typeParameterInfos: List, + val isAbstract: Boolean = false, + val isForCompanion: Boolean = false, + val modifierList: KtModifierList? = null +) { + abstract val kind: CallableKind + abstract val parameterInfos: List + + abstract fun copy(receiverTypeInfo: TypeInfo = this.receiverTypeInfo, + possibleContainers: List = this.possibleContainers, + isAbstract: Boolean = this.isAbstract): CallableInfo +} + +class FunctionInfo(name: String, + receiverTypeInfo: TypeInfo, + returnTypeInfo: TypeInfo, + possibleContainers: List = Collections.emptyList(), + override val parameterInfos: List = Collections.emptyList(), + typeParameterInfos: List = Collections.emptyList(), + val isOperator: Boolean = false, + val isInfix: Boolean = false, + isAbstract: Boolean = false, + isForCompanion: Boolean = false, + modifierList: KtModifierList? = null, + val preferEmptyBody: Boolean = false +) : CallableInfo(name, receiverTypeInfo, returnTypeInfo, possibleContainers, typeParameterInfos, isAbstract, isForCompanion, modifierList) { + override val kind: CallableKind get() = CallableKind.FUNCTION + + override fun copy(receiverTypeInfo: TypeInfo, possibleContainers: List, isAbstract: Boolean) = FunctionInfo( + name, + receiverTypeInfo, + returnTypeInfo, + possibleContainers, + parameterInfos, + typeParameterInfos, + isOperator, + isInfix, + isAbstract + ) +} + +class ClassWithPrimaryConstructorInfo( + val classInfo: ClassInfo, + expectedTypeInfo: TypeInfo, + modifierList: KtModifierList? = null +): CallableInfo( + classInfo.name, TypeInfo.Empty, expectedTypeInfo.forceNotNull(), Collections.emptyList(), classInfo.typeArguments, false, modifierList = modifierList +) { + override val kind: CallableKind get() = CallableKind.CLASS_WITH_PRIMARY_CONSTRUCTOR + override val parameterInfos: List get() = classInfo.parameterInfos + + override fun copy(receiverTypeInfo: TypeInfo, possibleContainers: List, isAbstract: Boolean) = throw UnsupportedOperationException() +} + +class ConstructorInfo( + override val parameterInfos: List, + val targetClass: PsiElement, + val isPrimary: Boolean = false, + modifierList: KtModifierList? = null, + val withBody: Boolean = false +): CallableInfo("", TypeInfo.Empty, TypeInfo.Empty, Collections.emptyList(), Collections.emptyList(), false, modifierList = modifierList) { + override val kind: CallableKind get() = CallableKind.CONSTRUCTOR + + override fun copy(receiverTypeInfo: TypeInfo, possibleContainers: List, isAbstract: Boolean) = throw UnsupportedOperationException() +} + +class PropertyInfo(name: String, + receiverTypeInfo: TypeInfo, + returnTypeInfo: TypeInfo, + val writable: Boolean, + possibleContainers: List = Collections.emptyList(), + typeParameterInfos: List = Collections.emptyList(), + isAbstract: Boolean = false, + val isLateinitPreferred: Boolean = false, + isForCompanion: Boolean = false, + modifierList: KtModifierList? = null, + val withInitializer: Boolean = false +) : CallableInfo(name, receiverTypeInfo, returnTypeInfo, possibleContainers, typeParameterInfos, isAbstract, isForCompanion, modifierList) { + override val kind: CallableKind get() = CallableKind.PROPERTY + override val parameterInfos: List get() = Collections.emptyList() + + override fun copy(receiverTypeInfo: TypeInfo, possibleContainers: List, isAbstract: Boolean) = + copyProperty(receiverTypeInfo, possibleContainers, isAbstract) + + fun copyProperty( + receiverTypeInfo: TypeInfo = this.receiverTypeInfo, + possibleContainers: List = this.possibleContainers, + isAbstract: Boolean = this.isAbstract, + isLateinitPreferred: Boolean = this.isLateinitPreferred + ) = PropertyInfo( + name, + receiverTypeInfo, + returnTypeInfo, + writable, + possibleContainers, + typeParameterInfos, + isAbstract, + isLateinitPreferred, + isForCompanion, + modifierList, + withInitializer + ) +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateBinaryOperationActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateBinaryOperationActionFactory.kt.172 new file mode 100644 index 00000000000..56353834c67 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateBinaryOperationActionFactory.kt.172 @@ -0,0 +1,62 @@ +/* + * Copyright 2010-2017 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.project.builtIns +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.* +import org.jetbrains.kotlin.lexer.KtToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.expressions.OperatorConventions +import java.util.* + +object CreateBinaryOperationActionFactory : CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtBinaryExpression? { + return diagnostic.psiElement.parent as? KtBinaryExpression + } + + override fun createCallableInfo(element: KtBinaryExpression, diagnostic: Diagnostic): CallableInfo? { + val token = element.operationToken as KtToken + val operationName = when (token) { + KtTokens.IDENTIFIER -> element.operationReference.getReferencedName() + else -> OperatorConventions.getNameForOperationSymbol(token, false, true)?.asString() + } ?: return null + val inOperation = token in OperatorConventions.IN_OPERATIONS + val comparisonOperation = token in OperatorConventions.COMPARISON_OPERATIONS + + val leftExpr = element.left ?: return null + val rightExpr = element.right ?: return null + + val receiverExpr = if (inOperation) rightExpr else leftExpr + val argumentExpr = if (inOperation) leftExpr else rightExpr + + val builtIns = element.builtIns + val receiverType = TypeInfo(receiverExpr, Variance.IN_VARIANCE) + val returnType = when { + inOperation -> TypeInfo.ByType(builtIns.booleanType, Variance.INVARIANT).noSubstitutions() + comparisonOperation -> TypeInfo.ByType(builtIns.intType, Variance.INVARIANT).noSubstitutions() + else -> TypeInfo(element, Variance.OUT_VARIANCE) + } + val parameters = Collections.singletonList(ParameterInfo(TypeInfo(argumentExpr, Variance.IN_VARIANCE))) + val isOperator = token != KtTokens.IDENTIFIER + return FunctionInfo(operationName, receiverType, returnType, parameterInfos = parameters, + isOperator = isOperator, + isInfix = !isOperator) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromCallActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromCallActionFactory.kt.172 new file mode 100644 index 00000000000..c10e2a81807 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromCallActionFactory.kt.172 @@ -0,0 +1,342 @@ +/* + * Copyright 2010-2016 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ConstructorDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult +import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.* +import org.jetbrains.kotlin.idea.refactoring.canRefactor +import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers +import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass +import org.jetbrains.kotlin.idea.util.isAbstract +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets +import org.jetbrains.kotlin.resolve.calls.callUtil.getCall +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.descriptorUtil.classValueType +import org.jetbrains.kotlin.resolve.scopes.receivers.* +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf +import java.lang.AssertionError +import java.util.* + +sealed class CreateCallableFromCallActionFactory( + extensionsEnabled: Boolean = true +) : CreateCallableMemberFromUsageFactory(extensionsEnabled) { + protected abstract fun doCreateCallableInfo( + expression: E, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ): CallableInfo? + + protected fun getExpressionOfInterest(diagnostic: Diagnostic): KtExpression? { + val diagElement = diagnostic.psiElement + if (PsiTreeUtil.getParentOfType( + diagElement, + KtTypeReference::class.java, KtAnnotationEntry::class.java, KtImportDirective::class.java + ) != null) return null + + return when (diagnostic.factory) { + in Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS, Errors.EXPRESSION_EXPECTED_PACKAGE_FOUND -> { + val parent = diagElement.parent + if (parent is KtCallExpression && parent.calleeExpression == diagElement) parent else diagElement + } + + Errors.NO_VALUE_FOR_PARAMETER, + Errors.TOO_MANY_ARGUMENTS, + Errors.NONE_APPLICABLE -> diagElement.getNonStrictParentOfType() + + Errors.TYPE_MISMATCH -> (diagElement.parent as? KtValueArgument)?.getStrictParentOfType() + + else -> throw AssertionError("Unexpected diagnostic: ${diagnostic.factory}") + } as? KtExpression + } + + override fun createCallableInfo(element: E, diagnostic: Diagnostic): CallableInfo? { + val project = element.project + + val calleeExpr = when (element) { + is KtCallExpression -> element.calleeExpression + is KtSimpleNameExpression -> element + else -> null + } as? KtSimpleNameExpression ?: return null + + if (calleeExpr.getReferencedNameElementType() != KtTokens.IDENTIFIER) return null + + val analysisResult = calleeExpr.analyzeAndGetResult() + val receiver = element.getCall(analysisResult.bindingContext)?.explicitReceiver + val receiverType = getReceiverTypeInfo(analysisResult.bindingContext, project, receiver) ?: return null + + val possibleContainers = + if (receiverType is TypeInfo.Empty) { + val containers = with(element.getQualifiedExpressionForSelectorOrThis().getExtractionContainers()) { + if (element is KtCallExpression) this else filter { it is KtClassBody || it is KtFile } + } + if (containers.isNotEmpty()) containers else return null + } + else Collections.emptyList() + + return doCreateCallableInfo(element, analysisResult, calleeExpr.getReferencedName(), receiverType, possibleContainers) + } + + private fun getReceiverTypeInfo(context: BindingContext, project: Project, receiver: Receiver?): TypeInfo? { + return when (receiver) { + null -> TypeInfo.Empty + is Qualifier -> { + val qualifierType = context.getType(receiver.expression) + if (qualifierType != null) return TypeInfo(qualifierType, Variance.IN_VARIANCE) + + if (receiver !is ClassQualifier) return null + val classifierType = receiver.descriptor.classValueType + if (classifierType != null) return TypeInfo(classifierType, Variance.IN_VARIANCE) + + val javaClassifier = receiver.descriptor as? JavaClassDescriptor ?: return null + val javaClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, javaClassifier) as? PsiClass + if (javaClass == null || !javaClass.canRefactor()) return null + TypeInfo.StaticContextRequired(TypeInfo(javaClassifier.defaultType, Variance.IN_VARIANCE)) + } + is ReceiverValue -> TypeInfo(receiver.type, Variance.IN_VARIANCE) + else -> throw AssertionError("Unexpected receiver: $receiver") + } + } + + protected fun getAbstractCallableInfo(mainCallable: CallableInfo, originalExpression: KtExpression): CallableInfo? { + val receiverTypeInfo: TypeInfo + val receiverType: KotlinType + + val originalReceiverTypeInfo = mainCallable.receiverTypeInfo + if (originalReceiverTypeInfo != TypeInfo.Empty) { + if (originalReceiverTypeInfo !is TypeInfo.ByType) return null + receiverTypeInfo = originalReceiverTypeInfo + receiverType = receiverTypeInfo.theType + } + else { + val containingClass = originalExpression.getStrictParentOfType() as? KtClass ?: return null + if (containingClass is KtEnumEntry) return null + receiverType = (containingClass.unsafeResolveToDescriptor() as ClassDescriptor).defaultType + receiverTypeInfo = TypeInfo(receiverType, Variance.IN_VARIANCE).ofThis() + } + + if (!receiverType.isAbstract() && TypeUtils.getAllSupertypes(receiverType).all { !it.isAbstract() }) return null + + return mainCallable.copy(receiverTypeInfo = receiverTypeInfo, possibleContainers = emptyList(), isAbstract = true) + } + + protected fun getCallableWithReceiverInsideExtension( + mainCallable: CallableInfo, + originalExpression: KtExpression, + context: BindingContext, + receiverType: TypeInfo + ): CallableInfo? { + if (receiverType != TypeInfo.Empty) return null + val callable = (originalExpression.getParentOfTypeAndBranch { bodyExpression } + ?: originalExpression.getParentOfTypeAndBranches { listOf(getter, setter) }) + ?: return null + if (callable !is KtFunctionLiteral && callable.receiverTypeReference == null) return null + val callableDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, callable] as? CallableDescriptor ?: return null + val extensionReceiverType = callableDescriptor.extensionReceiverParameter?.type ?: return null + val newReceiverTypeInfo = TypeInfo(extensionReceiverType, Variance.IN_VARIANCE) + return mainCallable.copy(receiverTypeInfo = newReceiverTypeInfo, possibleContainers = emptyList()) + } + + sealed class Property: CreateCallableFromCallActionFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtSimpleNameExpression? { + val refExpr = getExpressionOfInterest(diagnostic) as? KtNameReferenceExpression ?: return null + if (refExpr.getParentOfTypeAndBranch { callableReference } != null) return null + return refExpr + } + + override fun doCreateCallableInfo( + expression: KtSimpleNameExpression, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ): CallableInfo? { + val fullCallExpr = expression.getQualifiedExpressionForSelectorOrThis() + val varExpected = fullCallExpr.getAssignmentByLHS() != null + val expressionForTypeGuess = fullCallExpr.getExpressionForTypeGuess() + val returnTypes = expressionForTypeGuess.guessTypes(analysisResult.bindingContext, analysisResult.moduleDescriptor) + val returnTypeInfo = TypeInfo(expressionForTypeGuess, if (varExpected) Variance.INVARIANT else Variance.OUT_VARIANCE) + val canBeLateinit = + varExpected + && returnTypes.any { !it.isMarkedNullable && !KotlinBuiltIns.isPrimitiveType(it) } + && fullCallExpr.parents.firstOrNull { it is KtDeclarationWithBody || it is KtClassInitializer } is KtDeclarationWithBody + return PropertyInfo(name, receiverType, returnTypeInfo, varExpected, possibleContainers, isLateinitPreferred = canBeLateinit) + } + + object Default : Property() { + override fun doCreateCallableInfo( + expression: KtSimpleNameExpression, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ): CallableInfo? { + return super.doCreateCallableInfo( + expression, + analysisResult, + name, + receiverType, + possibleContainers.filterNot { it is KtClassBody && (it.parent as KtClassOrObject).isInterfaceClass() } + ) + } + } + + object Abstract : Property() { + override fun doCreateCallableInfo( + expression: KtSimpleNameExpression, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ) = super.doCreateCallableInfo(expression, analysisResult, name, receiverType, possibleContainers)?.let { + getAbstractCallableInfo(it, expression) + } + } + + object ByImplicitExtensionReceiver : Property() { + override fun doCreateCallableInfo( + expression: KtSimpleNameExpression, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ) = super.doCreateCallableInfo(expression, analysisResult, name, receiverType, possibleContainers)?.let { + ByImplicitExtensionReceiver.getCallableWithReceiverInsideExtension(it, expression, analysisResult.bindingContext, receiverType) + } + } + } + + sealed class Function: CreateCallableFromCallActionFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtCallExpression? { + return getExpressionOfInterest(diagnostic) as? KtCallExpression + } + + override fun doCreateCallableInfo( + expression: KtCallExpression, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ): CallableInfo? { + val parameters = expression.getParameterInfos() + val typeParameters = expression.getTypeInfoForTypeArguments() + val fullCallExpression = expression.getQualifiedExpressionForSelectorOrThis() + val expectedType = fullCallExpression.guessTypes(analysisResult.bindingContext, analysisResult.moduleDescriptor).singleOrNull() + val returnType = if (expectedType != null) { + TypeInfo(expectedType, Variance.OUT_VARIANCE) + } else { + TypeInfo(fullCallExpression, Variance.OUT_VARIANCE) + } + return FunctionInfo(name, receiverType, returnType, possibleContainers, parameters, typeParameters) + } + + object Default : Function() + + object Abstract : Function() { + override fun doCreateCallableInfo( + expression: KtCallExpression, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ) = super.doCreateCallableInfo(expression, analysisResult, name, receiverType, possibleContainers)?.let { + getAbstractCallableInfo(it, expression) + } + } + + object ByImplicitExtensionReceiver : Function() { + override fun doCreateCallableInfo( + expression: KtCallExpression, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ) = super.doCreateCallableInfo(expression, analysisResult, name, receiverType, possibleContainers)?.let { + getCallableWithReceiverInsideExtension(it, expression, analysisResult.bindingContext, receiverType) + } + } + } + + object Constructor: CreateCallableFromCallActionFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtCallExpression? { + return getExpressionOfInterest(diagnostic) as? KtCallExpression + } + + override fun doCreateCallableInfo( + expression: KtCallExpression, + analysisResult: AnalysisResult, + name: String, + receiverType: TypeInfo, + possibleContainers: List + ): CallableInfo? { + if (expression.typeArguments.isNotEmpty()) return null + + val classDescriptor = expression + .calleeExpression + ?.getReferenceTargets(analysisResult.bindingContext) + ?.mapNotNull { (it as? ConstructorDescriptor)?.containingDeclaration } + ?.distinct() + ?.singleOrNull() as? ClassDescriptor + val klass = classDescriptor?.source?.getPsi() + if ((klass !is KtClass && klass !is PsiClass) || !klass.canRefactor()) return null + + val expectedType = analysisResult.bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expression.getQualifiedExpressionForSelectorOrThis()] + ?: classDescriptor.builtIns.nullableAnyType + if (!classDescriptor.defaultType.isSubtypeOf(expectedType)) return null + + val parameters = expression.getParameterInfos() + + return ConstructorInfo(parameters, klass) + } + } + + companion object { + val FUNCTIONS = arrayOf(Function.Default, + Function.Abstract, + Function.ByImplicitExtensionReceiver, + Constructor) + val INSTANCES = arrayOf(Function.Default, + Function.Abstract, + Function.ByImplicitExtensionReceiver, + Constructor, + Property.Default, + Property.Abstract, + Property.ByImplicitExtensionReceiver) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt.172 new file mode 100644 index 00000000000..a70222f7991 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt.172 @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.psi.KtDestructuringDeclaration +import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver +import org.jetbrains.kotlin.types.Variance + +object CreateComponentFunctionActionFactory : CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtDestructuringDeclaration? { + QuickFixUtil.getParentElementOfType(diagnostic, KtDestructuringDeclaration::class.java)?.let { return it } + return QuickFixUtil.getParentElementOfType(diagnostic, KtForExpression::class.java)?.destructuringDeclaration + } + + override fun createCallableInfo(element: KtDestructuringDeclaration, diagnostic: Diagnostic): CallableInfo? { + val diagnosticWithParameters = Errors.COMPONENT_FUNCTION_MISSING.cast(diagnostic) + + val name = diagnosticWithParameters.a + if (!DataClassDescriptorResolver.isComponentLike(name)) return null + + val componentNumber = DataClassDescriptorResolver.getComponentIndex(name.asString()) - 1 + + val targetType = diagnosticWithParameters.b + val targetClassDescriptor = targetType.constructor.declarationDescriptor as? ClassDescriptor + if (targetClassDescriptor != null && targetClassDescriptor.isData) return null + + val ownerTypeInfo = TypeInfo(targetType, Variance.IN_VARIANCE) + val entries = element.entries + + val entry = entries[componentNumber] + val returnTypeInfo = TypeInfo(entry, Variance.OUT_VARIANCE) + + return FunctionInfo(name.identifier, ownerTypeInfo, returnTypeInfo, isOperator = true) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateGetFunctionActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateGetFunctionActionFactory.kt.172 new file mode 100644 index 00000000000..e3d66dd7d1b --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateGetFunctionActionFactory.kt.172 @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.ParameterInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.psi.KtArrayAccessExpression +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.util.OperatorNameConventions +import java.util.* + +object CreateGetFunctionActionFactory : CreateGetSetFunctionActionFactory(isGet = true) { + override fun createCallableInfo(element: KtArrayAccessExpression, diagnostic: Diagnostic): CallableInfo? { + val arrayExpr = element.arrayExpression ?: return null + + val arrayType = TypeInfo(arrayExpr, Variance.IN_VARIANCE) + val parameters = element.indexExpressions.map { ParameterInfo(TypeInfo(it, Variance.IN_VARIANCE)) } + val returnType = TypeInfo(element, Variance.OUT_VARIANCE) + return FunctionInfo( + OperatorNameConventions.GET.asString(), arrayType, returnType, Collections.emptyList(), parameters, isOperator = true + ) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateHasNextFunctionActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateHasNextFunctionActionFactory.kt.172 new file mode 100644 index 00000000000..29b7392bb7a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateHasNextFunctionActionFactory.kt.172 @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil +import org.jetbrains.kotlin.idea.project.builtIns +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.util.OperatorNameConventions + +object CreateHasNextFunctionActionFactory : CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtForExpression? { + return QuickFixUtil.getParentElementOfType(diagnostic, KtForExpression::class.java) + } + + override fun createCallableInfo(element: KtForExpression, diagnostic: Diagnostic): CallableInfo? { + val diagnosticWithParameters = + DiagnosticFactory.cast(diagnostic, Errors.HAS_NEXT_MISSING, Errors.HAS_NEXT_FUNCTION_NONE_APPLICABLE) + val ownerType = TypeInfo(diagnosticWithParameters.a, Variance.IN_VARIANCE) + val returnType = TypeInfo(element.builtIns.booleanType, Variance.OUT_VARIANCE) + return FunctionInfo(OperatorNameConventions.HAS_NEXT.asString(), ownerType, returnType, isOperator = true) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateInvokeFunctionActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateInvokeFunctionActionFactory.kt.172 new file mode 100644 index 00000000000..561534f2e19 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateInvokeFunctionActionFactory.kt.172 @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.project.builtIns +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.ParameterInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.isError +import org.jetbrains.kotlin.util.OperatorNameConventions + +object CreateInvokeFunctionActionFactory : CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtCallExpression? { + return diagnostic.psiElement.parent as? KtCallExpression + } + + override fun createCallableInfo(element: KtCallExpression, diagnostic: Diagnostic): CallableInfo? { + val expectedType = Errors.FUNCTION_EXPECTED.cast(diagnostic).b + if (expectedType.isError) return null + + val receiverType = TypeInfo(expectedType, Variance.IN_VARIANCE) + + val anyType = element.builtIns.nullableAnyType + val parameters = element.valueArguments.map { + ParameterInfo( + it.getArgumentExpression()?.let { TypeInfo(it, Variance.IN_VARIANCE) } ?: TypeInfo(anyType, Variance.IN_VARIANCE), + it.getArgumentName()?.referenceExpression?.getReferencedName() + ) + } + + val returnType = TypeInfo(element, Variance.OUT_VARIANCE) + return FunctionInfo(OperatorNameConventions.INVOKE.asString(), receiverType, returnType, parameterInfos = parameters, isOperator = true) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateIteratorFunctionActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateIteratorFunctionActionFactory.kt.172 new file mode 100644 index 00000000000..63ef070a873 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateIteratorFunctionActionFactory.kt.172 @@ -0,0 +1,62 @@ +/* + * Copyright 2010-2016 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks +import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.guessTypes +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.types.KotlinTypeFactory +import org.jetbrains.kotlin.types.TypeProjectionImpl +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.util.OperatorNameConventions +import java.util.* + +object CreateIteratorFunctionActionFactory : CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtForExpression? { + return QuickFixUtil.getParentElementOfType(diagnostic, KtForExpression::class.java) + } + + override fun createCallableInfo(element: KtForExpression, diagnostic: Diagnostic): CallableInfo? { + val file = diagnostic.psiFile as? KtFile ?: return null + val iterableExpr = element.loopRange ?: return null + val variableExpr: KtExpression = ((element.loopParameter ?: element.destructuringDeclaration) ?: return null) as KtExpression + val iterableType = TypeInfo(iterableExpr, Variance.IN_VARIANCE) + + val (bindingContext, moduleDescriptor) = file.analyzeWithAllCompilerChecks() + + val returnJetType = moduleDescriptor.builtIns.iterator.defaultType + val returnJetTypeParameterTypes = variableExpr.guessTypes(bindingContext, moduleDescriptor) + if (returnJetTypeParameterTypes.size != 1) return null + + val returnJetTypeParameterType = TypeProjectionImpl(returnJetTypeParameterTypes[0]) + val returnJetTypeArguments = Collections.singletonList(returnJetTypeParameterType) + val newReturnJetType = KotlinTypeFactory.simpleTypeWithNonTrivialMemberScope(returnJetType.annotations, + returnJetType.constructor, + returnJetTypeArguments, + returnJetType.isMarkedNullable, + returnJetType.memberScope) + val returnType = TypeInfo(newReturnJetType, Variance.OUT_VARIANCE) + return FunctionInfo(OperatorNameConventions.ITERATOR.asString(), iterableType, returnType, isOperator = true) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateNextFunctionActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateNextFunctionActionFactory.kt.172 new file mode 100644 index 00000000000..25f8ad6447a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateNextFunctionActionFactory.kt.172 @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.util.OperatorNameConventions + +object CreateNextFunctionActionFactory : CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtForExpression? { + return QuickFixUtil.getParentElementOfType(diagnostic, KtForExpression::class.java) + } + + override fun createCallableInfo(element: KtForExpression, diagnostic: Diagnostic): CallableInfo? { + val diagnosticWithParameters = DiagnosticFactory.cast(diagnostic, Errors.NEXT_MISSING, Errors.NEXT_NONE_APPLICABLE) + val ownerType = TypeInfo(diagnosticWithParameters.a, Variance.IN_VARIANCE) + + val variableExpr = element.loopParameter ?: element.destructuringDeclaration ?: return null + val returnType = TypeInfo(variableExpr as KtExpression, Variance.OUT_VARIANCE) + return FunctionInfo(OperatorNameConventions.NEXT.asString(), ownerType, returnType, isOperator = true) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreatePropertyDelegateAccessorsActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreatePropertyDelegateAccessorsActionFactory.kt.172 new file mode 100644 index 00000000000..9107e5ba371 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreatePropertyDelegateAccessorsActionFactory.kt.172 @@ -0,0 +1,97 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import com.intellij.util.SmartList +import org.jetbrains.kotlin.builtins.ReflectionTypes +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.VariableAccessorDescriptor +import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors +import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.project.languageVersionSettings +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.ParameterInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.util.OperatorNameConventions + +object CreatePropertyDelegateAccessorsActionFactory : CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtExpression? { + return diagnostic.psiElement as? KtExpression + } + + override fun extractFixData(element: KtExpression, diagnostic: Diagnostic): List { + val context = element.analyze() + + fun isApplicableForAccessor(accessor: VariableAccessorDescriptor?): Boolean = + accessor != null && context[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, accessor] == null + + val property = element.getNonStrictParentOfType() ?: return emptyList() + val propertyDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, property] as? VariableDescriptorWithAccessors + ?: return emptyList() + + if (propertyDescriptor is LocalVariableDescriptor + && !element.languageVersionSettings.supportsFeature(LanguageFeature.LocalDelegatedProperties)) { + return emptyList() + } + + val propertyReceiver = propertyDescriptor.extensionReceiverParameter ?: propertyDescriptor.dispatchReceiverParameter + val propertyType = propertyDescriptor.type + + val accessorReceiverType = TypeInfo(element, Variance.IN_VARIANCE) + val builtIns = propertyDescriptor.builtIns + val thisRefParam = ParameterInfo(TypeInfo(propertyReceiver?.type ?: builtIns.nullableNothingType, Variance.IN_VARIANCE)) + val kPropertyStarType = ReflectionTypes.createKPropertyStarType(propertyDescriptor.module) ?: return emptyList() + val metadataParam = ParameterInfo(TypeInfo(kPropertyStarType, Variance.IN_VARIANCE), "property") + + val callableInfos = SmartList() + + if (isApplicableForAccessor(propertyDescriptor.getter)) { + val getterInfo = FunctionInfo( + name = OperatorNameConventions.GET_VALUE.asString(), + receiverTypeInfo = accessorReceiverType, + returnTypeInfo = TypeInfo(propertyType, Variance.OUT_VARIANCE), + parameterInfos = listOf(thisRefParam, metadataParam), + isOperator = true + ) + callableInfos.add(getterInfo) + } + + if (propertyDescriptor.isVar && isApplicableForAccessor(propertyDescriptor.setter)) { + val newValueParam = ParameterInfo(TypeInfo(propertyType, Variance.IN_VARIANCE)) + val setterInfo = FunctionInfo( + name = OperatorNameConventions.SET_VALUE.asString(), + receiverTypeInfo = accessorReceiverType, + returnTypeInfo = TypeInfo(builtIns.unitType, Variance.OUT_VARIANCE), + parameterInfos = listOf(thisRefParam, metadataParam, newValueParam), + isOperator = true + ) + callableInfos.add(setterInfo) + } + + return callableInfos + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateSetFunctionActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateSetFunctionActionFactory.kt.172 new file mode 100644 index 00000000000..fdb05288c50 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateSetFunctionActionFactory.kt.172 @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil +import org.jetbrains.kotlin.idea.project.builtIns +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.ParameterInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.types.ErrorUtils +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.expressions.OperatorConventions +import org.jetbrains.kotlin.util.OperatorNameConventions +import java.util.* + +object CreateSetFunctionActionFactory : CreateGetSetFunctionActionFactory(isGet = false) { + override fun createCallableInfo(element: KtArrayAccessExpression, diagnostic: Diagnostic): CallableInfo? { + val arrayExpr = element.arrayExpression ?: return null + val arrayType = TypeInfo(arrayExpr, Variance.IN_VARIANCE) + + val builtIns = element.builtIns + + val parameters = element.indexExpressions.mapTo(ArrayList()) { + ParameterInfo(TypeInfo(it, Variance.IN_VARIANCE)) + } + val assignmentExpr = QuickFixUtil.getParentElementOfType(diagnostic, KtOperationExpression::class.java) ?: return null + val valType = when (assignmentExpr) { + is KtBinaryExpression -> { + TypeInfo(assignmentExpr.right ?: return null, Variance.IN_VARIANCE) + } + is KtUnaryExpression -> { + if (assignmentExpr.operationToken !in OperatorConventions.INCREMENT_OPERATIONS) return null + + val rhsType = assignmentExpr.resolveToCall()?.resultingDescriptor?.returnType + TypeInfo(if (rhsType == null || ErrorUtils.containsErrorType(rhsType)) builtIns.anyType else rhsType, Variance.IN_VARIANCE) + } + else -> return null + } + parameters.add(ParameterInfo(valType, "value")) + + val returnType = TypeInfo(builtIns.unitType, Variance.OUT_VARIANCE) + return FunctionInfo( + OperatorNameConventions.SET.asString(), arrayType, returnType, Collections.emptyList(), parameters, isOperator = true + ) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateUnaryOperationActionFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateUnaryOperationActionFactory.kt.172 new file mode 100644 index 00000000000..cdee9f26e66 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateUnaryOperationActionFactory.kt.172 @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.quickfix.createFromUsage.createCallable + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.FunctionInfo +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.kotlin.lexer.KtToken +import org.jetbrains.kotlin.psi.KtUnaryExpression +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.expressions.OperatorConventions + +object CreateUnaryOperationActionFactory: CreateCallableMemberFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtUnaryExpression? { + return diagnostic.psiElement.parent as? KtUnaryExpression + } + + override fun createCallableInfo(element: KtUnaryExpression, diagnostic: Diagnostic): CallableInfo? { + val token = element.operationToken as KtToken + val operationName = OperatorConventions.getNameForOperationSymbol(token, true, false) ?: return null + val incDec = token in OperatorConventions.INCREMENT_OPERATIONS + + val receiverExpr = element.baseExpression ?: return null + + val receiverType = TypeInfo(receiverExpr, Variance.IN_VARIANCE) + val returnType = if (incDec) TypeInfo.ByReceiverType(Variance.OUT_VARIANCE) else TypeInfo(element, Variance.OUT_VARIANCE) + return FunctionInfo(operationName.asString(), receiverType, returnType, isOperator = true) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt.172 new file mode 100644 index 00000000000..20656a3e3e3 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt.172 @@ -0,0 +1,390 @@ +/* + * Copyright 2010-2017 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.kotlin.idea.quickfix.crossLanguage + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.codeInsight.intention.QuickFixFactory +import com.intellij.lang.jvm.* +import com.intellij.lang.jvm.actions.* +import com.intellij.openapi.project.Project +import com.intellij.psi.* +import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl +import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade +import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration +import org.jetbrains.kotlin.asJava.toLightMethods +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.SourceElement +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.core.appendModifier +import org.jetbrains.kotlin.idea.quickfix.AddModifierFix +import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.* +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable.CreateCallableFromUsageFix +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.components.TypeUsage +import org.jetbrains.kotlin.load.java.lazy.JavaResolverComponents +import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext +import org.jetbrains.kotlin.load.java.lazy.TypeParameterResolver +import org.jetbrains.kotlin.load.java.lazy.child +import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaTypeParameterDescriptor +import org.jetbrains.kotlin.load.java.lazy.types.JavaTypeAttributes +import org.jetbrains.kotlin.load.java.lazy.types.JavaTypeResolver +import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter +import org.jetbrains.kotlin.load.java.structure.impl.JavaTypeImpl +import org.jetbrains.kotlin.load.java.structure.impl.JavaTypeParameterImpl +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.annotations.JVM_FIELD_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.annotations.JVM_STATIC_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.typeUtil.supertypes + +class KotlinElementActionsFactory : JvmElementActionsFactory() { + companion object { + val javaPsiModifiersMapping = mapOf( + JvmModifier.PRIVATE to KtTokens.PRIVATE_KEYWORD, + JvmModifier.PUBLIC to KtTokens.PUBLIC_KEYWORD, + JvmModifier.PROTECTED to KtTokens.PUBLIC_KEYWORD, + JvmModifier.ABSTRACT to KtTokens.ABSTRACT_KEYWORD + ) + } + + private class FakeExpressionFromParameter(private val psiParam: PsiParameter) : PsiReferenceExpressionImpl() { + override fun getText(): String = psiParam.name!! + override fun getProject(): Project = psiParam.project + override fun getParent(): PsiElement = psiParam.parent + override fun getType(): PsiType? = psiParam.type + override fun isValid(): Boolean = true + override fun getContainingFile(): PsiFile = psiParam.containingFile + override fun getReferenceName(): String? = psiParam.name + override fun resolve(): PsiElement? = psiParam + } + + private class ModifierBuilder( + private val targetContainer: KtElement, + private val allowJvmStatic: Boolean = true + ) { + private val psiFactory = KtPsiFactory(targetContainer.project) + + val modifierList = psiFactory.createEmptyModifierList() + + private fun JvmModifier.transformAndAppend(): Boolean { + javaPsiModifiersMapping[this]?.let { + modifierList.appendModifier(it) + return true + } + + when (this) { + JvmModifier.STATIC -> { + if (allowJvmStatic && targetContainer is KtClassOrObject) { + addAnnotation(JVM_STATIC_ANNOTATION_FQ_NAME) + } + } + JvmModifier.ABSTRACT -> modifierList.appendModifier(KtTokens.ABSTRACT_KEYWORD) + JvmModifier.FINAL -> modifierList.appendModifier(KtTokens.FINAL_KEYWORD) + else -> return false + } + + return true + } + + var isValid = true + private set + + fun addJvmModifier(modifier: JvmModifier) { + isValid = isValid && modifier.transformAndAppend() + } + + fun addJvmModifiers(modifiers: Iterable) { + modifiers.forEach { addJvmModifier(it) } + } + + fun addAnnotation(fqName: FqName) { + if (!isValid) return + modifierList.add(psiFactory.createAnnotationEntry("@${fqName.asString()}")) + } + } + + class CreatePropertyFix( + private val targetClass: JvmClass, + contextElement: KtElement, + propertyInfo: PropertyInfo + ) : CreateCallableFromUsageFix(contextElement, listOf(propertyInfo)) { + override fun getFamilyName() = "Add property" + override fun getText(): String { + val info = callableInfos.first() as PropertyInfo + return buildString { + append("Add '") + if (info.isLateinitPreferred) { + append("lateinit ") + } + append(if (info.writable) "var" else "val") + append("' property '${info.name}' to '${targetClass.name}'") + } + } + } + + private fun JvmClass.toKtClassOrFile(): KtElement? { + val psi = sourceElement + return when (psi) { + is KtClassOrObject -> psi + is KtLightClassForSourceDeclaration -> psi.kotlinOrigin + is KtLightClassForFacade -> psi.files.firstOrNull() + else -> null + } + } + + private inline fun JvmElement.toKtElement() = sourceElement?.unwrapped as? T + + private fun fakeParametersExpressions(parameters: List, project: Project): Array? = + when { + parameters.isEmpty() -> emptyArray() + else -> JavaPsiFacade + .getElementFactory(project) + .createParameterList( + parameters.map { it.name }.toTypedArray(), + parameters.map { it.type as? PsiType ?: return null }.toTypedArray() + ) + .parameters + .map(::FakeExpressionFromParameter) + .toTypedArray() + } + + private fun PsiType.collectTypeParameters(): List { + val results = ArrayList() + accept( + object : PsiTypeVisitor() { + override fun visitArrayType(arrayType: PsiArrayType) { + arrayType.componentType.accept(this) + } + + override fun visitClassType(classType: PsiClassType) { + (classType.resolve() as? PsiTypeParameter)?.let { results += it } + classType.parameters.forEach { it.accept(this) } + } + + override fun visitWildcardType(wildcardType: PsiWildcardType) { + wildcardType.bound?.accept(this) + } + } + ) + return results + } + + private fun PsiType.resolveToKotlinType(resolutionFacade: ResolutionFacade): KotlinType? { + val typeParameters = collectTypeParameters() + val components = resolutionFacade.getFrontendService(JavaResolverComponents::class.java) + val rootContext = LazyJavaResolverContext(components, TypeParameterResolver.EMPTY) { null } + val dummyPackageDescriptor = MutablePackageFragmentDescriptor(resolutionFacade.moduleDescriptor, FqName("dummy")) + val dummyClassDescriptor = ClassDescriptorImpl( + dummyPackageDescriptor, + Name.identifier("Dummy"), + Modality.FINAL, + ClassKind.CLASS, + emptyList(), + SourceElement.NO_SOURCE, + false + ) + val typeParameterResolver = object : TypeParameterResolver { + override fun resolveTypeParameter(javaTypeParameter: JavaTypeParameter): TypeParameterDescriptor? { + val psiTypeParameter = (javaTypeParameter as JavaTypeParameterImpl).psi + val index = typeParameters.indexOf(psiTypeParameter) + if (index < 0) return null + return LazyJavaTypeParameterDescriptor(rootContext.child(this), javaTypeParameter, index, dummyClassDescriptor) + } + } + val typeResolver = JavaTypeResolver(rootContext, typeParameterResolver) + val attributes = JavaTypeAttributes(TypeUsage.COMMON) + return typeResolver.transformJavaType(JavaTypeImpl.create(this), attributes).approximateFlexibleTypes(preferNotNull = true) + } + + private fun ExpectedTypes.toKotlinTypeInfo(resolutionFacade: ResolutionFacade): TypeInfo { + val candidateTypes = flatMapTo(LinkedHashSet()) { + val ktType = (it.theType as? PsiType)?.resolveToKotlinType(resolutionFacade) ?: return@flatMapTo emptyList() + when (it.theKind) { + ExpectedType.Kind.EXACT, ExpectedType.Kind.SUBTYPE -> listOf(ktType) + ExpectedType.Kind.SUPERTYPE -> listOf(ktType) + ktType.supertypes() + } + } + if (candidateTypes.isEmpty()) { + val nullableAnyType = resolutionFacade.moduleDescriptor.builtIns.nullableAnyType + return TypeInfo(nullableAnyType, Variance.INVARIANT) + } + return TypeInfo.ByExplicitCandidateTypes(candidateTypes.toList()) + } + + override fun createChangeModifierActions(target: JvmModifiersOwner, request: MemberRequest.Modifier): List { + val kModifierOwner = target.toKtElement() ?: return emptyList() + + val modifier = request.modifier + val shouldPresent = request.shouldPresent + val (kToken, shouldPresentMapped) = if (JvmModifier.FINAL == modifier) + KtTokens.OPEN_KEYWORD to !shouldPresent + else + javaPsiModifiersMapping[modifier] to shouldPresent + if (kToken == null) return emptyList() + + val action = if (shouldPresentMapped) + AddModifierFix.createIfApplicable(kModifierOwner, kToken) + else + RemoveModifierFix(kModifierOwner, kToken, false) + return listOfNotNull(action) + } + + override fun createAddConstructorActions(targetClass: JvmClass, request: MemberRequest.Constructor): List { + val targetKtClass = targetClass.toKtClassOrFile() as? KtClass ?: return emptyList() + + if (request.typeParameters.isNotEmpty()) return emptyList() + + val modifierBuilder = ModifierBuilder(targetKtClass).apply { addJvmModifiers(request.modifiers) } + if (!modifierBuilder.isValid) return emptyList() + + val resolutionFacade = targetKtClass.getResolutionFacade() + val nullableAnyType = resolutionFacade.moduleDescriptor.builtIns.nullableAnyType + val parameterInfos = request.parameters.mapIndexed { index, param -> + val ktType = (param.type as? PsiType)?.resolveToKotlinType(resolutionFacade) ?: nullableAnyType + val name = param.name ?: "arg${index + 1}" + ParameterInfo(TypeInfo(ktType, Variance.IN_VARIANCE), listOf(name)) + } + val needPrimary = !targetKtClass.hasExplicitPrimaryConstructor() + val constructorInfo = ConstructorInfo( + parameterInfos, + targetKtClass, + isPrimary = needPrimary, + modifierList = modifierBuilder.modifierList, + withBody = true + ) + val addConstructorAction = object : CreateCallableFromUsageFix(targetKtClass, listOf(constructorInfo)) { + override fun getFamilyName() = "Add method" + override fun getText() = "Add ${if (needPrimary) "primary" else "secondary"} constructor to '${targetClass.name}'" + } + + val changePrimaryConstructorAction = run { + val primaryConstructor = targetKtClass.primaryConstructor ?: return@run null + val lightMethod = primaryConstructor.toLightMethods().firstOrNull() ?: return@run null + val project = targetKtClass.project + val fakeParametersExpressions = fakeParametersExpressions(request.parameters, project) ?: return@run null + QuickFixFactory.getInstance() + .createChangeMethodSignatureFromUsageFix( + lightMethod, + fakeParametersExpressions, + PsiSubstitutor.EMPTY, + targetKtClass, + false, + 2 + ).takeIf { it.isAvailable(project, null, targetKtClass.containingFile) } + } + + return listOfNotNull(changePrimaryConstructorAction, addConstructorAction) + } + + override fun createAddPropertyActions(targetClass: JvmClass, request: MemberRequest.Property): List { + val targetContainer = targetClass.toKtClassOrFile() ?: return emptyList() + + val modifierBuilder = ModifierBuilder(targetContainer).apply { addJvmModifier(request.visibilityModifier) } + if (!modifierBuilder.isValid) return emptyList() + + val resolutionFacade = targetContainer.getResolutionFacade() + val nullableAnyType = resolutionFacade.moduleDescriptor.builtIns.nullableAnyType + val ktType = (request.propertyType as? PsiType)?.resolveToKotlinType(resolutionFacade) ?: nullableAnyType + val propertyInfo = PropertyInfo( + request.propertyName, + TypeInfo.Empty, + TypeInfo(ktType, Variance.INVARIANT), + request.setterRequired, + listOf(targetContainer), + modifierList = modifierBuilder.modifierList, + withInitializer = true + ) + val propertyInfos = if (request.setterRequired) { + listOf(propertyInfo, propertyInfo.copyProperty(isLateinitPreferred = true)) + } + else { + listOf(propertyInfo) + } + return propertyInfos.map { CreatePropertyFix(targetClass, targetContainer, it) } + } + + override fun createAddFieldActions(targetClass: JvmClass, request: CreateFieldRequest): List { + val targetContainer = targetClass.toKtClassOrFile() ?: return emptyList() + + val modifierBuilder = ModifierBuilder(targetContainer, allowJvmStatic = false).apply { + addJvmModifiers(request.modifiers) + addAnnotation(JVM_FIELD_ANNOTATION_FQ_NAME) + } + if (!modifierBuilder.isValid) return emptyList() + + val resolutionFacade = targetContainer.getResolutionFacade() + val typeInfo = request.fieldType.toKotlinTypeInfo(resolutionFacade) + val writable = JvmModifier.FINAL !in request.modifiers + val propertyInfo = PropertyInfo( + request.fieldName, + TypeInfo.Empty, + typeInfo, + writable, + listOf(targetContainer), + isForCompanion = JvmModifier.STATIC in request.modifiers, + modifierList = modifierBuilder.modifierList, + withInitializer = true + ) + val propertyInfos = if (writable) { + listOf(propertyInfo, propertyInfo.copyProperty(isLateinitPreferred = true)) + } + else { + listOf(propertyInfo) + } + return propertyInfos.map { CreatePropertyFix(targetClass, targetContainer, it) } + } + + override fun createAddMethodActions(targetClass: JvmClass, request: CreateMethodRequest): List { + val targetContainer = targetClass.toKtClassOrFile() ?: return emptyList() + + val modifierBuilder = ModifierBuilder(targetContainer).apply { addJvmModifiers(request.modifiers) } + if (!modifierBuilder.isValid) return emptyList() + + val resolutionFacade = targetContainer.getResolutionFacade() + val returnTypeInfo = request.returnType.toKotlinTypeInfo(resolutionFacade) + val parameterInfos = request.parameters.map { (suggestedNames, expectedTypes) -> + ParameterInfo(expectedTypes.toKotlinTypeInfo(resolutionFacade), suggestedNames.names.toList()) + } + val functionInfo = FunctionInfo( + request.methodName, + TypeInfo.Empty, + returnTypeInfo, + listOf(targetContainer), + parameterInfos, + isAbstract = JvmModifier.ABSTRACT in request.modifiers, + isForCompanion = JvmModifier.STATIC in request.modifiers, + modifierList = modifierBuilder.modifierList, + preferEmptyBody = true + ) + val action = object : CreateCallableFromUsageFix(targetContainer, listOf(functionInfo)) { + override fun getFamilyName() = "Add method" + override fun getText() = "Add method '${request.methodName}' to '${targetClass.name}'" + } + return listOf(action) + } +} \ No newline at end of file