diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.kt b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.kt index dda1af52a7f..d458c3d7b85 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.kt @@ -228,6 +228,10 @@ public class JetPsiFactory(private val project: Project) { return createFunction("fun foo$text{}").getValueParameterList()!! } + public fun createEnumEntry(text: String): JetEnumEntry { + return createDeclaration("enum class E {$text}").getDeclarations()[0] as JetEnumEntry + } + public fun createWhenEntry(entryText: String): JetWhenEntry { val function = createFunction("fun foo() { when(12) { " + entryText + " } }") val whenEntry = PsiTreeUtil.findChildOfType(function, javaClass()) diff --git a/idea/ide-common/src/org/jetbrains/jet/plugin/util/TypeUtils.kt b/idea/ide-common/src/org/jetbrains/jet/plugin/util/TypeUtils.kt index 072569e676d..bb87c45515f 100644 --- a/idea/ide-common/src/org/jetbrains/jet/plugin/util/TypeUtils.kt +++ b/idea/ide-common/src/org/jetbrains/jet/plugin/util/TypeUtils.kt @@ -36,6 +36,7 @@ fun JetType.makeNotNullable() = TypeUtils.makeNotNullable(this) fun JetType.supertypes(): Set = TypeUtils.getAllSupertypes(this) fun JetType.isUnit(): Boolean = KotlinBuiltIns.getInstance().isUnit(this) +fun JetType.isAny(): Boolean = KotlinBuiltIns.getInstance().isAnyOrNullableAny(this) public fun approximateFlexibleTypes(jetType: JetType, outermost: Boolean = true): JetType { if (jetType.isFlexible()) { diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt index cf02cf49e61..cc3a7dd938b 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt @@ -19,14 +19,12 @@ package org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder import com.intellij.codeInsight.navigation.NavigationUtil import com.intellij.codeInsight.template.* import com.intellij.codeInsight.template.impl.TemplateImpl -import com.intellij.codeInsight.template.impl.Variable import com.intellij.ide.fileTemplates.FileTemplate import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.progress.ProcessCanceledException -import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.IncorrectOperationException @@ -65,6 +63,8 @@ import org.jetbrains.jet.lang.descriptors.impl.MutablePackageFragmentDescriptor import org.jetbrains.jet.lang.descriptors.impl.TypeParameterDescriptorImpl import java.util.LinkedHashMap import org.jetbrains.jet.plugin.util.IdeDescriptorRenderers +import org.jetbrains.jet.plugin.quickfix.createFromUsage.createClass.ClassKind +import org.jetbrains.jet.plugin.util.isAny import org.jetbrains.jet.utils.addToStdlib.singletonOrEmptyList private val TYPE_PARAMETER_LIST_VARIABLE_NAME = "typeParameterList" @@ -113,7 +113,7 @@ fun List.getTypeByRenderedType(renderedType: String): JetType? = class CallableBuilderConfiguration( val callableInfos: List, - val originalExpression: JetExpression, + val originalElement: JetElement, val currentFile: JetFile, val currentEditor: Editor, val enableSubstitutions: Boolean = true @@ -285,10 +285,14 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { computeTypeCandidates(it.typeInfo, substitutions, scope) } - val returnTypeCandidates = computeTypeCandidates(callableInfo.returnTypeInfo, substitutions, scope) - skipReturnType = callableInfo is FunctionInfo - && returnTypeCandidates.size == 1 - && returnTypeCandidates.first().theType.isUnit() + val returnTypeCandidate = computeTypeCandidates(callableInfo.returnTypeInfo, substitutions, scope).singleOrNull() + skipReturnType = when (callableInfo.kind) { + CallableKind.FUNCTION -> + returnTypeCandidate?.theType?.isUnit() ?: false + CallableKind.CONSTRUCTOR -> + callableInfo.returnTypeInfo == TypeInfo.Empty || returnTypeCandidate?.theType?.isAny() ?: false + CallableKind.PROPERTY -> false + } // figure out type parameter renames to avoid conflicts typeParameterNameMap = getTypeParameterRenames(scope) @@ -355,31 +359,63 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { typeCandidates[typeInfo]?.forEach { it.render(typeParameterNameMap, fakeFunction) } } - private fun createDeclarationSkeleton(): JetCallableDeclaration { + private fun createDeclarationSkeleton(): JetNamedDeclaration { with (config) { val assignmentToReplace = if (containingElement is JetBlockExpression && (callableInfo as? PropertyInfo)?.writable ?: false) { - originalExpression as JetBinaryExpression + originalElement as JetBinaryExpression } else null val ownerTypeString = if (isExtension) "${receiverTypeCandidate!!.renderedType!!}." else "" + + val classKind = (callableInfo as? ConstructorInfo)?.classInfo?.kind + + fun renderParamList(): String { + val prefix = if (classKind == ClassKind.ANNOTATION_CLASS) "val " else "" + val list = callableInfo.parameterInfos.indices.map { i -> "${prefix}p$i: Any" }.joinToString(", ") + return if (callableInfo.parameterInfos.isNotEmpty() || callableInfo.kind == CallableKind.FUNCTION) "($list)" else list + } + val paramList = when (callableInfo.kind) { - CallableKind.FUNCTION -> - "(${(callableInfo as FunctionInfo).parameterInfos.indices.map { i -> "p$i: Any" }.joinToString(", ")})" - CallableKind.PROPERTY -> - "" + CallableKind.FUNCTION, CallableKind.CONSTRUCTOR -> renderParamList() + CallableKind.PROPERTY -> "" } val returnTypeString = if (skipReturnType || assignmentToReplace != null) "" else ": Any" val header = "$ownerTypeString${callableInfo.name}$paramList$returnTypeString" val psiFactory = JetPsiFactory(currentFile) - val declaration : PsiElement = when (callableInfo.kind) { - CallableKind.FUNCTION -> psiFactory.createFunction("fun $header {}") + val declaration : JetNamedDeclaration = when (callableInfo.kind) { + CallableKind.FUNCTION -> psiFactory.createFunction("fun<> $header {}") + CallableKind.CONSTRUCTOR -> { + with((callableInfo as ConstructorInfo).classInfo) { + val classBody = when (kind) { + ClassKind.ANNOTATION_CLASS, ClassKind.ENUM_ENTRY -> "" + else -> "{\n\n}" + } + when (kind) { + ClassKind.ENUM_ENTRY -> { + if (!(targetParent is JetClass && targetParent.isEnum())) throw AssertionError("Enum class expected: ${targetParent.getText()}") + val hasParameters = targetParent.getPrimaryConstructorParameters().isNotEmpty() + psiFactory.createEnumEntry("$name ${if (hasParameters) ": ${targetParent.getName()}()" else ""}") + } + else -> { + val innerMod = if (inner) "inner " else "" + val typeParamList = when (kind) { + ClassKind.PLAIN_CLASS, ClassKind.TRAIT -> "<>" + else -> "" + } + psiFactory.createDeclaration( + "$innerMod${kind.keyword} $name$typeParamList$paramList$returnTypeString $classBody" + ) + } + } + } + } CallableKind.PROPERTY -> { val valVar = if ((callableInfo as PropertyInfo).writable) "var" else "val" - psiFactory.createProperty("$valVar $header") + psiFactory.createProperty("$valVar<> $header") } } @@ -423,7 +459,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { } when (containingElement) { - is JetFile -> return append(declaration, containingElement.getLastChild()!!, false) as JetCallableDeclaration + is JetFile -> return append(declaration, containingElement.getLastChild()!!, false) as JetNamedDeclaration is JetClassOrObject -> { var classBody = containingElement.getBody() @@ -435,9 +471,9 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { if (declaration is JetNamedFunction) { val rBrace = classBody!!.getRBrace() return (rBrace?.let { append(declaration, it, true) } - ?: append(declaration, classBody!!.getLastChild()!!, false)) as JetCallableDeclaration + ?: append(declaration, classBody!!.getLastChild()!!, false)) as JetNamedDeclaration } - return prepend(declaration, classBody!!.getLBrace()!!, true) as JetCallableDeclaration + return prepend(declaration, classBody!!.getLBrace()!!, true) as JetNamedDeclaration } is JetBlockExpression -> { @@ -448,9 +484,9 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { parent.addAfter(newLine, containingElement) } - return prepend(declaration, containingElement.getFirstChild()!!, false) as JetCallableDeclaration + return prepend(declaration, containingElement.getFirstChild()!!, false) as JetNamedDeclaration } - return prepend(declaration, containingElement.getLBrace()!!, true) as JetCallableDeclaration + return prepend(declaration, containingElement.getLBrace()!!, true) as JetNamedDeclaration } else -> throw AssertionError("Invalid containing element: ${containingElement.getText()}") @@ -476,20 +512,20 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { return allTypeParametersNotInScope.zip(typeParameterNames).toMap() } - private fun setupTypeReferencesForShortening(declaration: JetCallableDeclaration, + private fun setupTypeReferencesForShortening(declaration: JetNamedDeclaration, typeRefsToShorten: MutableList, parameterTypeExpressions: List) { if (isExtension) { val receiverTypeRef = JetPsiFactory(declaration).createType(receiverTypeCandidate!!.theType.renderLong(typeParameterNameMap)) replaceWithLongerName(receiverTypeRef, receiverTypeCandidate.theType) - val funcReceiverTypeRef = declaration.getReceiverTypeReference() + val funcReceiverTypeRef = (declaration as? JetCallableDeclaration)?.getReceiverTypeReference() if (funcReceiverTypeRef != null) { typeRefsToShorten.add(funcReceiverTypeRef) } } - val returnTypeRef = declaration.getTypeReference() + val returnTypeRef = declaration.getReturnTypeReference() if (returnTypeRef != null) { val returnType = typeCandidates[callableInfo.returnTypeInfo]!!.getTypeByRenderedType( returnTypeRef.getText() @@ -498,11 +534,11 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { if (returnType != null) { // user selected a given type replaceWithLongerName(returnTypeRef, returnType) - typeRefsToShorten.add(declaration.getTypeReference()!!) + typeRefsToShorten.add(declaration.getReturnTypeReference()!!) } } - val valueParameters = declaration.getValueParameterList()?.getParameters() ?: Collections.emptyList() + val valueParameters = declaration.getValueParameters() val parameterIndicesToShorten = ArrayList() assert(valueParameters.size == parameterTypeExpressions.size) for ((i, parameter) in valueParameters.stream().withIndices()) { @@ -519,11 +555,10 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { } } - declaration.getValueParameterList()?.getParameters()?.let { expandedValueParameters -> - parameterIndicesToShorten.stream() - .map { expandedValueParameters[it].getTypeReference() } - .filterNotNullTo(typeRefsToShorten) - } + val expandedValueParameters = declaration.getValueParameters() + parameterIndicesToShorten.stream() + .map { expandedValueParameters[it].getTypeReference() } + .filterNotNullTo(typeRefsToShorten) } private fun setupFunctionBody(func: JetNamedFunction) { @@ -552,33 +587,47 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { func.getBodyExpression()!!.replace(newBodyExpression) } - private fun setupCallTypeArguments(callExpr: JetCallExpression, typeParameters: List) { - val oldTypeArgumentList = callExpr.getTypeArgumentList() ?: return + private fun setupCallTypeArguments(callElement: JetCallElement, typeParameters: List) { + val oldTypeArgumentList = callElement.getTypeArgumentList() ?: return val renderedTypeArgs = typeParameters.map { typeParameter -> val type = substitutions.first { it.byType.getConstructor().getDeclarationDescriptor() == typeParameter }.forType IdeDescriptorRenderers.SOURCE_CODE.renderType(type) } - oldTypeArgumentList.replace(JetPsiFactory(callExpr).createTypeArguments(renderedTypeArgs.joinToString(", ", "<", ">"))) - elementsToShorten.add(callExpr.getTypeArgumentList()) + if (renderedTypeArgs.isEmpty()) { + oldTypeArgumentList.delete() + } + else { + oldTypeArgumentList.replace(JetPsiFactory(callElement).createTypeArguments(renderedTypeArgs.joinToString(", ", "<", ">"))) + elementsToShorten.add(callElement.getTypeArgumentList()) + } } - private fun setupReturnTypeTemplate(builder: TemplateBuilder, declaration: JetCallableDeclaration): TypeExpression? { - val returnTypeRef = declaration.getTypeReference() ?: return null + private fun setupReturnTypeTemplate(builder: TemplateBuilder, declaration: JetNamedDeclaration): TypeExpression? { val candidates = typeCandidates[callableInfo.returnTypeInfo]!! - return when (candidates.size) { - 0 -> null + if (candidates.isEmpty()) return null - 1 -> { - builder.replaceElement(returnTypeRef, candidates.first().renderedType!!) - null + val elementToReplace: JetElement? + val expression: TypeExpression + when (declaration) { + is JetCallableDeclaration -> { + elementToReplace = declaration.getTypeReference() + expression = TypeExpression.ForTypeReference(candidates) } - - else -> { - val returnTypeExpression = TypeExpression(candidates) - builder.replaceElement(returnTypeRef, returnTypeExpression) - returnTypeExpression + is JetClassOrObject -> { + elementToReplace = declaration.getDelegationSpecifiers().firstOrNull() + expression = TypeExpression.ForDelegationSpecifier(candidates) } + else -> throw AssertionError("Unexpected declaration kind: ${declaration.getText()}") } + if (elementToReplace == null) return null + + if (candidates.size == 1) { + builder.replaceElement(elementToReplace, (expression.calculateResult(null) as TextResult).getText()) + return null + } + + builder.replaceElement(elementToReplace, expression) + return expression } private fun setupValVarTemplate(builder: TemplateBuilder, property: JetProperty) { @@ -587,7 +636,17 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { } } - private fun setupTypeParameterListTemplate(builder: TemplateBuilderImpl, declaration: JetCallableDeclaration): TypeParameterListExpression { + private fun setupTypeParameterListTemplate( + builder: TemplateBuilderImpl, + declaration: JetNamedDeclaration + ): TypeParameterListExpression? { + when (declaration) { + is JetObjectDeclaration -> return null + !is JetTypeParameterListOwner -> throw AssertionError("Unexpected declaration kind: ${declaration.getText()}") + } + + val typeParameterList = (declaration as JetTypeParameterListOwner).getTypeParameterList() ?: return null + val typeParameterMap = HashMap>() val mandatoryTypeParameters = ArrayList() @@ -598,14 +657,19 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { typeParameterMap[it.renderedType!!] = it.renderedTypeParameters!! } - if (declaration.getTypeReference() != null) { + if (declaration.getReturnTypeReference() != null) { typeCandidates[callableInfo.returnTypeInfo]!!.forEach { typeParameterMap[it.renderedType!!] = it.renderedTypeParameters!! } } - // ((3, 3) is after "fun") - builder.replaceElement(declaration, TextRange.create(3, 3), TYPE_PARAMETER_LIST_VARIABLE_NAME, null, false) - return TypeParameterListExpression(mandatoryTypeParameters, typeParameterMap) + + val expression = TypeParameterListExpression( + mandatoryTypeParameters, + typeParameterMap, + callableInfo.kind != CallableKind.CONSTRUCTOR + ) + builder.replaceElement(typeParameterList, expression, false) + return expression } private fun setupParameterTypeTemplates(builder: TemplateBuilder, parameterList: List): List { @@ -613,7 +677,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { val typeParameters = ArrayList() for ((parameter, jetParameter) in callableInfo.parameterInfos.zip(parameterList)) { - val parameterTypeExpression = TypeExpression(typeCandidates[parameter.typeInfo]!!) + val parameterTypeExpression = TypeExpression.ForTypeReference(typeCandidates[parameter.typeInfo]!!) val parameterTypeRef = jetParameter.getTypeReference()!! builder.replaceElement(parameterTypeRef, parameterTypeExpression) @@ -671,8 +735,8 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { if (!skipReturnType) { setupReturnTypeTemplate(builder, declaration) } - val parameterTypeExpressions = - setupParameterTypeTemplates(builder, declaration.getValueParameterList()?.getParameters() ?: Collections.emptyList()) + + 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 @@ -684,13 +748,14 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { // the template built by TemplateBuilderImpl is ordered by element position, but we want types to be first, so hack it val templateImpl = builder.buildInlineTemplate() as TemplateImpl val variables = templateImpl.getVariables()!! - for (i in 0..(callableInfo.parameterInfos.size - 1)) { - Collections.swap(variables, i * 2, i * 2 + 1) + if (variables.isNotEmpty()) { + val typeParametersVar = if (expression != null) variables.remove(0) else null + for (i in 0..(callableInfo.parameterInfos.size - 1)) { + Collections.swap(variables, i * 2, i * 2 + 1) + } + typeParametersVar?.let { variables.add(it) } } - // fix up the template to include the expression for the type parameter list - variables.add(Variable(TYPE_PARAMETER_LIST_VARIABLE_NAME, expression, expression, false, true)) - // TODO: Disabled shortening names because it causes some tests fail. Refactor code to use automatic reference shortening templateImpl.setToShortenLongNames(false) @@ -700,18 +765,20 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { PsiDocumentManager.getInstance(project).commitDocument(containingFileEditor.getDocument()) // file templates - val offset = templateImpl.getSegmentOffset(0) - val newDeclaration = PsiTreeUtil.findElementOfClassAtOffset( - containingFile, offset, javaClass(), false - )!! + val newDeclaration = if (templateImpl.getSegmentsCount() > 0) { + PsiTreeUtil.findElementOfClassAtOffset(containingFile, templateImpl.getSegmentOffset(0), declaration.javaClass, false)!! + } + else declarationPointer.getElement()!! ApplicationManager.getApplication()!!.runWriteAction { // file templates if (newDeclaration is JetNamedFunction) { setupFunctionBody(newDeclaration) - (config.originalExpression as? JetCallExpression)?.let { - setupCallTypeArguments(it, expression.currentTypeParameters) - } + } + + val callElement = config.originalElement as? JetCallElement + if (callElement != null) { + setupCallTypeArguments(callElement, expression?.currentTypeParameters ?: Collections.emptyList()) } // change short type names to fully qualified ones (to be shortened below) @@ -726,4 +793,21 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { } } +private fun JetNamedDeclaration.getValueParameters(): List { + return when (this) { + is JetCallableDeclaration -> getValueParameterList() + is JetClass -> getPrimaryConstructorParameterList() + is JetObjectDeclaration -> null + else -> throw AssertionError("Unexpected declaration kind: ${getText()}") + }?.getParameters() ?: Collections.emptyList() +} + +private fun JetNamedDeclaration.getReturnTypeReference(): JetTypeReference? { + return when (this) { + is JetCallableDeclaration -> getTypeReference() + is JetClassOrObject -> getDelegationSpecifiers().firstOrNull()?.getTypeReference() + else -> throw AssertionError("Unexpected declaration kind: ${getText()}") + } +} + fun CallableBuilderConfiguration.createBuilder(): CallableBuilder = CallableBuilder(this) diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/CallableInfo.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/CallableInfo.kt index 24bfe7705c8..9a80731b547 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/CallableInfo.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/CallableInfo.kt @@ -14,6 +14,12 @@ import org.jetbrains.jet.lang.types.ErrorUtils import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns import org.jetbrains.jet.lang.psi.JetElement import org.jetbrains.jet.lang.psi.JetTypeReference +import org.jetbrains.jet.plugin.quickfix.createFromUsage.createClass.ClassKind +import org.jetbrains.jet.plugin.quickfix.createFromUsage.createClass.ClassInfo +import org.jetbrains.jet.plugin.util.makeNotNullable +import org.jetbrains.jet.lang.types.TypeUtils +import org.jetbrains.jet.lang.types.checker.JetTypeChecker +import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor /** * Represents a concrete type or a set of types yet to be inferred from an expression. @@ -77,6 +83,15 @@ fun TypeInfo(theType: JetType, variance: Variance): TypeInfo = TypeInfo.ByType(t 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) +} + /** * Encapsulates information about a function parameter that is going to be created. */ @@ -87,6 +102,7 @@ class ParameterInfo( enum class CallableKind { FUNCTION + CONSTRUCTOR PROPERTY } @@ -111,6 +127,13 @@ class FunctionInfo(name: String, override val kind: CallableKind get() = CallableKind.FUNCTION } +class ConstructorInfo(val classInfo: ClassInfo, expectedTypeInfo: TypeInfo): CallableInfo( + classInfo.name, TypeInfo.Empty, expectedTypeInfo.forceNotNull(), Collections.emptyList(), classInfo.typeArguments +) { + override val kind: CallableKind get() = CallableKind.CONSTRUCTOR + override val parameterInfos: List get() = classInfo.parameterInfos +} + class PropertyInfo(name: String, receiverTypeInfo: TypeInfo, returnTypeInfo: TypeInfo, diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/templateExpressions.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/templateExpressions.kt index 91d67f5719a..d040cd3dcf6 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/templateExpressions.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/templateExpressions.kt @@ -19,6 +19,11 @@ import org.jetbrains.jet.lang.psi.JetCallableDeclaration import java.util.Collections import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor import org.jetbrains.jet.lang.descriptors.FunctionDescriptor +import org.jetbrains.jet.lang.descriptors.ClassDescriptor +import org.jetbrains.jet.lang.descriptors.ClassKind +import org.jetbrains.jet.lang.psi.JetClass +import org.jetbrains.jet.lang.psi.JetParameterList +import org.jetbrains.jet.lang.psi.JetNamedDeclaration /** * Special Expression for parameter names based on its type. @@ -48,8 +53,12 @@ private class ParameterNameExpression( val editor = context.getEditor()!! val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) as JetFile val elementAt = file.findElementAt(offset) - val func = PsiTreeUtil.getParentOfType(elementAt, javaClass()) ?: return array() - val parameterList = func.getValueParameterList()!! + val declaration = PsiTreeUtil.getParentOfType(elementAt, javaClass(), javaClass()) ?: return array() + val parameterList = when (declaration) { + is JetFunction -> declaration.getValueParameterList() + is JetClass -> declaration.getPrimaryConstructorParameterList() + else -> throw AssertionError("Unexpected declaration: ${declaration.getText()}") + } // add names based on selected type val parameter = PsiTreeUtil.getParentOfType(elementAt, javaClass()) @@ -80,11 +89,24 @@ private class ParameterNameExpression( } /** - * An Expression for type references. + * An Expression for type references and delegation specifiers. */ -private class TypeExpression(public val typeCandidates: List) : Expression() { - private val cachedLookupElements: Array = - typeCandidates.map { LookupElementBuilder.create(it, it.renderedType!!) }.copyToArray() +private abstract class TypeExpression(public val typeCandidates: List) : Expression() { + class ForTypeReference(typeCandidates: List) : TypeExpression(typeCandidates) { + override val cachedLookupElements: Array = + typeCandidates.map { LookupElementBuilder.create(it, it.renderedType!!) }.copyToArray() + } + + class ForDelegationSpecifier(typeCandidates: List) : TypeExpression(typeCandidates) { + override val cachedLookupElements: Array = + typeCandidates.map { + val descriptor = it.theType.getConstructor().getDeclarationDescriptor() as ClassDescriptor + val text = it.renderedType!! + if (descriptor.getKind() == ClassKind.TRAIT) "" else "()" + LookupElementBuilder.create(it, text) + }.copyToArray() + } + + protected abstract val cachedLookupElements: Array override fun calculateResult(context: ExpressionContext?): Result { val lookupItems = calculateLookupItems(context) @@ -100,7 +122,10 @@ private class TypeExpression(public val typeCandidates: List) : E * A sort-of dummy Expression for parameter lists, to allow us to update the parameter list as the user makes selections. */ private class TypeParameterListExpression(private val mandatoryTypeParameters: List, - private val parameterTypeToTypeParameterNamesMap: Map>) : Expression() { + private val parameterTypeToTypeParameterNamesMap: Map>, + insertLeadingSpace: Boolean) : Expression() { + private val prefix = if (insertLeadingSpace) " <" else "<" + public var currentTypeParameters: List = Collections.emptyList() private set @@ -113,12 +138,11 @@ private class TypeParameterListExpression(private val mandatoryTypeParameters: L val editor = context.getEditor()!! val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) as JetFile val elementAt = file.findElementAt(offset) - val callable = PsiTreeUtil.getParentOfType(elementAt, javaClass()) ?: return TextResult("") - val parameters = callable.getValueParameterList()?.getParameters() ?: Collections.emptyList() + val declaration = PsiTreeUtil.getParentOfType(elementAt, javaClass()) ?: return TextResult("") val renderedTypeParameters = LinkedHashSet() renderedTypeParameters.addAll(mandatoryTypeParameters) - for (parameter in parameters) { + for (parameter in declaration.getValueParameters()) { val parameterTypeRef = parameter.getTypeReference() if (parameterTypeRef != null) { val typeParameterNamesFromParameter = parameterTypeToTypeParameterNamesMap[parameterTypeRef.getText()] @@ -127,7 +151,7 @@ private class TypeParameterListExpression(private val mandatoryTypeParameters: L } } } - val returnTypeRef = callable.getTypeReference() + val returnTypeRef = declaration.getReturnTypeReference() if (returnTypeRef != null) { val typeParameterNamesFromReturnType = parameterTypeToTypeParameterNamesMap[returnTypeRef.getText()] if (typeParameterNamesFromReturnType != null) { @@ -140,7 +164,7 @@ private class TypeParameterListExpression(private val mandatoryTypeParameters: L currentTypeParameters = sortedRenderedTypeParameters.map { it.typeParameter } return TextResult( - if (sortedRenderedTypeParameters.empty) "" else sortedRenderedTypeParameters.map { it.text }.joinToString(", ", " <", ">") + if (sortedRenderedTypeParameters.empty) "" else sortedRenderedTypeParameters.map { it.text }.joinToString(", ", prefix, ">") ) } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/typeUtils.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/typeUtils.kt index 6b5b7cef156..83d4c5e4c08 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/typeUtils.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/callableBuilder/typeUtils.kt @@ -83,10 +83,14 @@ fun JetType.getTypeParameters(): Set { return typeParameters } -fun JetExpression.guessTypes(context: BindingContext, module: ModuleDescriptor?): Array { +fun JetExpression.guessTypes( + context: BindingContext, + module: ModuleDescriptor?, + coerceUnusedToUnit: Boolean = true +): Array { val builtIns = KotlinBuiltIns.getInstance() - if (this !is JetDeclaration && isUsedAsStatement(context)) return array(builtIns.getUnitType()) + if (coerceUnusedToUnit && this !is JetDeclaration && isUsedAsStatement(context)) return array(builtIns.getUnitType()) // if we know the actual type of the expression val theType1 = context[BindingContext.EXPRESSION_TYPE, this] diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt new file mode 100644 index 00000000000..cc200c14675 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createClass/CreateClassFromUsageFix.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.quickfix.createFromUsage.createClass + +import org.jetbrains.jet.plugin.quickfix.createFromUsage.CreateFromUsageFixBase +import org.jetbrains.jet.lang.psi.JetElement +import org.jetbrains.jet.plugin.JetBundle +import com.intellij.openapi.project.Project +import com.intellij.openapi.editor.Editor +import org.jetbrains.jet.lang.psi.JetFile +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiDirectory +import org.jetbrains.jet.plugin.JetFileType +import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils +import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.TypeInfo +import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.ParameterInfo +import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.ConstructorInfo +import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.CallableBuilderConfiguration +import org.jetbrains.jet.lang.psi.JetExpression +import java.util.Collections +import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.createBuilder +import com.intellij.openapi.command.CommandProcessor +import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.CallablePlacement +import org.jetbrains.jet.plugin.refactoring.getOrCreateKotlinFile +import org.jetbrains.jet.plugin.quickfix.createFromUsage.createClass.ClassKind.* + +enum class ClassKind(val keyword: String, val description: String) { + PLAIN_CLASS: ClassKind("class", "class") + ENUM_CLASS: ClassKind("enum class", "enum") + ENUM_ENTRY: ClassKind("", "enum constant") + ANNOTATION_CLASS: ClassKind("annotation class", "annotation") + TRAIT: ClassKind("trait", "trait") + OBJECT: ClassKind("object", "object") +} + +public class ClassInfo( + val kind: ClassKind, + val name: String, + val targetParent: PsiElement, + val expectedTypeInfo: TypeInfo, + val inner: Boolean = false, + val typeArguments: List = Collections.emptyList(), + val parameterInfos: List = Collections.emptyList() +) + +public class CreateClassFromUsageFix( + element: JetElement, + val classInfo: ClassInfo +): CreateFromUsageFixBase(element) { + override fun getText(): String = + JetBundle.message("create.0.from.usage", "${classInfo.kind.description} '${classInfo.name}'") + + override fun invoke(project: Project, editor: Editor, file: JetFile) { + with (classInfo) { + val targetParent = when (targetParent) { + is JetElement -> targetParent + is PsiDirectory -> { + val fileName = "$name.${JetFileType.INSTANCE.getDefaultExtension()}" + val targetFile = getOrCreateKotlinFile(fileName, targetParent) + if (targetFile == null) { + val filePath = "${targetParent.getVirtualFile().getPath()}/$fileName" + CodeInsightUtils.showErrorHint( + targetParent.getProject(), + editor, + "File $filePath already exists but does not correspond to Kotlin file", + "Create file", + null + ) + } + targetFile + } + else -> throw AssertionError("Unexpected element: " + targetParent.getText()) + } as? JetElement ?: return + + val constructorInfo = ConstructorInfo(classInfo, expectedTypeInfo) + val builder = CallableBuilderConfiguration( + Collections.singletonList(constructorInfo), element as JetElement, file, editor, kind == PLAIN_CLASS || kind == TRAIT + ).createBuilder() + builder.placement = CallablePlacement.NoReceiver(targetParent) + CommandProcessor.getInstance().executeCommand(project, { builder.build() }, getText(), null) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateCallableFromUsageFix.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateCallableFromUsageFix.kt index c48fa552afa..8ecf890238a 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateCallableFromUsageFix.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/createFromUsage/createFunction/CreateCallableFromUsageFix.kt @@ -36,6 +36,7 @@ public class CreateCallableFromUsageFix( val kind = when (it.kind) { CallableKind.FUNCTION -> "function" CallableKind.PROPERTY -> "property" + else -> throw AssertionError("Unexpected callable info: $it") } "$kind '${it.name}'" } diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt index 3f408f954c4..301efa40105 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -80,6 +80,9 @@ fun PsiElement.getAndRemoveCopyableUserData(key: Key): T? { return data } +fun getOrCreateKotlinFile(fileName: String, targetDir: PsiDirectory): JetFile? = + (targetDir.findFile(fileName) ?: createKotlinFile(fileName, targetDir)) as? JetFile + fun createKotlinFile(fileName: String, targetDir: PsiDirectory): JetFile { val packageName = targetDir.getPackage()?.getQualifiedName()