diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirVisibilityChecker.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirVisibilityChecker.kt index 2b53c7f13cb..908f4daec4f 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirVisibilityChecker.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirVisibilityChecker.kt @@ -9,6 +9,7 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.analysis.api.components.KtVisibilityChecker import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirFileSymbol +import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirPsiJavaClassSymbol import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirSymbol import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol @@ -17,11 +18,14 @@ import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDesignation import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirSafe import org.jetbrains.kotlin.analysis.utils.printer.parentsOfType +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.java.JavaVisibilities import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.utils.effectiveVisibility import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.packageFqName import org.jetbrains.kotlin.fir.resolve.calls.ExpressionReceiverValue import org.jetbrains.kotlin.fir.resolve.transformers.publishedApiEffectiveVisibility import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase @@ -44,6 +48,10 @@ internal class KtFirVisibilityChecker( require(candidateSymbol is KtFirSymbol<*>) require(useSiteFile is KtFirFileSymbol) + if (candidateSymbol is KtFirPsiJavaClassSymbol) { + candidateSymbol.isVisibleByPsi(useSiteFile)?.let { return it } + } + val useSiteFirFile = useSiteFile.firSymbol.fir val containers = collectContainingDeclarations(position) @@ -65,6 +73,47 @@ internal class KtFirVisibilityChecker( ) } + /** + * [isVisibleByPsi] is a heuristic that decides visibility for most [KtFirPsiJavaClassSymbol]s without deferring to its FIR symbol, + * thereby avoiding lazy construction of the FIR class. The visibility rules are tailored specifically for Java classes accessed from + * Kotlin. They cover the most popular visibilities `private`, `public`, and default (package) visibility for top-level and nested + * classes. + * + * Returns `null` if visibility cannot be decided by the heuristic. + */ + private fun KtFirPsiJavaClassSymbol.isVisibleByPsi(useSiteFile: KtFirFileSymbol): Boolean? { + val classId = classIdIfNonLocal + if (classId == null) { + // Local classes are only visible in their containing method. Since we're checking the visibility for a Kotlin file, a local + // class from Java can never be accessible. + return false + } + + when (visibility) { + Visibilities.Private -> + // Private classes from Java cannot be accessed from Kotlin. + return false + + Visibilities.Public -> + return when (val outerClass = this.outerClass) { + null -> true + else -> outerClass.isVisibleByPsi(useSiteFile) + } + + JavaVisibilities.PackageVisibility -> { + val isSamePackage = classId.packageFqName == useSiteFile.firSymbol.fir.packageFqName + if (!isSamePackage) return false + + return when (val outerClass = this.outerClass) { + null -> true + else -> outerClass.isVisibleByPsi(useSiteFile) + } + } + } + + return null + } + override fun isPublicApi(symbol: KtSymbolWithVisibility): Boolean { require(symbol is KtFirSymbol<*>) val declaration = symbol.firSymbol.fir as? FirMemberDeclaration ?: return false diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/symbols/KtFirPsiJavaClassSymbol.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/symbols/KtFirPsiJavaClassSymbol.kt index 4d093f054cb..0449be14a43 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/symbols/KtFirPsiJavaClassSymbol.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/symbols/KtFirPsiJavaClassSymbol.kt @@ -92,6 +92,11 @@ internal class KtFirPsiJavaClassSymbol( override val isInner: Boolean get() = withValidityAssertion { classIdIfNonLocal?.outerClassId != null && !javaClass.isStatic } + val outerClass: KtFirPsiJavaClassSymbol? + get() = withValidityAssertion { + psi.containingClass?.let { KtFirPsiJavaClassSymbol(it, analysisSession) } + } + override val isData: Boolean get() = withValidityAssertion { false } override val isInline: Boolean get() = withValidityAssertion { false } override val isFun: Boolean get() = withValidityAssertion { false }