K2 resolve: prefer derived class property to base class field

#KT-50082 Fixed
This commit is contained in:
Mikhail Glukhikh
2022-11-16 17:14:56 +01:00
committed by teamcity
parent 642bbd38ba
commit 59bafedd8a
15 changed files with 407 additions and 41 deletions
@@ -3407,6 +3407,12 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis
runTest("compiler/fir/analysis-tests/testData/resolve/problems/emptySelectorInQualifiedExpression.kt");
}
@Test
@TestMetadata("enumEntryFieldShadow.kt")
public void testEnumEntryFieldShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/enumEntryFieldShadow.kt");
}
@Test
@TestMetadata("expectConstructor.kt")
public void testExpectConstructor() throws Exception {
@@ -3568,6 +3574,34 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis
}
}
@Nested
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/propertyVsField")
@TestDataPath("$PROJECT_ROOT")
public class PropertyVsField {
@Test
public void testAllFilesPresentInPropertyVsField() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/propertyVsField"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("fieldPropertyShadow.kt")
public void testFieldPropertyShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/fieldPropertyShadow.kt");
}
@Test
@TestMetadata("propertyAndTwoFields.kt")
public void testPropertyAndTwoFields() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/propertyAndTwoFields.kt");
}
@Test
@TestMetadata("propertyFieldShadow.kt")
public void testPropertyFieldShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/propertyFieldShadow.kt");
}
}
@Nested
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/references")
@TestDataPath("$PROJECT_ROOT")
@@ -3005,6 +3005,11 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract
runTest("compiler/fir/analysis-tests/testData/resolve/problems/emptySelectorInQualifiedExpression.kt");
}
@TestMetadata("enumEntryFieldShadow.kt")
public void testEnumEntryFieldShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/enumEntryFieldShadow.kt");
}
@TestMetadata("expectConstructor.kt")
public void testExpectConstructor() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/expectConstructor.kt");
@@ -3144,6 +3149,34 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/propertyVsField")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class PropertyVsField extends AbstractLazyBodyIsNotTouchedTilContractsPhaseTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInPropertyVsField() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/propertyVsField"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("fieldPropertyShadow.kt")
public void testFieldPropertyShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/fieldPropertyShadow.kt");
}
@TestMetadata("propertyAndTwoFields.kt")
public void testPropertyAndTwoFields() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/propertyAndTwoFields.kt");
}
@TestMetadata("propertyFieldShadow.kt")
public void testPropertyFieldShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/propertyFieldShadow.kt");
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/references")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -0,0 +1,28 @@
FILE: DerivedEnum.kt
public final enum class DerivedEnum : R|BaseJava|, R|kotlin/Enum<DerivedEnum>| {
private constructor(): R|DerivedEnum| {
super<R|kotlin/Enum<DerivedEnum>|>()
}
public final static enum entry x: R|DerivedEnum|
public final fun foo(): R|kotlin/Unit| {
R|/bar|(R|/DerivedEnum.x|)
R|/baz|(<Unresolved name: y>#)
R|/baz|(Q|BaseJava|.R|/BaseJava.y|)
}
public final static fun values(): R|kotlin/Array<DerivedEnum>| {
}
public final static fun valueOf(value: R|kotlin/String|): R|DerivedEnum| {
}
public final static val entries: R|kotlin/enums/EnumEntries<DerivedEnum>|
public get(): R|kotlin/enums/EnumEntries<DerivedEnum>|
}
public final fun bar(e: R|DerivedEnum|): R|kotlin/Unit| {
}
public final fun baz(s: R|kotlin/String|): R|kotlin/Unit| {
Q|DerivedEnum|.R|/DerivedEnum.x|
}
@@ -0,0 +1,24 @@
// FILE: BaseJava.java
public interface BaseJava {
String x = "";
String y = "";
}
// FILE: DerivedEnum.kt
enum class DerivedEnum : BaseJava {
x;
fun foo() {
bar(x)
baz(<!UNRESOLVED_REFERENCE!>y<!>)
baz(BaseJava.y)
}
}
fun bar(e: DerivedEnum) {}
fun baz(s: String) {
DerivedEnum.x
}
@@ -0,0 +1,30 @@
FILE: KotlinBase.kt
public open class KotlinBase : R|kotlin/Any| {
public constructor(): R|KotlinBase| {
super<R|kotlin/Any|>()
}
public final val abcd: R|kotlin/String| = String(abcd)
public get(): R|kotlin/String|
}
FILE: KotlinProxy.kt
public abstract interface KotlinProxy : R|kotlin/Any| {
public open val zyxw: R|kotlin/String|
public get(): R|kotlin/String| {
^ String(zyxw)
}
}
FILE: test.kt
public final class Derived : R|Test|, R|KotlinProxy| {
public constructor(): R|Derived| {
super<R|Test|>()
}
public final fun test(): R|kotlin/Unit| {
this@R|/Derived|.R|/Test.abcd|
this@R|/Derived|.R|/Test.zyxw|
}
}
@@ -0,0 +1,28 @@
// FILE: Test.java
public class Test extends KotlinBase {
public final String abcd = "ABCD";
public final String zyxw = "ZYXW";
}
// FILE: KotlinBase.kt
open class KotlinBase {
val abcd = "abcd"
}
// FILE: KotlinProxy.kt
interface KotlinProxy {
val zyxw get() = "zyxw"
}
// FILE: test.kt
class Derived : Test(), KotlinProxy {
fun test() {
abcd // field!
zyxw // field!
}
}
@@ -0,0 +1,14 @@
FILE: KotlinBase.kt
public open class KotlinBase : R|kotlin/Any| {
public constructor(): R|KotlinBase| {
super<R|kotlin/Any|>()
}
public final val abcd: R|kotlin/String| = String(abcd)
public get(): R|kotlin/String|
}
FILE: test.kt
public final fun test(d: R|JavaDerived|): R|kotlin/Unit| {
R|<local>/d|.R|/JavaDerived.abcd|
}
@@ -0,0 +1,23 @@
// FILE: Test.java
public class Test extends KotlinBase {
public final String abcd = "ABCD";
}
// FILE: KotlinBase.kt
open class KotlinBase {
val abcd = "abcd"
}
// FILE: JavaDerived.java
public class JavaDerived extends Test {
public final String abcd = "AaBbCcDd"
}
// FILE: test.kt
fun test(d: JavaDerived) {
d.abcd // JavaDerived
}
@@ -0,0 +1,20 @@
FILE: test.kt
public final class Test2 : R|Test| {
public constructor(): R|Test2| {
super<R|Test|>()
}
private final val text: R|kotlin/String| = String(BCDE)
private get(): R|kotlin/String|
private final val publicPrivateText: R|kotlin/String| = String(YXWV)
private get(): R|kotlin/String|
public final fun check(): R|kotlin/String| {
^check this@R|/Test2|.R|/Test2.text|
}
}
public final fun check(): R|kotlin/String!| {
^check R|/Test2.Test2|().R|/Test.publicPrivateText|
}
@@ -0,0 +1,20 @@
// FILE: Test.java
public class Test {
protected final String text = "ABCD";
public final String publicPrivateText = "ZYXW";
}
// FILE: test.kt
class Test2 : Test() {
private val text = "BCDE"
private val publicPrivateText = "YXWV"
fun check() = text // Should be resolved to Test2.text, not to Test.text
}
fun check() = Test2().publicPrivateText // Should be resolved to Test.publicPrivateText (Test2 member is private)
@@ -3407,6 +3407,12 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/emptySelectorInQualifiedExpression.kt");
}
@Test
@TestMetadata("enumEntryFieldShadow.kt")
public void testEnumEntryFieldShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/enumEntryFieldShadow.kt");
}
@Test
@TestMetadata("expectConstructor.kt")
public void testExpectConstructor() throws Exception {
@@ -3568,6 +3574,34 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest {
}
}
@Nested
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/propertyVsField")
@TestDataPath("$PROJECT_ROOT")
public class PropertyVsField {
@Test
public void testAllFilesPresentInPropertyVsField() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/propertyVsField"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("fieldPropertyShadow.kt")
public void testFieldPropertyShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/fieldPropertyShadow.kt");
}
@Test
@TestMetadata("propertyAndTwoFields.kt")
public void testPropertyAndTwoFields() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/propertyAndTwoFields.kt");
}
@Test
@TestMetadata("propertyFieldShadow.kt")
public void testPropertyFieldShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/propertyFieldShadow.kt");
}
}
@Nested
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/references")
@TestDataPath("$PROJECT_ROOT")
@@ -3407,6 +3407,12 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
runTest("compiler/fir/analysis-tests/testData/resolve/problems/emptySelectorInQualifiedExpression.kt");
}
@Test
@TestMetadata("enumEntryFieldShadow.kt")
public void testEnumEntryFieldShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/enumEntryFieldShadow.kt");
}
@Test
@TestMetadata("expectConstructor.kt")
public void testExpectConstructor() throws Exception {
@@ -3568,6 +3574,34 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
}
}
@Nested
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/propertyVsField")
@TestDataPath("$PROJECT_ROOT")
public class PropertyVsField {
@Test
public void testAllFilesPresentInPropertyVsField() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/propertyVsField"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("fieldPropertyShadow.kt")
public void testFieldPropertyShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/fieldPropertyShadow.kt");
}
@Test
@TestMetadata("propertyAndTwoFields.kt")
public void testPropertyAndTwoFields() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/propertyAndTwoFields.kt");
}
@Test
@TestMetadata("propertyFieldShadow.kt")
public void testPropertyFieldShadow() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/propertyVsField/propertyFieldShadow.kt");
}
}
@Nested
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/references")
@TestDataPath("$PROJECT_ROOT")
@@ -10,11 +10,12 @@ import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.FirField
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.AbstractConeCallConflictResolver
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.inference.InferenceComponents
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.unwrapFakeOverrides
import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator
class JvmPlatformOverloadsConflictResolver(
@@ -31,21 +32,59 @@ class JvmPlatformOverloadsConflictResolver(
return candidates
}
val result = mutableSetOf<Candidate>()
outerLoop@ for (myCandidate in candidates) {
val me = myCandidate.symbol.fir
if (me is FirProperty && me.symbol.containingClassLookupTag() != null) {
for (otherCandidate in candidates) {
val other = otherCandidate.symbol.fir
if (other is FirField && other.symbol.containingClassLookupTag() != null) {
// NB: FE 1.0 does class equivalence check here
// However, in FIR container classes aren't the same for our samples (see fieldPropertyOverloads.kt)
// E.g. we can have SomeConcreteJavaEnum for field and kotlin.Enum for static property 'name'
continue@outerLoop
}
for (myCandidate in candidates) {
when (val me = myCandidate.symbol.fir) {
is FirProperty -> if (!me.isShadowedByFieldCandidate(candidates)) {
result += myCandidate
}
is FirField -> if (!me.isShadowedByPropertyCandidate(candidates)) {
result += myCandidate
}
else -> result += myCandidate
}
result += myCandidate
}
return result
}
private fun FirProperty.isShadowedByFieldCandidate(candidates: Set<Candidate>): Boolean {
val propertyContainingClassLookupTag = unwrapFakeOverrides().symbol.containingClassLookupTag() ?: return false
for (otherCandidate in candidates) {
val field = otherCandidate.symbol.fir as? FirField ?: continue
val fieldContainingClassLookupTag = field.unwrapFakeOverrides().symbol.containingClassLookupTag()
if (fieldContainingClassLookupTag != null &&
!propertyContainingClassLookupTag.strictlyDerivedFrom(fieldContainingClassLookupTag)
) {
// NB: FE 1.0 does class equivalence check here ^^^
// However, in FIR container classes aren't the same for our samples (see fieldPropertyOverloads.kt)
// E.g. we can have SomeConcreteJavaEnum for field and kotlin.Enum for static property 'name'
return true
}
}
return false
}
private fun FirField.isShadowedByPropertyCandidate(candidates: Set<Candidate>): Boolean {
val fieldContainingClassLookupTag = unwrapFakeOverrides().symbol.containingClassLookupTag() ?: return false
for (otherCandidate in candidates) {
val property = otherCandidate.symbol.fir as? FirProperty ?: continue
val propertyContainingClassLookupTag = property.unwrapFakeOverrides().symbol.containingClassLookupTag()
if (propertyContainingClassLookupTag != null &&
propertyContainingClassLookupTag.strictlyDerivedFrom(fieldContainingClassLookupTag)
) {
// NB: FE 1.0 does class equivalence check here ^^^
// However, in FIR container classes aren't the same for our samples (see fieldPropertyOverloads.kt)
// E.g. we can have SomeConcreteJavaEnum for field and kotlin.Enum for static property 'name'
return true
}
}
return false
}
private fun ConeClassLikeLookupTag.strictlyDerivedFrom(other: ConeClassLikeLookupTag): Boolean {
if (this == other) return false
val session = inferenceComponents.session
val thisClass = this.toFirRegularClassSymbol(session)?.fir ?: return false
return thisClass.isSubclassOf(other, session, isStrict = true)
}
}
@@ -194,7 +194,7 @@ abstract class FirVisibilityChecker : FirSessionComponent {
if (isStatic && containingClass != null) {
containingUseSiteDeclarations.firstNotNullOfOrNull {
if (it !is FirClass) return@firstNotNullOfOrNull null
it.takeIf { it.isSubClass(containingLookupTag, session, supertypeSupplier) }
it.takeIf { it.isSubclassOf(containingLookupTag, session, isStrict = false, supertypeSupplier) }
}?.let { return it }
}
@@ -380,10 +380,6 @@ abstract class FirVisibilityChecker : FirSessionComponent {
return false
}
// 'local' isn't taken into account here
private fun ClassId.isSame(other: ClassId): Boolean =
packageFqName == other.packageFqName && relativeClassName == other.relativeClassName
private fun ConeClassLikeLookupTag.ownerIfCompanion(session: FirSession): ConeClassLikeLookupTag? {
if (classId.isLocal) return null
val outerClassId = classId.outerClassId ?: return null
@@ -405,11 +401,11 @@ abstract class FirVisibilityChecker : FirSessionComponent {
supertypeSupplier: SupertypeSupplier
): Boolean {
dispatchReceiver?.ownerIfCompanion(session)?.let { companionOwnerLookupTag ->
if (containingUseSiteClass.isSubClass(companionOwnerLookupTag, session, supertypeSupplier)) return true
if (containingUseSiteClass.isSubclassOf(companionOwnerLookupTag, session, isStrict = false, supertypeSupplier)) return true
}
return when {
!containingUseSiteClass.isSubClass(ownerLookupTag, session, supertypeSupplier) -> false
!containingUseSiteClass.isSubclassOf(ownerLookupTag, session, isStrict = false, supertypeSupplier) -> false
isVariableOrNamedFunction -> doesReceiverFitForProtectedVisibility(
dispatchReceiver,
containingUseSiteClass,
@@ -460,26 +456,6 @@ abstract class FirVisibilityChecker : FirSessionComponent {
return false
}
private fun FirClass.isSubClass(
ownerLookupTag: ConeClassLikeLookupTag,
session: FirSession,
supertypeSupplier: SupertypeSupplier
): Boolean {
if (classId.isSame(ownerLookupTag.classId)) return true
return lookupSuperTypes(
this,
lookupInterfaces = true,
deep = true,
session,
substituteTypes = false,
supertypeSupplier
).any { superType ->
// Note: We check just classId here, so type substitution isn't needed ^ (we aren't interested in type arguments)
(superType as? ConeClassLikeType)?.fullyExpandedType(session)?.lookupTag?.classId?.isSame(ownerLookupTag.classId) == true
}
}
private fun ReceiverValue?.ownerIfCompanion(session: FirSession): ConeClassLikeLookupTag? =
(this?.type as? ConeClassLikeType)?.lookupTag?.ownerIfCompanion(session)
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.resolve
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.classId
import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
import org.jetbrains.kotlin.fir.declarations.utils.superConeTypes
@@ -21,6 +22,7 @@ import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.types.model.CaptureStatus
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.SmartSet
@@ -90,6 +92,33 @@ fun lookupSuperTypes(
}
}
fun FirClass.isSubclassOf(
ownerLookupTag: ConeClassLikeLookupTag,
session: FirSession,
isStrict: Boolean,
supertypeSupplier: SupertypeSupplier = SupertypeSupplier.Default
): Boolean {
if (classId.isSame(ownerLookupTag.classId)) {
return !isStrict
}
return lookupSuperTypes(
this,
lookupInterfaces = true,
deep = true,
session,
substituteTypes = false,
supertypeSupplier
).any { superType ->
// Note: We check just classId here, so type substitution isn't needed
superType.lookupTag.classId.isSame(ownerLookupTag.classId)
}
}
// 'local' isn't taken into account here
fun ClassId.isSame(other: ClassId): Boolean =
packageFqName == other.packageFqName && relativeClassName == other.relativeClassName
fun FirClass.isThereLoopInSupertypes(session: FirSession): Boolean {
val visitedSymbols: MutableSet<FirClassifierSymbol<*>> = SmartSet.create()
val inProcess: MutableSet<FirClassifierSymbol<*>> = mutableSetOf()