Move: Implement more accurate 'protected' visibility check
#KT-17545 Fixed
This commit is contained in:
+51
-11
@@ -50,13 +50,13 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
|||||||
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
|
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
|
||||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
import org.jetbrains.kotlin.utils.SmartSet
|
import org.jetbrains.kotlin.utils.SmartSet
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class MoveConflictChecker(
|
class MoveConflictChecker(
|
||||||
@@ -327,27 +327,67 @@ class MoveConflictChecker(
|
|||||||
|
|
||||||
private fun checkVisibilityInDeclarations(conflicts: MultiMap<PsiElement, String>) {
|
private fun checkVisibilityInDeclarations(conflicts: MultiMap<PsiElement, String>) {
|
||||||
val targetContainer = moveTarget.getContainerDescriptor() ?: return
|
val targetContainer = moveTarget.getContainerDescriptor() ?: return
|
||||||
|
|
||||||
|
fun DeclarationDescriptor.targetAwareContainingDescriptor(): DeclarationDescriptor? {
|
||||||
|
val defaultContainer = containingDeclaration
|
||||||
|
val psi = (this as? DeclarationDescriptorWithSource)?.source?.getPsi()
|
||||||
|
return if (psi != null && psi in allElementsToMove) targetContainer else defaultContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DeclarationDescriptor.targetAwareContainers(): Sequence<DeclarationDescriptor> {
|
||||||
|
return generateSequence(this) { it.targetAwareContainingDescriptor() }.drop(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DeclarationDescriptor.targetAwareContainingClass(): ClassDescriptor? {
|
||||||
|
return targetAwareContainers().firstIsInstanceOrNull<ClassDescriptor>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DeclarationDescriptorWithVisibility.isProtectedVisible(referrerDescriptor: DeclarationDescriptor): Boolean {
|
||||||
|
val givenClassDescriptor = targetAwareContainingClass()
|
||||||
|
val referrerClassDescriptor = referrerDescriptor.targetAwareContainingClass() ?: return false
|
||||||
|
if (givenClassDescriptor != null && givenClassDescriptor.isCompanionObject) {
|
||||||
|
val companionOwner = givenClassDescriptor.targetAwareContainingClass()
|
||||||
|
if (companionOwner != null && referrerClassDescriptor.isSubclassOf(companionOwner)) return true
|
||||||
|
}
|
||||||
|
val whatDeclaration = DescriptorUtils.unwrapFakeOverrideToAnyDeclaration(this)
|
||||||
|
val classDescriptor = whatDeclaration.targetAwareContainingClass() ?: return false
|
||||||
|
if (referrerClassDescriptor.isSubclassOf(classDescriptor)) return true
|
||||||
|
return referrerDescriptor.targetAwareContainingDescriptor()?.let { isProtectedVisible(it) } ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DeclarationDescriptorWithVisibility.isVisibleFrom(ref: PsiReference): Boolean {
|
||||||
|
val targetVisibility = visibility.normalize()
|
||||||
|
if (targetVisibility == Visibilities.PUBLIC) return true
|
||||||
|
|
||||||
|
return when (targetVisibility) {
|
||||||
|
Visibilities.PROTECTED -> {
|
||||||
|
val referrer = ref.element.getStrictParentOfType<KtDeclaration>()
|
||||||
|
val referrerDescriptor = referrer?.resolveToDescriptor() ?: return true
|
||||||
|
isProtectedVisible(referrerDescriptor)
|
||||||
|
}
|
||||||
|
else -> isVisibleIn(targetContainer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (declaration in elementsToMove - doNotGoIn) {
|
for (declaration in elementsToMove - doNotGoIn) {
|
||||||
declaration.forEachDescendantOfType<KtReferenceExpression> { refExpr ->
|
declaration.forEachDescendantOfType<KtReferenceExpression> { refExpr ->
|
||||||
refExpr.references
|
refExpr.references
|
||||||
.forEach { ref ->
|
.forEach { ref ->
|
||||||
val target = ref.resolve() ?: return@forEach
|
val target = ref.resolve() ?: return@forEach
|
||||||
if (isToBeMoved(target)) return@forEach
|
if (isToBeMoved(target)) return@forEach
|
||||||
|
|
||||||
val targetDescriptor = when (target) {
|
val targetDescriptor = when (target) {
|
||||||
is KtDeclaration -> target.resolveToDescriptor()
|
is KtDeclaration -> target.resolveToDescriptor()
|
||||||
is PsiMember -> target.getJavaMemberDescriptor()
|
is PsiMember -> target.getJavaMemberDescriptor()
|
||||||
else -> null
|
else -> null
|
||||||
} ?: return@forEach
|
} as? DeclarationDescriptorWithVisibility ?: return@forEach
|
||||||
if (targetDescriptor is CallableMemberDescriptor &&
|
|
||||||
targetDescriptor.visibility.normalize() == Visibilities.PROTECTED) {
|
var isVisible = targetDescriptor.isVisibleFrom(ref)
|
||||||
val resolvedCall = refExpr.getResolvedCall(refExpr.analyze(BodyResolveMode.PARTIAL)) ?: return@forEach
|
if (isVisible && targetDescriptor is ConstructorDescriptor) {
|
||||||
val dispatchReceiver = resolvedCall.dispatchReceiver
|
isVisible = targetDescriptor.containingDeclaration.isVisibleFrom(ref)
|
||||||
if (dispatchReceiver is ExpressionReceiver && dispatchReceiver.expression is KtSuperExpression) return@forEach
|
|
||||||
val receiverClass = resolvedCall.dispatchReceiver?.type?.constructor?.declarationDescriptor?.source?.getPsi()
|
|
||||||
if (receiverClass != null && isToBeMoved(receiverClass)) return@forEach
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetDescriptor.isVisibleIn(targetContainer)) {
|
if (!isVisible) {
|
||||||
val message = "${render(declaration)} uses ${render(target)} which will be inaccessible after move"
|
val message = "${render(declaration)} uses ${render(target)} which will be inaccessible after move"
|
||||||
conflicts.putValue(refExpr, message.capitalize())
|
conflicts.putValue(refExpr, message.capitalize())
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
open class Older {
|
||||||
|
protected object ProtectedObject { val inProtectedObject = 0 }
|
||||||
|
protected class ProtectedClass { val inProtectedClass = 0 }
|
||||||
|
protected fun protectedFun() = 0
|
||||||
|
protected var protectedVar = 0
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar.Older
|
||||||
|
|
||||||
|
class Younger : Older() {
|
||||||
|
protected val v1: ProtectedObject = ProtectedObject
|
||||||
|
val v2 = ProtectedObject.inProtectedObject
|
||||||
|
protected val v3: ProtectedClass = ProtectedClass()
|
||||||
|
val v4 = ProtectedClass().inProtectedClass
|
||||||
|
val v5 = protectedFun()
|
||||||
|
val v6 = protectedVar
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
open class <caret>Older {
|
||||||
|
protected object ProtectedObject { val inProtectedObject = 0 }
|
||||||
|
protected class ProtectedClass { val inProtectedClass = 0 }
|
||||||
|
protected fun protectedFun() = 0
|
||||||
|
protected var protectedVar = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
class Younger : Older() {
|
||||||
|
protected val v1: ProtectedObject = ProtectedObject
|
||||||
|
val v2 = ProtectedObject.inProtectedObject
|
||||||
|
protected val v3: ProtectedClass = ProtectedClass()
|
||||||
|
val v4 = ProtectedClass().inProtectedClass
|
||||||
|
val v5 = protectedFun()
|
||||||
|
val v6 = protectedVar
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"mainFile": "foo/test.kt",
|
||||||
|
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
|
||||||
|
"targetPackage": "bar"
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
import foo.Older
|
||||||
|
|
||||||
|
class Younger : Older() {
|
||||||
|
protected val v1: ProtectedObject = ProtectedObject
|
||||||
|
val v2 = ProtectedObject.inProtectedObject
|
||||||
|
protected val v3: ProtectedClass = ProtectedClass()
|
||||||
|
val v4 = ProtectedClass().inProtectedClass
|
||||||
|
val v5 = protectedFun()
|
||||||
|
val v6 = protectedVar
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
open class Older {
|
||||||
|
protected object ProtectedObject { val inProtectedObject = 0 }
|
||||||
|
protected class ProtectedClass { val inProtectedClass = 0 }
|
||||||
|
protected fun protectedFun() = 0
|
||||||
|
protected var protectedVar = 0
|
||||||
|
}
|
||||||
|
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
open class Older {
|
||||||
|
protected object ProtectedObject { val inProtectedObject = 0 }
|
||||||
|
protected class ProtectedClass { val inProtectedClass = 0 }
|
||||||
|
protected fun protectedFun() = 0
|
||||||
|
protected var protectedVar = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
class <caret>Younger : Older() {
|
||||||
|
protected val v1: ProtectedObject = ProtectedObject
|
||||||
|
val v2 = ProtectedObject.inProtectedObject
|
||||||
|
protected val v3: ProtectedClass = ProtectedClass()
|
||||||
|
val v4 = ProtectedClass().inProtectedClass
|
||||||
|
val v5 = protectedFun()
|
||||||
|
val v6 = protectedVar
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"mainFile": "foo/test.kt",
|
||||||
|
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
|
||||||
|
"targetPackage": "bar"
|
||||||
|
}
|
||||||
@@ -576,6 +576,18 @@ public class MoveTestGenerated extends AbstractMoveTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/protectedMembersExternalRefs/protectedMembersExternalRefs.test")
|
||||||
|
public void testKotlin_moveTopLevelDeclarations_misc_protectedMembersExternalRefs_ProtectedMembersExternalRefs() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/protectedMembersExternalRefs/protectedMembersExternalRefs.test");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/protectedMembersInternalRefs/protectedMembersInternalRefs.test")
|
||||||
|
public void testKotlin_moveTopLevelDeclarations_misc_protectedMembersInternalRefs_ProtectedMembersInternalRefs() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/protectedMembersInternalRefs/protectedMembersInternalRefs.test");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test")
|
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test")
|
||||||
public void testKotlin_moveTopLevelDeclarations_misc_selfReferences_SelfReferences() throws Exception {
|
public void testKotlin_moveTopLevelDeclarations_misc_selfReferences_SelfReferences() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user