From 58ef7f2691ffb1ee5d932b5a5f2a453f7e10d9bf Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 25 May 2015 14:42:55 +0300 Subject: [PATCH] Extraction Engine: Suggest both bounds for nullability-flexible types without nullability annotations. Do not omit return type if inferred one is flexible #KT-6837 Fixed --- .../jetbrains/kotlin/idea/util/TypeUtils.kt | 10 ++-- .../ConvertToExpressionBodyIntention.kt | 31 ++++++------- .../ui/KotlinExtractFunctionDialog.form | 34 +++++++++++++- .../ui/KotlinExtractFunctionDialog.java | 46 +++++++++++++++++-- .../ExtractableCodeDescriptor.kt | 21 ++++++++- .../extractableAnalysisUtil.kt | 3 +- .../extractionEngine/extractorUtil.kt | 8 ++-- .../KotlinIntroduceParameterDialog.kt | 5 +- .../KotlinIntroducePropertyHandler.kt | 2 +- .../basic/callWithPlatformTypeReceiver.kt | 1 + .../callWithPlatformTypeReceiver.kt.after | 3 +- .../javaAnnotatedNotNull.java | 8 ++++ .../javaAnnotatedNotNull.kt | 5 ++ .../javaAnnotatedNotNull.kt.after | 7 +++ .../javaAnnotatedNullable.java | 8 ++++ .../javaAnnotatedNullable.kt | 4 ++ .../javaAnnotatedNullable.kt.after | 6 +++ .../javaNoNullabilityAnnotation.java | 5 ++ .../javaNoNullabilityAnnotation.kt | 5 ++ .../javaNoNullabilityAnnotation.kt.after | 7 +++ ...rameterNotResolvableInTargetScope.kt.after | 2 +- ...eParameterResolvableInTargetScope.kt.after | 2 +- .../introduce/AbstractJetExtractionTest.kt | 8 ++++ .../introduce/JetExtractionTestGenerated.java | 27 +++++++++++ 24 files changed, 221 insertions(+), 37 deletions(-) create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.java create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.java create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt.after create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.java create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt create mode 100644 idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt.after diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/TypeUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/TypeUtils.kt index dce21917d4a..f69c29cee03 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/TypeUtils.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/TypeUtils.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport import org.jetbrains.kotlin.load.java.JvmAnnotationNames.JETBRAINS_NOT_NULL_ANNOTATION +import org.jetbrains.kotlin.load.java.JvmAnnotationNames.JETBRAINS_NULLABLE_ANNOTATION import org.jetbrains.kotlin.load.java.JvmAnnotationNames.JETBRAINS_READONLY_ANNOTATION import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.platform.JavaToKotlinClassMap @@ -49,13 +50,13 @@ public fun approximateFlexibleTypes(jetType: JetType, outermost: Boolean = true) // Foo! -> Foo? var approximation = if (isCollection) - TypeUtils.makeNullableAsSpecified(if (jetType.isMarkedReadOnly()) flexible.upperBound else flexible.lowerBound, outermost) + TypeUtils.makeNullableAsSpecified(if (jetType.isAnnotatedReadOnly()) flexible.upperBound else flexible.lowerBound, outermost) else if (outermost) flexible.upperBound else flexible.lowerBound approximation = approximateFlexibleTypes(approximation) - approximation = if (jetType.isMarkedNotNull()) approximation.makeNotNullable() else approximation + approximation = if (jetType.isAnnotatedNotNull()) approximation.makeNotNullable() else approximation if (approximation.isMarkedNullable() && !flexible.lowerBound.isMarkedNullable() && TypeUtils.isTypeParameter(approximation) && TypeUtils.hasNullableSuperType(approximation)) { approximation = approximation.makeNotNullable() @@ -72,8 +73,9 @@ public fun approximateFlexibleTypes(jetType: JetType, outermost: Boolean = true) ) } -private fun JetType.isMarkedReadOnly() = hasAnnotationMaybeExternal(JETBRAINS_READONLY_ANNOTATION) -private fun JetType.isMarkedNotNull() = hasAnnotationMaybeExternal(JETBRAINS_NOT_NULL_ANNOTATION) +public fun JetType.isAnnotatedReadOnly(): Boolean = hasAnnotationMaybeExternal(JETBRAINS_READONLY_ANNOTATION) +public fun JetType.isAnnotatedNotNull(): Boolean = hasAnnotationMaybeExternal(JETBRAINS_NOT_NULL_ANNOTATION) +public fun JetType.isAnnotatedNullable(): Boolean = hasAnnotationMaybeExternal(JETBRAINS_NULLABLE_ANNOTATION) private fun JetType.hasAnnotationMaybeExternal(fqName: FqName) = with (getAnnotations()) { findAnnotation(fqName) ?: findExternalAnnotation(fqName) diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyIntention.kt index 1fde9d8ee01..9cf14b60828 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyIntention.kt @@ -43,14 +43,24 @@ public class ConvertToExpressionBodyIntention : JetSelfTargetingOffsetIndependen } override fun applyTo(element: JetDeclarationWithBody, editor: Editor) { - applyToInternal(element, editor) + applyToInternal(element) { + val typeRef = it.getTypeReference()!! + val colon = it.getColon()!! + val range = TextRange(colon.startOffset, typeRef.endOffset) + editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset()) + editor.getCaretModel().moveToOffset(range.getEndOffset()) + } } - public fun applyTo(declaration: JetDeclarationWithBody) { - applyToInternal(declaration, null) + public fun applyTo(declaration: JetDeclarationWithBody, canDeleteTypeRef: Boolean) { + applyToInternal(declaration) { + if (canDeleteTypeRef) { + it.deleteChildRange(it.getColon()!!, it.getTypeReference()!!) + } + } } - private fun applyToInternal(declaration: JetDeclarationWithBody, editor: Editor?) { + private fun applyToInternal(declaration: JetDeclarationWithBody, onFinish: (JetCallableDeclaration) -> Unit) { val value = calcValue(declaration)!! if (!declaration.hasDeclaredReturnType() && declaration is JetNamedFunction) { @@ -67,18 +77,7 @@ public class ConvertToExpressionBodyIntention : JetSelfTargetingOffsetIndependen body.replace(value) if (omitType) { - declaration as JetCallableDeclaration - - val typeRef = declaration.getTypeReference()!! - val colon = declaration.getColon()!! - if (editor != null) { - val range = TextRange(colon.startOffset, typeRef.endOffset) - editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset()) - editor.getCaretModel().moveToOffset(range.getEndOffset()) - } - else { - (declaration as PsiElement).deleteChildRange(colon, typeRef) - } + onFinish(declaration as JetCallableDeclaration) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.form b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.form index 9aeb9a38b53..f8151c44fad 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.form +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.form @@ -3,7 +3,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -125,6 +125,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java index 0223100f434..eba479464fd 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.idea.core.refactoring.RefactoringPackage; import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle; import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*; import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers; +import org.jetbrains.kotlin.types.JetType; import javax.swing.*; import java.awt.*; @@ -53,6 +54,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { private JPanel functionNamePanel; private NameSuggestionsField functionNameField; private JLabel functionNameLabel; + private JComboBox returnTypeBox; + private JPanel returnTypePanel; private KotlinParameterTablePanel parameterTablePanel; private final Project project; @@ -139,6 +142,40 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { functionNamePanel.add(functionNameField, BorderLayout.CENTER); functionNameLabel.setLabelFor(functionNameField); + List possibleReturnTypes = ExtractionEnginePackage.getPossibleReturnTypes(extractableCodeDescriptor.getControlFlow()); + if (possibleReturnTypes.size() > 1) { + DefaultComboBoxModel returnTypeBoxModel = new DefaultComboBoxModel(possibleReturnTypes.toArray()); + returnTypeBox.setModel(returnTypeBoxModel); + returnTypeBox.setRenderer( + new DefaultListCellRenderer() { + @NotNull + @Override + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus + ) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + setText(IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType((JetType) value)); + return this; + } + } + ); + returnTypeBox.addItemListener( + new ItemListener() { + @Override + public void itemStateChanged(@NotNull ItemEvent e) { + update(); + } + } + ); + } + else { + returnTypePanel.getParent().remove(returnTypePanel); + } + boolean enableVisibility = isVisibilitySectionAvailable(); visibilityBox.setEnabled(enableVisibility); if (enableVisibility) { @@ -217,7 +254,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { getFunctionName(), getVisibility(), parameterTablePanel.getReceiverInfo(), - parameterTablePanel.getParameterInfos()); + parameterTablePanel.getParameterInfos(), + (JetType) returnTypeBox.getSelectedItem()); } @NotNull @@ -230,7 +268,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { @NotNull String newName, @NotNull String newVisibility, @Nullable KotlinParameterTablePanel.ParameterInfo newReceiverInfo, - @NotNull List newParameterInfos + @NotNull List newParameterInfos, + @Nullable JetType returnType ) { Map oldToNewParameters = ContainerUtil.newLinkedHashMap(); for (KotlinParameterTablePanel.ParameterInfo parameterInfo : newParameterInfos) { @@ -283,7 +322,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper { newReceiver, originalDescriptor.getTypeParameters(), replacementMap, - controlFlow + controlFlow, + returnType != null ? returnType : originalDescriptor.getReturnType() ); } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt index d4ba98b3c59..a1b91102dfd 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt @@ -32,6 +32,9 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputVa import org.jetbrains.kotlin.descriptors.ModuleDescriptor import kotlin.properties.Delegates import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes +import org.jetbrains.kotlin.idea.util.isAnnotatedNotNull +import org.jetbrains.kotlin.idea.util.isAnnotatedNullable import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiRange import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.idea.util.isUnit @@ -291,6 +294,19 @@ data class ControlFlow( } } +val ControlFlow.possibleReturnTypes: List + get() { + val returnType = outputValueBoxer.returnType + return when { + !returnType.isNullabilityFlexible() -> + listOf(returnType) + returnType.isAnnotatedNotNull(), returnType.isAnnotatedNullable() -> + listOf(approximateFlexibleTypes(returnType)) + else -> + returnType.getCapability(javaClass()).let { listOf(it.upperBound, it.lowerBound) } + } + } + fun ControlFlow.toDefault(): ControlFlow = copy(outputValues = outputValues.filterNot { it is OutputValue.Jump || it is OutputValue.ExpressionValue }) @@ -303,7 +319,8 @@ data class ExtractableCodeDescriptor( val receiverParameter: Parameter?, val typeParameters: List, val replacementMap: Map, - val controlFlow: ControlFlow + val controlFlow: ControlFlow, + val returnType: JetType ) { val name: String get() = suggestedNames.firstOrNull() ?: "" val duplicates: List by Delegates.lazy { findDuplicates() } @@ -365,7 +382,7 @@ enum class ExtractionTarget(val name: String) { fun checkSignatureAndParent(descriptor: ExtractableCodeDescriptor): Boolean { if (!descriptor.parameters.isEmpty()) return false - if (descriptor.controlFlow.outputValueBoxer.returnType.isUnit()) return false + if (descriptor.returnType.isUnit()) return false val parent = descriptor.extractionData.targetSibling.getParent() return (parent is JetFile || parent is JetClassBody) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index 02130481a85..aaa925e083f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -885,7 +885,8 @@ fun ExtractionData.performAnalysis(): AnalysisResult { receiverParameter, paramsInfo.typeParameters.sortBy { it.originalDeclaration.getName()!! }, paramsInfo.replacementMap, - if (messages.isEmpty()) controlFlow else controlFlow.toDefault() + if (messages.isEmpty()) controlFlow else controlFlow.toDefault(), + returnType ), if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR, messages diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index e8162f43957..28eb54d5136 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -45,6 +45,8 @@ import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.types.JetType +import org.jetbrains.kotlin.types.isFlexible +import org.jetbrains.kotlin.types.isNullabilityFlexible import java.util.ArrayList import java.util.Collections import java.util.HashMap @@ -85,7 +87,7 @@ fun ExtractionGeneratorConfiguration.getDeclarationText( parameter.getParameterType(descriptor.extractionData.options.allowSpecialClassNames).typeAsString()) } - with(descriptor.controlFlow.outputValueBoxer.returnType) { + with(descriptor.returnType) { if (isDefault() || isError() || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) { builder.noReturnType() } else { @@ -272,7 +274,7 @@ private fun makeCall( } else { val varNameValidator = JetNameValidatorImpl(block, anchorInBlock, JetNameValidatorImpl.Target.PROPERTIES) - val resultVal = JetNameSuggester.suggestNames(controlFlow.outputValueBoxer.returnType, varNameValidator, null).first() + val resultVal = JetNameSuggester.suggestNames(extractableDescriptor.returnType, varNameValidator, null).first() block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock) block.addBefore(newLine, anchorInBlock) controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal) @@ -541,7 +543,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( val bodyExpression = body.getStatements().singleOrNull() val bodyOwner = body.getParent() as JetDeclarationWithBody if (bodyExpression != null && !bodyExpression.isMultiLine() && convertToExpressionBody.isApplicableTo(bodyOwner)) { - convertToExpressionBody.applyTo(bodyOwner) + convertToExpressionBody.applyTo(bodyOwner, !descriptor.returnType.isFlexible()) } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt index 14d0c94a461..154e75cbfe9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt @@ -75,7 +75,7 @@ public class KotlinIntroduceParameterDialog private ( introduceParameterDescriptor, lambdaExtractionDescriptor, lambdaExtractionDescriptor.suggestedNames.copyToArray(), - listOf(lambdaExtractionDescriptor.controlFlow.outputValueBoxer.returnType), + listOf(lambdaExtractionDescriptor.returnType), helper ) @@ -275,7 +275,8 @@ public class KotlinIntroduceParameterDialog private ( chosenName, "", parameterTablePanel?.getReceiverInfo(), - parameterTablePanel?.getParameterInfos() ?: listOf() + parameterTablePanel?.getParameterInfos() ?: listOf(), + null ) val options = ExtractionGeneratorOptions.DEFAULT.copy( target = ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION, diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt index a5464df66e6..5701a34b76f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt @@ -88,7 +88,7 @@ public class KotlinIntroducePropertyHandler( project = project, title = INTRODUCE_PROPERTY, doNotChangeVar = false, - exprType = descriptor.controlFlow.outputValueBoxer.returnType, + exprType = descriptor.returnType, extractionResult = it, availableTargets = propertyTargets.filter { it.isAvailable(descriptor) } ) diff --git a/idea/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt b/idea/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt index 75ecb2db68d..514c149d396 100644 --- a/idea/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt +++ b/idea/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt @@ -1,4 +1,5 @@ // WITH_RUNTIME +// SUGGESTED_RETURN_TYPES: kotlin.Boolean?, kotlin.Boolean // PARAM_DESCRIPTOR: value-parameter val it: kotlin.Map.Entry<(Boolean..Boolean?), (Boolean..Boolean?)> defined in test. // PARAM_TYPES: kotlin.Map.Entry<(Boolean..Boolean?), (Boolean..Boolean?)> fun test() { diff --git a/idea/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.after b/idea/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.after index d257041f3c2..4146cbab390 100644 --- a/idea/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.after +++ b/idea/testData/refactoring/extractFunction/basic/callWithPlatformTypeReceiver.kt.after @@ -1,8 +1,9 @@ // WITH_RUNTIME +// SUGGESTED_RETURN_TYPES: kotlin.Boolean?, kotlin.Boolean // PARAM_DESCRIPTOR: value-parameter val it: kotlin.Map.Entry<(Boolean..Boolean?), (Boolean..Boolean?)> defined in test. // PARAM_TYPES: kotlin.Map.Entry<(Boolean..Boolean?), (Boolean..Boolean?)> fun test() { J.getMap().filter { b(it) } } -private fun b(it: Map.Entry) = it.getKey() \ No newline at end of file +private fun b(it: Map.Entry): Boolean? = it.getKey() \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.java b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.java new file mode 100644 index 00000000000..a642b82fe35 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.java @@ -0,0 +1,8 @@ +import org.jetbrains.annotations.NotNull; + +public class J { + @NotNull + static String notNull() { + return ""; + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt new file mode 100644 index 00000000000..bf9f63044a2 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME +// SUGGESTED_NAMES: s, getS +fun test() { + val s = J.notNull() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt.after new file mode 100644 index 00000000000..c9fd0689e12 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// SUGGESTED_NAMES: s, getS +fun test() { + val s = s() +} + +private fun s(): String = J.notNull() \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.java b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.java new file mode 100644 index 00000000000..a7184a0d228 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.java @@ -0,0 +1,8 @@ +import org.jetbrains.annotations.Nullable; + +public class J { + @Nullable + static String nullable() { + return null; + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt new file mode 100644 index 00000000000..3cbdab393dc --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt @@ -0,0 +1,4 @@ +// SUGGESTED_NAMES: s, getS +fun test() { + val s = J.nullable() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt.after new file mode 100644 index 00000000000..e7b5d46d31b --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt.after @@ -0,0 +1,6 @@ +// SUGGESTED_NAMES: s, getS +fun test() { + val s = s() +} + +private fun s(): String? = J.nullable() \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.java b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.java new file mode 100644 index 00000000000..07a2545450c --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.java @@ -0,0 +1,5 @@ +public class J { + static String unknown() { + return ""; + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt new file mode 100644 index 00000000000..361e372b613 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt @@ -0,0 +1,5 @@ +// SUGGESTED_NAMES: s, getS +// SUGGESTED_RETURN_TYPES: kotlin.String?, kotlin.String +fun test() { + val s = J.unknown() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt.after new file mode 100644 index 00000000000..9cc2fa455cb --- /dev/null +++ b/idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt.after @@ -0,0 +1,7 @@ +// SUGGESTED_NAMES: s, getS +// SUGGESTED_RETURN_TYPES: kotlin.String?, kotlin.String +fun test() { + val s = s() +} + +private fun s(): String? = J.unknown() \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/typeParameters/typeParameterNotResolvableInTargetScope.kt.after b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterNotResolvableInTargetScope.kt.after index d4af32e7b7a..a357e3394ad 100644 --- a/idea/testData/refactoring/extractFunction/typeParameters/typeParameterNotResolvableInTargetScope.kt.after +++ b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterNotResolvableInTargetScope.kt.after @@ -15,4 +15,4 @@ class Foo { } } -private fun Foo.t(l: String) = map[l] \ No newline at end of file +private fun Foo.t(l: String): T = map[l] \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt.after b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt.after index 56b4d7bb3f2..31579c65aca 100644 --- a/idea/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt.after +++ b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt.after @@ -11,5 +11,5 @@ class Foo { return t(l) } - private fun t(l: String) = map[l] + private fun t(l: String): T = map[l] } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt index dad34d84618..a68dc4a867d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt @@ -46,6 +46,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinI import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetNamedDeclaration import org.jetbrains.kotlin.renderer.DescriptorRenderer @@ -239,6 +240,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe val explicitPreviousSibling = file.findElementByComment("// SIBLING:") val fileText = file.getText() ?: "" val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ") + val expectedReturnTypes = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_RETURN_TYPES: ") val expectedDescriptors = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString() val expectedTypes = @@ -269,6 +271,9 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe ) { val descriptor = descriptorWithConflicts.descriptor val actualNames = descriptor.suggestedNames + val actualReturnTypes = descriptor.controlFlow.possibleReturnTypes.map { + IdeDescriptorRenderers.SOURCE_CODE.renderType(it) + } val allParameters = emptyOrSingletonList(descriptor.receiverParameter) + descriptor.parameters val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString() val actualTypes = allParameters.map { @@ -278,6 +283,9 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe if (actualNames.size() != 1 || expectedNames.isNotEmpty()) { assertEquals(expectedNames, actualNames, "Expected names mismatch.") } + if (actualReturnTypes.size() != 1 || expectedReturnTypes.isNotEmpty()) { + assertEquals(expectedReturnTypes, actualReturnTypes, "Expected return types mismatch.") + } assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.") assertEquals(expectedTypes, actualTypes, "Expected types mismatch.") diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java index a5d6c7c3188..36cf9fa72b0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -1050,6 +1050,33 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { } } + @TestMetadata("idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReturnTypeCandidates extends AbstractJetExtractionTest { + public void testAllFilesPresentInReturnTypeCandidates() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("javaAnnotatedNotNull.kt") + public void testJavaAnnotatedNotNull() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNotNull.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("javaAnnotatedNullable.kt") + public void testJavaAnnotatedNullable() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaAnnotatedNullable.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("javaNoNullabilityAnnotation.kt") + public void testJavaNoNullabilityAnnotation() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/controlFlow/returnTypeCandidates/javaNoNullabilityAnnotation.kt"); + doExtractFunctionTest(fileName); + } + } + @TestMetadata("idea/testData/refactoring/extractFunction/controlFlow/throws") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)