Create From Usage: Generate Unit-typed declaration if result is unused
#KT-5903 Fixed
This commit is contained in:
+17
-14
@@ -59,6 +59,8 @@ import org.jetbrains.jet.plugin.util.application.runWriteAction
|
|||||||
import org.jetbrains.jet.plugin.refactoring.isMultiLine
|
import org.jetbrains.jet.plugin.refactoring.isMultiLine
|
||||||
import org.jetbrains.kotlin.util.printAndReturn
|
import org.jetbrains.kotlin.util.printAndReturn
|
||||||
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
|
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 TYPE_PARAMETER_LIST_VARIABLE_NAME = "typeParameterList"
|
||||||
private val TEMPLATE_FROM_USAGE_FUNCTION_BODY = "New Kotlin Function Body.kt"
|
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 {
|
private inner class Context {
|
||||||
val isUnit: Boolean
|
val skipReturnType: Boolean
|
||||||
val isExtension: Boolean
|
val isExtension: Boolean
|
||||||
val containingFile: JetFile
|
val containingFile: JetFile
|
||||||
val containingFileEditor: Editor
|
val containingFileEditor: Editor
|
||||||
@@ -209,8 +211,6 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
containingFileEditor = config.currentEditor
|
containingFileEditor = config.currentEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
isUnit = config.callableInfo.returnTypeInfo.let { it is TypeInfo.ByType && it.theType.isUnit() }
|
|
||||||
|
|
||||||
val scope = if (isExtension || receiverClassDescriptor == null) {
|
val scope = if (isExtension || receiverClassDescriptor == null) {
|
||||||
currentFileModule.getPackage(config.currentFile.getPackageFqName())!!.getMemberScope()
|
currentFileModule.getPackage(config.currentFile.getPackageFqName())!!.getMemberScope()
|
||||||
}
|
}
|
||||||
@@ -226,21 +226,22 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
val substitutions = ownerTypeArguments.zip(classTypeParameters).map {
|
val substitutions = ownerTypeArguments.zip(classTypeParameters).map {
|
||||||
JetTypeSubstitution(it.first.getType(), it.second.getType())
|
JetTypeSubstitution(it.first.getType(), it.second.getType())
|
||||||
}.copyToArray()
|
}.copyToArray()
|
||||||
config.callableInfo.parameterInfos.forEach {
|
config.callableInfo.parameterInfos.forEach {
|
||||||
parameter ->
|
computeTypeCandidates(it.typeInfo, substitutions, scope)
|
||||||
computeTypeCandidates(parameter.typeInfo, substitutions, scope)
|
|
||||||
}
|
|
||||||
if (!isUnit) {
|
|
||||||
computeTypeCandidates(config.callableInfo.returnTypeInfo, 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
|
// now that we have done substitutions, we can throw it away
|
||||||
receiverTypeCandidate = receiverType?.let { TypeCandidate(it, scope) }
|
receiverTypeCandidate = receiverType?.let { TypeCandidate(it, scope) }
|
||||||
|
|
||||||
// figure out type parameter renames to avoid conflicts
|
// figure out type parameter renames to avoid conflicts
|
||||||
typeParameterNameMap = getTypeParameterRenames(scope)
|
typeParameterNameMap = getTypeParameterRenames(scope)
|
||||||
config.callableInfo.parameterInfos.forEach { renderTypeCandidates(it.typeInfo, typeParameterNameMap) }
|
config.callableInfo.parameterInfos.forEach { renderTypeCandidates(it.typeInfo, typeParameterNameMap) }
|
||||||
if (!isUnit) {
|
if (!skipReturnType) {
|
||||||
renderTypeCandidates(config.callableInfo.returnTypeInfo, typeParameterNameMap)
|
renderTypeCandidates(config.callableInfo.returnTypeInfo, typeParameterNameMap)
|
||||||
}
|
}
|
||||||
receiverTypeCandidate?.render(typeParameterNameMap)
|
receiverTypeCandidate?.render(typeParameterNameMap)
|
||||||
@@ -268,7 +269,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
CallableKind.PROPERTY ->
|
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 header = "$ownerTypeString${callableInfo.name}$paramList$returnTypeString"
|
||||||
|
|
||||||
val psiFactory = JetPsiFactory(currentFile)
|
val psiFactory = JetPsiFactory(currentFile)
|
||||||
@@ -366,7 +367,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
.flatMap { it.typeParameters.stream() }
|
.flatMap { it.typeParameters.stream() }
|
||||||
.toCollection(allTypeParametersNotInScope)
|
.toCollection(allTypeParametersNotInScope)
|
||||||
|
|
||||||
if (!isUnit) {
|
if (!skipReturnType) {
|
||||||
computeTypeCandidates(config.callableInfo.returnTypeInfo).stream().flatMapTo(allTypeParametersNotInScope) { it.typeParameters.stream() }
|
computeTypeCandidates(config.callableInfo.returnTypeInfo).stream().flatMapTo(allTypeParametersNotInScope) { it.typeParameters.stream() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +433,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
private fun setupFunctionBody(func: JetNamedFunction) {
|
private fun setupFunctionBody(func: JetNamedFunction) {
|
||||||
val fileTemplate = FileTemplateManager.getInstance()!!.getCodeTemplate(TEMPLATE_FROM_USAGE_FUNCTION_BODY)
|
val fileTemplate = FileTemplateManager.getInstance()!!.getCodeTemplate(TEMPLATE_FROM_USAGE_FUNCTION_BODY)
|
||||||
val properties = Properties()
|
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 {
|
receiverClassDescriptor?.let {
|
||||||
properties.setProperty(FileTemplate.ATTRIBUTE_CLASS_NAME, DescriptorUtils.getFqName(it).asString())
|
properties.setProperty(FileTemplate.ATTRIBUTE_CLASS_NAME, DescriptorUtils.getFqName(it).asString())
|
||||||
properties.setProperty(FileTemplate.ATTRIBUTE_SIMPLE_CLASS_NAME, it.getName().asString())
|
properties.setProperty(FileTemplate.ATTRIBUTE_SIMPLE_CLASS_NAME, it.getName().asString())
|
||||||
@@ -554,7 +555,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
if (declaration is JetProperty) {
|
if (declaration is JetProperty) {
|
||||||
setupValVarTemplate(builder, declaration)
|
setupValVarTemplate(builder, declaration)
|
||||||
}
|
}
|
||||||
val returnTypeExpression = if (isUnit) null else setupReturnTypeTemplate(builder, declaration)
|
val returnTypeExpression = if (skipReturnType) null else setupReturnTypeTemplate(builder, declaration)
|
||||||
val parameterTypeExpressions =
|
val parameterTypeExpressions =
|
||||||
setupParameterTypeTemplates(builder, declaration.getValueParameterList()?.getParameters() ?: Collections.emptyList())
|
setupParameterTypeTemplates(builder, declaration.getValueParameterList()?.getParameters() ?: Collections.emptyList())
|
||||||
|
|
||||||
@@ -581,6 +582,8 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
// run the template
|
// run the template
|
||||||
TemplateManager.getInstance(project).startTemplate(containingFileEditor, templateImpl, object : TemplateEditingAdapter() {
|
TemplateManager.getInstance(project).startTemplate(containingFileEditor, templateImpl, object : TemplateEditingAdapter() {
|
||||||
override fun templateFinished(template: Template?, brokenOff: Boolean) {
|
override fun templateFinished(template: Template?, brokenOff: Boolean) {
|
||||||
|
PsiDocumentManager.getInstance(project).commitDocument(containingFileEditor.getDocument())
|
||||||
|
|
||||||
// file templates
|
// file templates
|
||||||
val offset = templateImpl.getSegmentOffset(0)
|
val offset = templateImpl.getSegmentOffset(0)
|
||||||
val newDeclaration = PsiTreeUtil.findElementOfClassAtOffset(
|
val newDeclaration = PsiTreeUtil.findElementOfClassAtOffset(
|
||||||
|
|||||||
+5
@@ -23,6 +23,9 @@ import org.jetbrains.jet.lang.types.Variance
|
|||||||
import org.jetbrains.jet.lang.types.TypeProjectionImpl
|
import org.jetbrains.jet.lang.types.TypeProjectionImpl
|
||||||
import org.jetbrains.jet.lang.types.JetTypeImpl
|
import org.jetbrains.jet.lang.types.JetTypeImpl
|
||||||
import org.jetbrains.jet.lang.psi.psiUtil.getAssignmentByLHS
|
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 {
|
private fun JetType.contains(inner: JetType): Boolean {
|
||||||
return JetTypeChecker.DEFAULT.equalTypes(this, inner) || getArguments().any { inner in it.getType() }
|
return JetTypeChecker.DEFAULT.equalTypes(this, inner) || getArguments().any { inner in it.getType() }
|
||||||
@@ -72,6 +75,8 @@ fun JetType.getTypeParameters(): Set<TypeParameterDescriptor> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun JetExpression.guessTypes(context: BindingContext): Array<JetType> {
|
fun JetExpression.guessTypes(context: BindingContext): Array<JetType> {
|
||||||
|
if (this !is JetDeclaration && isUsedAsStatement(context)) return array(KotlinBuiltIns.getInstance().getUnitType())
|
||||||
|
|
||||||
// if we know the actual type of the expression
|
// if we know the actual type of the expression
|
||||||
val theType1 = context[BindingContext.EXPRESSION_TYPE, this]
|
val theType1 = context[BindingContext.EXPRESSION_TYPE, this]
|
||||||
if (theType1 != null) {
|
if (theType1 != null) {
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
// "Create function 'plusAssign' from usage" "true"
|
// "Create function 'plusAssign' from usage" "true"
|
||||||
|
|
||||||
class A<T>(val n: T) {
|
class A<T>(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.
|
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
}
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// ERROR: Unresolved reference: B
|
// ERROR: Unresolved reference: B
|
||||||
|
|
||||||
class A: B {
|
class A: B {
|
||||||
fun foo(): Any {
|
fun foo() {
|
||||||
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
|
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// "Create function 'foo' from usage" "true"
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
<caret>foo(2, "2")
|
||||||
|
}
|
||||||
+9
@@ -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
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// "Create local variable 'foo'" "true"
|
||||||
|
// ACTION: Create parameter 'foo'
|
||||||
|
// ERROR: Variable 'foo' must be initialized
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val u: Unit = <caret>foo
|
||||||
|
}
|
||||||
@@ -880,6 +880,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("beforeUnknownType.kt")
|
||||||
public void testUnknownType() throws Exception {
|
public void testUnknownType() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/beforeUnknownType.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/beforeUnknownType.kt");
|
||||||
@@ -1269,6 +1275,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter")
|
||||||
|
|||||||
Reference in New Issue
Block a user