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 cbe83bb9cfa..1f8899d5080 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 @@ -59,6 +59,8 @@ import org.jetbrains.jet.plugin.util.application.runWriteAction import org.jetbrains.jet.plugin.refactoring.isMultiLine import org.jetbrains.kotlin.util.printAndReturn import org.jetbrains.jet.lang.types.checker.JetTypeChecker +import org.jetbrains.jet.plugin.intentions.declarations.DeclarationUtils +import org.jetbrains.jet.plugin.intentions.SpecifyTypeExplicitlyAction private val TYPE_PARAMETER_LIST_VARIABLE_NAME = "typeParameterList" private val TEMPLATE_FROM_USAGE_FUNCTION_BODY = "New Kotlin Function Body.kt" @@ -171,7 +173,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { } private inner class Context { - val isUnit: Boolean + val skipReturnType: Boolean val isExtension: Boolean val containingFile: JetFile val containingFileEditor: Editor @@ -209,8 +211,6 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { containingFileEditor = config.currentEditor } - isUnit = config.callableInfo.returnTypeInfo.let { it is TypeInfo.ByType && it.theType.isUnit() } - val scope = if (isExtension || receiverClassDescriptor == null) { currentFileModule.getPackage(config.currentFile.getPackageFqName())!!.getMemberScope() } @@ -226,21 +226,22 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { val substitutions = ownerTypeArguments.zip(classTypeParameters).map { JetTypeSubstitution(it.first.getType(), it.second.getType()) }.copyToArray() - config.callableInfo.parameterInfos.forEach { - parameter -> - computeTypeCandidates(parameter.typeInfo, substitutions, scope) - } - if (!isUnit) { - computeTypeCandidates(config.callableInfo.returnTypeInfo, substitutions, scope) + config.callableInfo.parameterInfos.forEach { + computeTypeCandidates(it.typeInfo, substitutions, scope) } + val returnTypeCandidates = computeTypeCandidates(config.callableInfo.returnTypeInfo, substitutions, scope) + skipReturnType = config.callableInfo is FunctionInfo + && returnTypeCandidates.size == 1 + && returnTypeCandidates.first().theType.isUnit() + // now that we have done substitutions, we can throw it away receiverTypeCandidate = receiverType?.let { TypeCandidate(it, scope) } // figure out type parameter renames to avoid conflicts typeParameterNameMap = getTypeParameterRenames(scope) config.callableInfo.parameterInfos.forEach { renderTypeCandidates(it.typeInfo, typeParameterNameMap) } - if (!isUnit) { + if (!skipReturnType) { renderTypeCandidates(config.callableInfo.returnTypeInfo, typeParameterNameMap) } receiverTypeCandidate?.render(typeParameterNameMap) @@ -268,7 +269,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { CallableKind.PROPERTY -> "" } - val returnTypeString = if (isUnit || assignmentToReplace != null) "" else ": Any" + val returnTypeString = if (skipReturnType || assignmentToReplace != null) "" else ": Any" val header = "$ownerTypeString${callableInfo.name}$paramList$returnTypeString" val psiFactory = JetPsiFactory(currentFile) @@ -366,7 +367,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { .flatMap { it.typeParameters.stream() } .toCollection(allTypeParametersNotInScope) - if (!isUnit) { + if (!skipReturnType) { computeTypeCandidates(config.callableInfo.returnTypeInfo).stream().flatMapTo(allTypeParametersNotInScope) { it.typeParameters.stream() } } @@ -432,7 +433,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { private fun setupFunctionBody(func: JetNamedFunction) { val fileTemplate = FileTemplateManager.getInstance()!!.getCodeTemplate(TEMPLATE_FROM_USAGE_FUNCTION_BODY) val properties = Properties() - properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, if (isUnit) "Unit" else func.getTypeReference()!!.getText()) + properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, if (skipReturnType) "Unit" else func.getTypeReference()!!.getText()) receiverClassDescriptor?.let { properties.setProperty(FileTemplate.ATTRIBUTE_CLASS_NAME, DescriptorUtils.getFqName(it).asString()) properties.setProperty(FileTemplate.ATTRIBUTE_SIMPLE_CLASS_NAME, it.getName().asString()) @@ -554,7 +555,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { if (declaration is JetProperty) { setupValVarTemplate(builder, declaration) } - val returnTypeExpression = if (isUnit) null else setupReturnTypeTemplate(builder, declaration) + val returnTypeExpression = if (skipReturnType) null else setupReturnTypeTemplate(builder, declaration) val parameterTypeExpressions = setupParameterTypeTemplates(builder, declaration.getValueParameterList()?.getParameters() ?: Collections.emptyList()) @@ -581,6 +582,8 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { // run the template TemplateManager.getInstance(project).startTemplate(containingFileEditor, templateImpl, object : TemplateEditingAdapter() { override fun templateFinished(template: Template?, brokenOff: Boolean) { + PsiDocumentManager.getInstance(project).commitDocument(containingFileEditor.getDocument()) + // file templates val offset = templateImpl.getSegmentOffset(0) val newDeclaration = PsiTreeUtil.findElementOfClassAtOffset( 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 87eb3f6e5d3..8bb2083f665 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 @@ -23,6 +23,9 @@ import org.jetbrains.jet.lang.types.Variance import org.jetbrains.jet.lang.types.TypeProjectionImpl import org.jetbrains.jet.lang.types.JetTypeImpl import org.jetbrains.jet.lang.psi.psiUtil.getAssignmentByLHS +import org.jetbrains.jet.lang.resolve.bindingContextUtil.isUsedAsStatement +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns +import org.jetbrains.jet.lang.psi.JetDeclaration private fun JetType.contains(inner: JetType): Boolean { return JetTypeChecker.DEFAULT.equalTypes(this, inner) || getArguments().any { inner in it.getType() } @@ -72,6 +75,8 @@ fun JetType.getTypeParameters(): Set { } fun JetExpression.guessTypes(context: BindingContext): Array { + if (this !is JetDeclaration && isUsedAsStatement(context)) return array(KotlinBuiltIns.getInstance().getUnitType()) + // if we know the actual type of the expression val theType1 = context[BindingContext.EXPRESSION_TYPE, this] if (theType1 != null) { diff --git a/idea/testData/quickfix/createFromUsage/createFunction/binaryOperations/afterPlusAssignOnUserType.kt b/idea/testData/quickfix/createFromUsage/createFunction/binaryOperations/afterPlusAssignOnUserType.kt index b35a70c8e01..a180988525c 100644 --- a/idea/testData/quickfix/createFromUsage/createFunction/binaryOperations/afterPlusAssignOnUserType.kt +++ b/idea/testData/quickfix/createFromUsage/createFunction/binaryOperations/afterPlusAssignOnUserType.kt @@ -1,7 +1,7 @@ // "Create function 'plusAssign' from usage" "true" class A(val n: T) { - fun plusAssign(arg: T): Unit { + fun plusAssign(arg: T) { throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. } } diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/afterUnitFun.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/afterUnitFun.kt new file mode 100644 index 00000000000..b6e552934c4 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/afterUnitFun.kt @@ -0,0 +1,9 @@ +// "Create function 'foo' from usage" "true" + +fun test() { + foo(2, "2") +} + +fun foo(i: Int, s: String) { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/afterUnresolvedSupertype.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/afterUnresolvedSupertype.kt index 6996a1c5579..c23c71c838d 100644 --- a/idea/testData/quickfix/createFromUsage/createFunction/call/afterUnresolvedSupertype.kt +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/afterUnresolvedSupertype.kt @@ -2,7 +2,7 @@ // ERROR: Unresolved reference: B class A: B { - fun foo(): Any { + fun foo() { throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. } diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/beforeUnitFun.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/beforeUnitFun.kt new file mode 100644 index 00000000000..12914a36cdb --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/beforeUnitFun.kt @@ -0,0 +1,5 @@ +// "Create function 'foo' from usage" "true" + +fun test() { + foo(2, "2") +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/localVariable/afterUnitTypedInFun.kt b/idea/testData/quickfix/createFromUsage/createVariable/localVariable/afterUnitTypedInFun.kt new file mode 100644 index 00000000000..ccd8e5be17f --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/localVariable/afterUnitTypedInFun.kt @@ -0,0 +1,9 @@ +// "Create local variable 'foo'" "true" +// ACTION: Create parameter 'foo' +// ERROR: Variable 'foo' must be initialized + +fun test() { + val foo: Unit + + val u: Unit = foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/localVariable/beforeUnitTypedInFun.kt b/idea/testData/quickfix/createFromUsage/createVariable/localVariable/beforeUnitTypedInFun.kt new file mode 100644 index 00000000000..011d3d6382c --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/localVariable/beforeUnitTypedInFun.kt @@ -0,0 +1,7 @@ +// "Create local variable 'foo'" "true" +// ACTION: Create parameter 'foo' +// ERROR: Variable 'foo' must be initialized + +fun test() { + val u: Unit = foo +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java index 7445b205f01..254e149d1af 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java @@ -880,6 +880,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("beforeUnitFun.kt") + public void testUnitFun() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/beforeUnitFun.kt"); + doTest(fileName); + } + @TestMetadata("beforeUnknownType.kt") public void testUnknownType() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/beforeUnknownType.kt"); @@ -1269,6 +1275,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("beforeUnitTypedInFun.kt") + public void testUnitTypedInFun() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/localVariable/beforeUnitTypedInFun.kt"); + doTest(fileName); + } + } @TestMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter")