diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt index 676b77670ef..75b6c93ef0a 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt @@ -699,6 +699,7 @@ class Fir2IrDeclarationStorage( private val FirProperty.fieldVisibility: Visibility get() = when { + hasExplicitBackingField -> backingField?.visibility ?: status.visibility isLateInit -> setter?.visibility ?: status.visibility isConst -> status.visibility hasJvmFieldAnnotation -> status.visibility @@ -798,7 +799,6 @@ class Fir2IrDeclarationStorage( parent = irParent } val type = property.returnTypeRef.toIrType() - val initializer = property.initializer val delegate = property.delegate val getter = property.getter val setter = property.setter @@ -810,14 +810,21 @@ class Fir2IrDeclarationStorage( Name.identifier("${property.name}\$delegate"), true, delegate ) } else { + val initializer = property.backingField?.initializer ?: property.initializer + // There are cases when we get here for properties + // that have no backing field. For example, in the + // funExpression.kt test there's an attempt + // to access the `javaClass` property of the `foo0`'s + // `block` argument + val typeToUse = property.backingField?.returnTypeRef?.toIrType() ?: type createBackingField( property, IrDeclarationOrigin.PROPERTY_BACKING_FIELD, components.visibilityConverter.convertToDescriptorVisibility(property.fieldVisibility), - property.name, property.isVal, initializer, type + property.name, property.isVal, initializer, typeToUse ).also { field -> if (initializer is FirConstExpression<*>) { // TODO: Normally we shouldn't have error type here - val constType = initializer.typeRef.toIrType().takeIf { it !is IrErrorType } ?: type + val constType = initializer.typeRef.toIrType().takeIf { it !is IrErrorType } ?: typeToUse field.initializer = factory.createExpressionBody(initializer.toIrConst(constType)) } } diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/ClassMemberGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/ClassMemberGenerator.kt index 88f3abf4ceb..ef6451964cc 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/ClassMemberGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/ClassMemberGenerator.kt @@ -158,7 +158,7 @@ internal class ClassMemberGenerator( } fun convertPropertyContent(irProperty: IrProperty, property: FirProperty, containingClass: FirClass?): IrProperty { - val initializer = property.initializer + val initializer = property.backingField?.initializer ?: property.initializer val delegate = property.delegate val propertyType = property.returnTypeRef.toIrType() irProperty.initializeBackingField(property, initializerExpression = initializer ?: delegate) diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/lazy/Fir2IrLazyProperty.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/lazy/Fir2IrLazyProperty.kt index 59112bb49ee..abce1eac35b 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/lazy/Fir2IrLazyProperty.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/lazy/Fir2IrLazyProperty.kt @@ -29,6 +29,8 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource import org.jetbrains.kotlin.fir.backend.* import org.jetbrains.kotlin.fir.declarations.utils.* +import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.ir.expressions.IrExpressionBody import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol class Fir2IrLazyProperty( @@ -88,6 +90,16 @@ class Fir2IrLazyProperty( with(typeConverter) { fir.returnTypeRef.toIrType() } } + private fun toIrInitializer(initializer: FirExpression?): IrExpressionBody? { + return if (initializer is FirConstExpression<*>) { + // TODO: Normally we shouldn't have error type here + val constType = with(typeConverter) { initializer.typeRef.toIrType().takeIf { it !is IrErrorType } ?: type } + factory.createExpressionBody(initializer.toIrConst(constType)) + } else { + null + } + } + @OptIn(ObsoleteDescriptorBasedAPI::class) override var backingField: IrField? by lazyVar(lock) { // TODO: this checks are very preliminary, FIR resolve should determine backing field presence itself @@ -96,6 +108,26 @@ class Fir2IrLazyProperty( !fir.isConst && (fir.modality == Modality.ABSTRACT || parent is IrClass && parent.isInterface) -> { null } + fir.hasExplicitBackingField -> { + with(declarationStorage) { + val backingFieldType = with(typeConverter) { + fir.backingField?.returnTypeRef?.toIrType() + } + val initializer = fir.backingField?.initializer ?: fir.initializer + val visibility = fir.backingField?.visibility ?: fir.visibility + createBackingField( + fir, + IrDeclarationOrigin.PROPERTY_BACKING_FIELD, + components.visibilityConverter.convertToDescriptorVisibility(visibility), + fir.name, + fir.isVal, + initializer, + backingFieldType + ).also { field -> + field.initializer = toIrInitializer(initializer) + } + } + } fir.initializer != null || fir.getter is FirDefaultPropertyGetter || fir.isVar && fir.setter is FirDefaultPropertySetter -> { with(declarationStorage) { createBackingField( @@ -103,12 +135,7 @@ class Fir2IrLazyProperty( components.visibilityConverter.convertToDescriptorVisibility(fir.visibility), fir.name, fir.isVal, fir.initializer, type ).also { field -> - val initializer = fir.initializer - if (initializer is FirConstExpression<*>) { - // TODO: Normally we shouldn't have error type here - val constType = with(typeConverter) { initializer.typeRef.toIrType().takeIf { it !is IrErrorType } ?: type } - field.initializer = factory.createExpressionBody(initializer.toIrConst(constType)) - } + field.initializer = toIrInitializer(fir.initializer) } } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt index 49d152870cc..a009ac09ac5 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.hasExplicitBackingField import org.jetbrains.kotlin.fir.declarations.utils.isInner import org.jetbrains.kotlin.fir.declarations.utils.isReferredViaField import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic @@ -44,6 +45,7 @@ import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability import org.jetbrains.kotlin.resolve.calls.tower.isSuccess import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.utils.addToStdlib.safeAs class FirCallResolver( private val components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents, @@ -727,6 +729,11 @@ class FirCallResolver( resolvedSymbol = coneSymbol } } + if (coneSymbol.safeAs()?.hasExplicitBackingField == true) { + return FirPropertyWithExplicitBackingFieldResolvedNamedReference( + source, name, candidate.symbol, candidate.hasVisibleBackingField + ) + } /* * This `if` is an optimization for local variables and properties without type parameters * Since they have no type variables, so we can don't run completion on them at all and create diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt index 8e93ae7e60a..6a2acb54710 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt @@ -18,11 +18,13 @@ import org.jetbrains.kotlin.fir.resolve.calls.FirSyntheticFunctionSymbol import org.jetbrains.kotlin.fir.resolve.calls.ReceiverValue import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirBackingFieldSymbol import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.utils.addToStdlib.safeAs abstract class FirModuleVisibilityChecker : FirSessionComponent { abstract fun isInFriendModule(declaration: T): Boolean where T : FirMemberDeclaration, T : FirDeclaration @@ -51,6 +53,23 @@ abstract class FirVisibilityChecker : FirSessionComponent { } } + private fun FirMemberDeclaration.getBackingFieldIfApplicable(): FirBackingField? { + val field = this.safeAs()?.getExplicitBackingField() + ?: return null + + // This check prevents resolving protected and + // public fields. + if ( + field.visibility == Visibilities.PrivateToThis || + field.visibility == Visibilities.Private || + field.visibility == Visibilities.Internal + ) { + return field + } + + return null + } + fun isVisible( declaration: FirMemberDeclaration, candidate: Candidate @@ -71,7 +90,20 @@ abstract class FirVisibilityChecker : FirSessionComponent { return true } - return isVisible(declaration, session, useSiteFile, containingDeclarations, candidate.dispatchReceiverValue) + val visible = isVisible(declaration, session, useSiteFile, containingDeclarations, candidate.dispatchReceiverValue) + val backingField = declaration.getBackingFieldIfApplicable() + + if (visible && backingField != null) { + candidate.hasVisibleBackingField = isVisible( + backingField, + session, + useSiteFile, + containingDeclarations, + candidate.dispatchReceiverValue, + ) + } + + return visible } fun isVisible( @@ -277,6 +309,7 @@ val FirSession.visibilityChecker: FirVisibilityChecker by FirSession.sessionComp fun FirBasedSymbol<*>.getOwnerLookupTag(): ConeClassLikeLookupTag? { return when (this) { + is FirBackingFieldSymbol -> fir.propertySymbol.getOwnerLookupTag() is FirClassLikeSymbol<*> -> { if (classId.isLocal) { (fir as? FirRegularClass)?.containingClassForLocal() diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt index a320ee03ea3..d0132a33d6e 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt @@ -11,7 +11,9 @@ import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.canNarrowDownGetterType import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType +import org.jetbrains.kotlin.fir.declarations.utils.isFinal import org.jetbrains.kotlin.fir.declarations.utils.isInner import org.jetbrains.kotlin.fir.declarations.utils.isLocal import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic @@ -187,6 +189,30 @@ internal fun typeForQualifierByDeclaration(declaration: FirDeclaration, resultTy return null } +private fun FirPropertyWithExplicitBackingFieldResolvedNamedReference.getNarrowedDownSymbol(): FirBasedSymbol<*> { + val propertyReceiver = resolvedSymbol.safeAs() + ?: return resolvedSymbol + + // This can happen in case of 2 properties referencing + // each other recursively. See: Jet81.fir.kt + if ( + propertyReceiver.fir.returnTypeRef is FirImplicitTypeRef || + propertyReceiver.fir.backingField?.returnTypeRef is FirImplicitTypeRef + ) { + return resolvedSymbol + } + + if ( + propertyReceiver.isFinal && + hasVisibleBackingField && + propertyReceiver.canNarrowDownGetterType + ) { + return propertyReceiver.fir.backingField?.symbol ?: resolvedSymbol + } + + return resolvedSymbol +} + fun BodyResolveComponents.typeFromCallee(access: T): FirResolvedTypeRef { return when (val newCallee = access.calleeReference) { is FirErrorNamedReference -> @@ -198,16 +224,8 @@ fun BodyResolveComponents.typeFromCallee(access: T): FirReso typeFromSymbol(newCallee.candidateSymbol, false) } is FirPropertyWithExplicitBackingFieldResolvedNamedReference -> { - val backingField = newCallee.resolvedSymbol - .safeAs() - ?.fir - ?.backingField - - if (newCallee.hasVisibleBackingField && backingField != null) { - typeFromSymbol(backingField.symbol, false) - } else { - typeFromSymbol(newCallee.resolvedSymbol, false) - } + val symbol = newCallee.getNarrowedDownSymbol() + typeFromSymbol(symbol, false) } is FirResolvedNamedReference -> { typeFromSymbol(newCallee.resolvedSymbol, false) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Candidate.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Candidate.kt index fcb598fa040..e091a7a6968 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Candidate.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Candidate.kt @@ -145,6 +145,8 @@ class Candidate( fun extensionReceiverExpression(): FirExpression = extensionReceiverValue?.receiverExpression?.takeIf { it !is FirExpressionStub } ?: FirNoReceiverExpression + var hasVisibleBackingField = false + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirNamedReferenceWithCandidate.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirNamedReferenceWithCandidate.kt index 56d8515a410..b9c15f26e7c 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirNamedReferenceWithCandidate.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirNamedReferenceWithCandidate.kt @@ -8,8 +8,11 @@ package org.jetbrains.kotlin.fir.resolve.calls import org.jetbrains.kotlin.fir.FirImplementationDetail import org.jetbrains.kotlin.fir.FirSourceElement import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.visitors.FirTransformer +import org.jetbrains.kotlin.fir.visitors.FirVisitor import org.jetbrains.kotlin.name.Name @OptIn(FirImplementationDetail::class) @@ -32,3 +35,18 @@ class FirErrorReferenceWithCandidate( ) : FirNamedReferenceWithCandidate(source, name, candidate) { override val isError: Boolean get() = true } + +class FirPropertyWithExplicitBackingFieldResolvedNamedReference( + override val source: FirSourceElement?, + override val name: Name, + override val resolvedSymbol: FirBasedSymbol<*>, + val hasVisibleBackingField: Boolean, +) : FirResolvedNamedReference() { + override val candidateSymbol: FirBasedSymbol<*>? get() = null + + override fun acceptChildren(visitor: FirVisitor, data: D) {} + + override fun transformChildren(transformer: FirTransformer, data: D): FirPropertyWithExplicitBackingFieldResolvedNamedReference { + return this + } +} diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt index a12751fbe4b..7e67e16e1e8 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt @@ -69,11 +69,6 @@ open class ExpressionReceiverValue( } } -class NarrowedExpressionReceiverValue( - explicitReceiver: FirExpression, - override val type: ConeKotlinType, -) : ExpressionReceiverValue(explicitReceiver) - sealed class ImplicitReceiverValue>( val boundSymbol: S, type: ConeKotlinType, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirTowerResolveTask.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirTowerResolveTask.kt index 7d901a8d8f6..1c637d8069b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirTowerResolveTask.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirTowerResolveTask.kt @@ -5,34 +5,21 @@ package org.jetbrains.kotlin.fir.resolve.calls.tower -import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fir.asReversedFrozen -import org.jetbrains.kotlin.fir.containingClass -import org.jetbrains.kotlin.fir.declarations.utils.canNarrowDownGetterType -import org.jetbrains.kotlin.fir.declarations.utils.isFinal -import org.jetbrains.kotlin.fir.declarations.utils.visibility import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.expressions.builder.buildExpressionStub -import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS import org.jetbrains.kotlin.fir.resolve.FirTowerDataContext import org.jetbrains.kotlin.fir.resolve.calls.* -import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.scopes.FirScope -import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol -import org.jetbrains.kotlin.fir.types.ConeKotlinType -import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef -import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.impl.FirImplicitBuiltinTypeRef import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind import org.jetbrains.kotlin.resolve.descriptorUtil.HIDES_MEMBERS_NAME_LIST -import org.jetbrains.kotlin.utils.addToStdlib.safeAs - internal class TowerDataElementsForName( name: Name, @@ -236,64 +223,12 @@ internal open class FirTowerResolveTask( ) } - private fun FirPropertySymbol.hasAccessibleBackingField(info: CallInfo): Boolean { - val receiverContainer = containingClass()?.toSymbol(info.session) - - if (!this.isFinal) { - return false - } - - return when (fir.backingField?.visibility) { - Visibilities.Private -> { - info.containingDeclarations.any { - it.symbol == receiverContainer - } - } - Visibilities.Internal -> { - receiverContainer?.moduleData == info.containingFile.moduleData - } - else -> { - false - } - } - } - - private fun FirExpression.getNarrowedDownReturnType(info: CallInfo): ConeKotlinType? { - val propertyReceiver = this.safeAs() - ?.calleeReference - ?.safeAs() - ?.resolvedSymbol - ?.safeAs() - ?: return null - - // This can happen in case of 2 properties referencing - // each other recursively. See: Jet81.fir.kt - if ( - propertyReceiver.fir.returnTypeRef is FirImplicitTypeRef || - propertyReceiver.fir.backingField?.returnTypeRef is FirImplicitTypeRef - ) { - return null - } - - if (propertyReceiver.hasAccessibleBackingField(info) && propertyReceiver.canNarrowDownGetterType) { - return propertyReceiver.fir.backingField?.returnTypeRef?.coneType - } - - return null - } - suspend fun runResolverForExpressionReceiver( info: CallInfo, receiver: FirExpression, parentGroup: TowerGroup = TowerGroup.EmptyRoot ) { - val narrowedType = receiver.getNarrowedDownReturnType(info) - - val explicitReceiverValue = if (narrowedType != null) { - NarrowedExpressionReceiverValue(receiver, narrowedType) - } else { - ExpressionReceiverValue(receiver) - } + val explicitReceiverValue = ExpressionReceiverValue(receiver) processExtensionsThatHideMembers(info, explicitReceiverValue, parentGroup) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/providers/impl/FirProviderImpl.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/providers/impl/FirProviderImpl.kt index 61ed0808432..0e4ed44b27e 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/providers/impl/FirProviderImpl.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/providers/impl/FirProviderImpl.kt @@ -29,6 +29,9 @@ class FirProviderImpl(val session: FirSession, val kotlinScopeProvider: FirKotli symbol.originalIfFakeOverride()?.let { return getFirCallableContainerFile(it) } + if (symbol is FirBackingFieldSymbol) { + return getFirCallableContainerFile(symbol.fir.propertySymbol) + } if (symbol is FirAccessorSymbol) { val fir = symbol.fir if (fir is FirSyntheticProperty) {