From eb594a28971b2f65b0f35bc292744d694b0c72fe Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Fri, 27 Mar 2015 13:42:43 +0300 Subject: [PATCH] Extract Function: Approximate non-resolvable types with nearest resolvable supertype when possible #KT-7120 Fixed --- .../kotlin/resolve/scopes/JetScopeUtils.java | 21 ++++---- .../kotlin/idea/util/ImportsUtils.kt | 0 .../jetbrains/kotlin/idea/util/TypeUtils.kt | 19 ++++++++ .../QuickFixFactoryForTypeMismatchError.java | 45 +++-------------- .../refactoring/JetNameValidatorImpl.java | 13 ++--- .../extractableAnalysisUtil.kt | 48 ++++++++++++------- .../debugger/tinyApp/outs/ceObject.out | 1 + .../compilingEvaluator/ceObject.kt | 3 ++ .../anonymousObject.kt.conflicts | 2 +- .../nonDenotableTypes/anonymousObject.kt | 16 ++----- .../anonymousObject.kt.after | 7 +++ .../anonymousObjectWithCall.kt | 15 ++++++ ...s => anonymousObjectWithCall.kt.conflicts} | 0 .../localClassWithSuperclass.kt | 10 ++++ .../localClassWithSuperclass.kt.after | 12 +++++ .../localClassWithSuperclassParameter.kt | 13 +++++ ...localClassWithSuperclassParameter.kt.after | 15 ++++++ ...lClassWithSuperclassParameterInLocalFun.kt | 13 +++++ ...WithSuperclassParameterInLocalFun.kt.after | 15 ++++++ ...sWithSuperclassParameterNoApproximation.kt | 11 +++++ ...classParameterNoApproximation.kt.conflicts | 1 + .../introduce/JetExtractionTestGenerated.java | 30 ++++++++++++ 22 files changed, 224 insertions(+), 86 deletions(-) rename idea/{idea-analysis => ide-common}/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt (100%) create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.after create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt rename idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/{anonymousObject.kt.conflicts => anonymousObjectWithCall.kt.conflicts} (100%) create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt.after create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt.after create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt.after create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt create mode 100644 idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt.conflicts diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/JetScopeUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/JetScopeUtils.java index bf95e912af8..1308b52a1b9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/JetScopeUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/JetScopeUtils.java @@ -20,15 +20,12 @@ import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.descriptors.*; -import org.jetbrains.kotlin.psi.JetClassBody; -import org.jetbrains.kotlin.psi.JetClassOrObject; -import org.jetbrains.kotlin.psi.JetExpression; -import org.jetbrains.kotlin.psi.JetFile; +import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.TraceBasedRedeclarationHandler; @@ -158,12 +155,12 @@ public final class JetScopeUtils { } @Nullable - public static JetScope getResolutionScope(@NotNull JetExpression expression, @NotNull AnalysisResult analysisResult) { - PsiElement parent = expression.getParent(); + public static JetScope getResolutionScope(@NotNull PsiElement element, @NotNull BindingContext context) { + PsiElement parent = element.getParent(); if (parent instanceof JetClassBody) { JetClassOrObject classOrObject = (JetClassOrObject) parent.getParent(); - ClassDescriptor classDescriptor = analysisResult.getBindingContext().get(BindingContext.CLASS, classOrObject); + ClassDescriptor classDescriptor = context.get(BindingContext.CLASS, classOrObject); if (classDescriptor instanceof ClassDescriptorWithResolutionScopes) { return ((ClassDescriptorWithResolutionScopes) classDescriptor).getScopeForMemberDeclarationResolution(); } @@ -171,10 +168,14 @@ public final class JetScopeUtils { } if (parent instanceof JetFile) { - PackageViewDescriptor packageView = analysisResult.getModuleDescriptor().getPackage(((JetFile) parent).getPackageFqName()); + PackageFragmentDescriptor packageFragment = context.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, (JetFile) parent); + if (packageFragment == null) return null; + + PackageViewDescriptor packageView = packageFragment.getContainingDeclaration().getPackage(((JetFile) parent).getPackageFqName()); return packageView != null ? packageView.getMemberScope() : null; } - return analysisResult.getBindingContext().get(BindingContext.RESOLUTION_SCOPE, expression); + JetExpression expression = PsiTreeUtil.getParentOfType(element, JetExpression.class, false); + return expression != null ? context.get(BindingContext.RESOLUTION_SCOPE, expression) : null; } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt similarity index 100% rename from idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt rename to idea/ide-common/src/org/jetbrains/kotlin/idea/util/ImportsUtils.kt 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 4dd6affa275..7240b9e6efe 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 @@ -20,7 +20,10 @@ import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.resolve.jvm.kotlinSignature.CollectionClassMapping 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 +import org.jetbrains.kotlin.resolve.scopes.JetScope import java.util.LinkedHashSet import org.jetbrains.kotlin.types.typeUtil.substitute @@ -87,3 +90,19 @@ public fun JetType.nullability(): TypeNullability { else -> TypeNullability.NOT_NULL } } + +fun JetType.isResolvableInScope(scope: JetScope?, checkTypeParameters: Boolean): Boolean { + if (canBeReferencedViaImport()) return true + + val descriptor = getConstructor().getDeclarationDescriptor() + if (descriptor == null || descriptor.getName().isSpecial()) return false + if (!checkTypeParameters && descriptor is TypeParameterDescriptor) return true + + return scope != null && scope.getClassifier(descriptor.getName()) == descriptor +} + +public fun JetType.approximateWithResolvableType(scope: JetScope?, checkTypeParameters: Boolean): JetType { + if (isError() || isResolvableInScope(scope, checkTypeParameters)) return this + return supertypes().firstOrNull { it.isResolvableInScope(scope, checkTypeParameters) } + ?: KotlinBuiltIns.getInstance().getAnyType() +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.java b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.java index 9f3fde12fc8..ed71a936ded 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.java +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.java @@ -20,21 +20,15 @@ import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; -import kotlin.Function1; -import kotlin.KotlinPackage; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.analyzer.AnalysisResult; -import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.CallableDescriptor; -import org.jetbrains.kotlin.descriptors.ClassifierDescriptor; import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1; import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters2; import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages; import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; -import org.jetbrains.kotlin.idea.imports.ImportsPackage; import org.jetbrains.kotlin.idea.util.UtilPackage; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext; @@ -44,7 +38,6 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.scopes.JetScope; import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils; import org.jetbrains.kotlin.types.JetType; -import org.jetbrains.kotlin.types.TypeUtils; import java.util.Collections; import java.util.LinkedList; @@ -54,36 +47,12 @@ import java.util.List; public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFactory { private final static Logger LOG = Logger.getInstance(QuickFixFactoryForTypeMismatchError.class); - private static boolean isResolvableType(@NotNull JetType type, @Nullable JetScope scope) { - if (ImportsPackage.canBeReferencedViaImport(type)) return true; - - ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor(); - if (descriptor == null || descriptor.getName().isSpecial()) return false; - - return scope != null && scope.getClassifier(descriptor.getName()) == descriptor; - } - - private static JetType approximateWithResolvableType(@NotNull JetType type, @Nullable final JetScope scope) { - if (isResolvableType(type, scope)) return type; - JetType superType = KotlinPackage.firstOrNull( - TypeUtils.getAllSupertypes(type), - new Function1() { - @Override - public Boolean invoke(JetType type) { - return isResolvableType(type, scope); - } - } - ); - return superType != null ? superType : KotlinBuiltIns.getInstance().getAnyType(); - } - @NotNull @Override protected List doCreateActions(@NotNull Diagnostic diagnostic) { List actions = new LinkedList(); - AnalysisResult analysisResult = ResolvePackage.analyzeFullyAndGetResult((JetFile) diagnostic.getPsiFile()); - BindingContext context = analysisResult.getBindingContext(); + BindingContext context = ResolvePackage.analyzeFully((JetFile) diagnostic.getPsiFile()); PsiElement diagnosticElement = diagnostic.getPsiElement(); if (!(diagnosticElement instanceof JetExpression)) { @@ -133,8 +102,8 @@ public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFact JetExpression initializer = property.getInitializer(); if (QuickFixUtil.canEvaluateTo(initializer, expression) || (getter != null && QuickFixUtil.canFunctionOrGetterReturnExpression(property.getGetter(), expression))) { - JetScope scope = JetScopeUtils.getResolutionScope(property, analysisResult); - JetType typeToInsert = approximateWithResolvableType(expressionType, scope); + JetScope scope = JetScopeUtils.getResolutionScope(property, context); + JetType typeToInsert = UtilPackage.approximateWithResolvableType(expressionType, scope, false); actions.add(new ChangeVariableTypeFix(property, typeToInsert)); } } @@ -147,8 +116,8 @@ public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFact ? BindingContextUtilPackage.getTargetFunction((JetReturnExpression) expressionParent, context) : PsiTreeUtil.getParentOfType(expression, JetFunction.class, true); if (function instanceof JetFunction && QuickFixUtil.canFunctionOrGetterReturnExpression(function, expression)) { - JetScope scope = JetScopeUtils.getResolutionScope(function, analysisResult); - JetType typeToInsert = approximateWithResolvableType(expressionType, scope); + JetScope scope = JetScopeUtils.getResolutionScope(function, context); + JetType typeToInsert = UtilPackage.approximateWithResolvableType(expressionType, scope, false); actions.add(new ChangeFunctionReturnTypeFix((JetFunction) function, typeToInsert)); } @@ -187,8 +156,8 @@ public class QuickFixFactoryForTypeMismatchError extends JetIntentionActionsFact ? expressionType : context.get(BindingContext.EXPRESSION_TYPE, valueArgument.getArgumentExpression()); if (correspondingParameter != null && valueArgumentType != null) { - JetScope scope = JetScopeUtils.getResolutionScope(valueArgument.getArgumentExpression(), analysisResult); - JetType typeToInsert = approximateWithResolvableType(valueArgumentType, scope); + JetScope scope = JetScopeUtils.getResolutionScope(valueArgument.getArgumentExpression(), context); + JetType typeToInsert = UtilPackage.approximateWithResolvableType(valueArgumentType, scope, true); actions.add(new ChangeParameterTypeFix(correspondingParameter, typeToInsert)); } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/JetNameValidatorImpl.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/JetNameValidatorImpl.java index 4e785687269..505e8585185 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/JetNameValidatorImpl.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/JetNameValidatorImpl.java @@ -19,14 +19,11 @@ package org.jetbrains.kotlin.idea.refactoring; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.analyzer.AnalysisResult; -import org.jetbrains.kotlin.descriptors.ClassDescriptor; -import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes; -import org.jetbrains.kotlin.descriptors.PackageViewDescriptor; import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; import org.jetbrains.kotlin.name.Name; -import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.psi.JetElement; +import org.jetbrains.kotlin.psi.JetExpression; +import org.jetbrains.kotlin.psi.JetVisitorVoid; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.scopes.JetScope; import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils; @@ -76,7 +73,7 @@ public class JetNameValidatorImpl extends JetNameValidator { private boolean checkElement(String name, PsiElement sibling, final Set visitedScopes) { if (!(sibling instanceof JetElement)) return true; - final AnalysisResult analysisResult = ResolvePackage.analyzeAndGetResult((JetElement) sibling); + final BindingContext context = ResolvePackage.analyze((JetElement) sibling); final Name identifier = Name.identifier(name); final Ref result = new Ref(true); @@ -90,7 +87,7 @@ public class JetNameValidatorImpl extends JetNameValidator { @Override public void visitExpression(@NotNull JetExpression expression) { - JetScope resolutionScope = JetScopeUtils.getResolutionScope(expression, analysisResult); + JetScope resolutionScope = JetScopeUtils.getResolutionScope(expression, context); if (resolutionScope != null) { if (!visitedScopes.add(resolutionScope)) return; 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 ca35c8470c0..92487ee93ba 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 @@ -41,7 +41,6 @@ import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde -import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport import org.jetbrains.kotlin.idea.imports.importableFqNameSafe import org.jetbrains.kotlin.idea.refactoring.* import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage @@ -52,6 +51,8 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputVa import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ParameterUpdate import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValueBoxer.AsList import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.approximateWithResolvableType +import org.jetbrains.kotlin.idea.util.isResolvableInScope import org.jetbrains.kotlin.idea.util.makeNullable import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType @@ -63,6 +64,8 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement import org.jetbrains.kotlin.resolve.calls.CallTransformer import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny +import org.jetbrains.kotlin.resolve.scopes.JetScope +import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver import org.jetbrains.kotlin.types.* @@ -127,7 +130,9 @@ private fun List.getExitPoints(): List = private fun List.getResultTypeAndExpressions( bindingContext: BindingContext, - options: ExtractionOptions): Pair> { + targetScope: JetScope?, + options: ExtractionOptions +): Pair> { fun instructionToExpression(instruction: Instruction, unwrapReturn: Boolean): JetExpression? { return when (instruction) { is ReturnValueInstruction -> @@ -148,7 +153,9 @@ private fun List.getResultTypeAndExpressions( } val resultTypes = map(::instructionToType).filterNotNull() - val resultType = if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else DEFAULT_RETURN_TYPE + var commonSupertype = if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else DEFAULT_RETURN_TYPE + val resultType = if (options.allowSpecialClassNames) commonSupertype else commonSupertype.approximateWithResolvableType(targetScope, false) + val expressions = map { instructionToExpression(it, false) }.filterNotNull() return resultType to expressions @@ -217,6 +224,7 @@ private fun ExtractionData.analyzeControlFlow( bindingContext: BindingContext, modifiedVarDescriptors: Map>, options: ExtractionOptions, + targetScope: JetScope?, parameters: Set ): Pair { val exitPoints = localInstructions.getExitPoints() @@ -269,8 +277,8 @@ private fun ExtractionData.analyzeControlFlow( val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext) val (declarationsToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is JetProperty && it.isLocal() } - val (typeOfDefaultFlow, defaultResultExpressions) = defaultExits.getResultTypeAndExpressions(bindingContext, options) - val (returnValueType, valuedReturnExpressions) = valuedReturnExits.getResultTypeAndExpressions(bindingContext, options) + val (typeOfDefaultFlow, defaultResultExpressions) = defaultExits.getResultTypeAndExpressions(bindingContext, targetScope, options) + val (returnValueType, valuedReturnExpressions) = valuedReturnExits.getResultTypeAndExpressions(bindingContext, targetScope, options) val emptyControlFlow = ControlFlow(Collections.emptyList(), { OutputValueBoxer.AsTuple(it, module) }, declarationsToCopy) @@ -421,14 +429,14 @@ fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List val parameterTypeDescriptor = typeToCheck.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor val typeParameter = parameterTypeDescriptor?.let { DescriptorToSourceUtils.descriptorToDeclaration(it) } as? JetTypeParameter - extractable && (typeParameter != null || typeToCheck.canBeReferencedViaImport()) + extractable && (typeParameter != null || typeToCheck.isResolvableInScope(targetScope, false)) } } @@ -436,6 +444,7 @@ private fun JetType.processTypeIfExtractable( typeParameters: MutableSet, nonDenotableTypes: MutableSet, options: ExtractionOptions, + targetScope: JetScope?, processTypeArguments: Boolean = true ): Boolean { return collectReferencedTypes(processTypeArguments).fold(true) { (extractable, typeToCheck) -> @@ -450,7 +459,7 @@ private fun JetType.processTypeIfExtractable( extractable } - typeToCheck.canBeReferencedViaImport() -> + typeToCheck.isResolvableInScope(targetScope, false) -> extractable options.allowSpecialClassNames && typeToCheck.isSpecial() -> @@ -470,7 +479,8 @@ private fun JetType.processTypeIfExtractable( private class MutableParameter( override val argumentText: String, override val originalDescriptor: DeclarationDescriptor, - override val receiverCandidate: Boolean + override val receiverCandidate: Boolean, + private val targetScope: JetScope? ): Parameter { // All modifications happen in the same thread private var writable: Boolean = true @@ -525,7 +535,7 @@ private class MutableParameter( override fun getParameterTypeCandidates(allowSpecialClassNames: Boolean): List { return if (!allowSpecialClassNames) { - parameterTypeCandidates.filter { it.isExtractable() } + parameterTypeCandidates.filter { it.isExtractable(targetScope) } } else { parameterTypeCandidates } @@ -560,6 +570,7 @@ private fun ExtractionData.inferParametersInfo( commonParent: PsiElement, pseudocode: Pseudocode, bindingContext: BindingContext, + targetScope: JetScope?, modifiedVarDescriptors: Set ): ParametersInfo { val info = ParametersInfo() @@ -610,7 +621,7 @@ private fun ExtractionData.inferParametersInfo( if (referencedClassDescriptor != null) { if (!referencedClassDescriptor.getDefaultType().processTypeIfExtractable( - info.typeParameters, info.nonDenotableTypes, options, false + info.typeParameters, info.nonDenotableTypes, options, targetScope, false )) continue info.replacementMap[refInfo.offsetInBody] = FqNameReplacement(originalDescriptor.importableFqNameSafe) @@ -642,7 +653,7 @@ private fun ExtractionData.inferParametersInfo( else (thisExpr ?: ref).getText() ?: throw AssertionError("'this' reference shouldn't be empty: code fragment = $codeFragmentText") - MutableParameter(argumentText, descriptorToExtract, extractThis) + MutableParameter(argumentText, descriptorToExtract, extractThis, targetScope) } if (!extractThis) { @@ -681,7 +692,9 @@ private fun ExtractionData.inferParametersInfo( ) for ((descriptorToExtract, parameter) in extractedDescriptorToParameter) { - if (!parameter.getParameterType(options.allowSpecialClassNames).processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options)) continue + if (!parameter + .getParameterType(options.allowSpecialClassNames) + .processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options, targetScope)) continue with (parameter) { if (currentName == null) { @@ -693,7 +706,7 @@ private fun ExtractionData.inferParametersInfo( } for (typeToCheck in info.typeParameters.flatMapTo(HashSet()) { it.collectReferencedTypes(bindingContext) }) { - typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options) + typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes, options, targetScope) } @@ -765,6 +778,8 @@ fun ExtractionData.performAnalysis(): AnalysisResult { val bindingContext = commonParent.getContextForContainingDeclarationBody() if (bindingContext == null) return noContainerError + val targetScope = JetScopeUtils.getResolutionScope(targetSibling, bindingContext) + val pseudocodeDeclaration = PsiTreeUtil.getParentOfType(commonParent, javaClass(), javaClass()) ?: commonParent.getNonStrictParentOfType() @@ -786,7 +801,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult { val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext) - val paramsInfo = inferParametersInfo(commonParent, pseudocode, bindingContext, modifiedVarDescriptorsWithExpressions.keySet()) + val paramsInfo = inferParametersInfo(commonParent, pseudocode, bindingContext, targetScope, modifiedVarDescriptorsWithExpressions.keySet()) if (paramsInfo.errorMessage != null) { return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!)) } @@ -803,12 +818,13 @@ fun ExtractionData.performAnalysis(): AnalysisResult { bindingContext, modifiedVarDescriptorsForControlFlow, options, + targetScope, paramsInfo.parameters ) controlFlowMessage?.let { messages.add(it) } val returnType = controlFlow.outputValueBoxer.returnType - returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes, options) + returnType.processTypeIfExtractable(paramsInfo.typeParameters, paramsInfo.nonDenotableTypes, options, targetScope) if (paramsInfo.nonDenotableTypes.isNotEmpty()) { val typeStr = paramsInfo.nonDenotableTypes.map {it.renderForMessage()}.sort() diff --git a/idea/testData/debugger/tinyApp/outs/ceObject.out b/idea/testData/debugger/tinyApp/outs/ceObject.out index 3b86403374d..b6604c3ab36 100644 --- a/idea/testData/debugger/tinyApp/outs/ceObject.out +++ b/idea/testData/debugger/tinyApp/outs/ceObject.out @@ -3,6 +3,7 @@ LineBreakpoint created at ceObject.kt:5 Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' ceObject.kt:5 Compile bytecode for (object: T {}).test() +Compile bytecode for (object: T { fun a() = 1 }).a() Compile bytecode for object: T {} Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceObject.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceObject.kt index c40c516b416..9a298af3791 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceObject.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceObject.kt @@ -12,5 +12,8 @@ trait T { // EXPRESSION: (object: T {}).test() // RESULT: 1: I +// EXPRESSION: (object: T { fun a() = 1 }).a() +// RESULT: 1: I + // EXPRESSION: object: T {} // RESULT: instance of packageForDebugger.PackageForDebuggerPackage$debugFile$@packagePartHASH$myFun$1(id=ID): LpackageForDebugger/PackageForDebuggerPackage$debugFile$@packagePartHASH$myFun$1; \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/anonymousObject.kt.conflicts b/idea/testData/refactoring/extractFunction/controlFlow/unextractable/anonymousObject.kt.conflicts index 440cc5151f4..289b2cd07f8 100644 --- a/idea/testData/refactoring/extractFunction/controlFlow/unextractable/anonymousObject.kt.conflicts +++ b/idea/testData/refactoring/extractFunction/controlFlow/unextractable/anonymousObject.kt.conflicts @@ -1 +1 @@ -Cannot extract method since following types are not denotable in the target scope: <no name provided> \ No newline at end of file +Following declarations are used outside of selected code fragment: override fun call(): Int \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt index dec1f8a1436..5f51bf42fbb 100644 --- a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt @@ -1,15 +1,5 @@ -trait Callable { - fun call(): T -} +trait T -fun foo(a: Int): Int { - // SIBLING: - val o = object: Callable { - val b: Int = 1 - - override fun call(): Int { - return a + b - } - } - return o.call() +fun foo(): T { + return object: T() {} } \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.after b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.after new file mode 100644 index 00000000000..c188b4ca8dc --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.after @@ -0,0 +1,7 @@ +trait T + +fun foo(): T { + return t() +} + +private fun t() = object : T() {} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt new file mode 100644 index 00000000000..dec1f8a1436 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt @@ -0,0 +1,15 @@ +trait Callable { + fun call(): T +} + +fun foo(a: Int): Int { + // SIBLING: + val o = object: Callable { + val b: Int = 1 + + override fun call(): Int { + return a + b + } + } + return o.call() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.conflicts b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt.conflicts similarity index 100% rename from idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObject.kt.conflicts rename to idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt.conflicts diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt new file mode 100644 index 00000000000..bf535a64bce --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt @@ -0,0 +1,10 @@ +trait T + +fun foo(): T { + class A: T + + // SIBLING: + fun bar(): T { + return A() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt.after b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt.after new file mode 100644 index 00000000000..b92f1fd9896 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt.after @@ -0,0 +1,12 @@ +trait T + +fun foo(): T { + class A: T + + fun a() = A() + + // SIBLING: + fun bar(): T { + return a() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt new file mode 100644 index 00000000000..776e61fc8b0 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt @@ -0,0 +1,13 @@ +// PARAM_DESCRIPTOR: val a: foo.A defined in foo.bar +// PARAM_TYPES: T +trait T + +// SIBLING: +fun foo(): T { + class A: T + + fun bar(): T { + val a = A() + return a + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt.after b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt.after new file mode 100644 index 00000000000..39fd527e2a7 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt.after @@ -0,0 +1,15 @@ +// PARAM_DESCRIPTOR: val a: foo.A defined in foo.bar +// PARAM_TYPES: T +trait T + +// SIBLING: +fun foo(): T { + class A: T + + fun bar(): T { + val a = A() + return t(a) + } +} + +private fun t(a: T) = a \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt new file mode 100644 index 00000000000..f85f38aa64e --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt @@ -0,0 +1,13 @@ +// PARAM_DESCRIPTOR: val a: foo.A defined in foo.bar +// PARAM_TYPES: foo.A, T +trait T + +fun foo(): T { + class A: T + + // SIBLING: + fun bar(): T { + val a = A() + return a + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt.after b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt.after new file mode 100644 index 00000000000..e32b95330c7 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt.after @@ -0,0 +1,15 @@ +// PARAM_DESCRIPTOR: val a: foo.A defined in foo.bar +// PARAM_TYPES: foo.A, T +trait T + +fun foo(): T { + class A: T + + fun a(a: A) = a + + // SIBLING: + fun bar(): T { + val a = A() + return a(a) + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt new file mode 100644 index 00000000000..ebeeb79a7f2 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt @@ -0,0 +1,11 @@ +trait T + +// SIBLING: +fun foo(): T { + class A: T + + fun bar(): A { + val a = A() + return a + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt.conflicts b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt.conflicts new file mode 100644 index 00000000000..353d432ac71 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt.conflicts @@ -0,0 +1 @@ +Cannot extract method since following types are not denotable in the target scope: A \ No newline at end of file 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 401660466fc..8de36ca42a3 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -1897,11 +1897,41 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest(fileName); } + @TestMetadata("anonymousObjectWithCall.kt") + public void testAnonymousObjectWithCall() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/anonymousObjectWithCall.kt"); + doExtractFunctionTest(fileName); + } + @TestMetadata("localClass.kt") public void testLocalClass() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClass.kt"); doExtractFunctionTest(fileName); } + + @TestMetadata("localClassWithSuperclass.kt") + public void testLocalClassWithSuperclass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclass.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("localClassWithSuperclassParameter.kt") + public void testLocalClassWithSuperclassParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameter.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("localClassWithSuperclassParameterInLocalFun.kt") + public void testLocalClassWithSuperclassParameterInLocalFun() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterInLocalFun.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("localClassWithSuperclassParameterNoApproximation.kt") + public void testLocalClassWithSuperclassParameterNoApproximation() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/nonDenotableTypes/localClassWithSuperclassParameterNoApproximation.kt"); + doExtractFunctionTest(fileName); + } } }