[FIR] 3/n FirInlineDeclarationChecker: take into consideration upcoming data class copy visibility change

^KT-68621 Fixed
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineDeclarationChecker.kt
index 12e6b38..9c7d2f9 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineDeclarationChecker.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineDeclarationChecker.kt
@@ -18,6 +18,10 @@
 import org.jetbrains.kotlin.diagnostics.reportOn
 import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.analysis.checkers.*
 import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
+import org.jetbrains.kotlin.fir.analysis.checkers.expression.isDataClassCopy
+import org.jetbrains.kotlin.fir.analysis.checkers.getDirectOverriddenSymbols
+import org.jetbrains.kotlin.fir.analysis.checkers.inlineCheckerExtension
+import org.jetbrains.kotlin.fir.analysis.checkers.isInlineOnly
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
 import org.jetbrains.kotlin.fir.declarations.*
 import org.jetbrains.kotlin.fir.declarations.utils.*
@@ -63,6 +67,34 @@
         // prevent delegation to visitQualifiedAccessExpression, which causes redundant diagnostics
         override fun visitSmartCastExpression(smartCastExpression: FirSmartCastExpression, data: CheckerContext) {}
 
+        private fun accessedDeclarationEffectiveVisibility(
+            accessExpression: FirStatement,
+            accessedSymbol: FirBasedSymbol<*>,
+            context: CheckerContext,
+        ): EffectiveVisibility {
+            val recordedEffectiveVisibility = when (accessedSymbol) {
+                is FirCallableSymbol<*> -> accessedSymbol.publishedApiEffectiveVisibility ?: accessedSymbol.effectiveVisibility
+                is FirClassLikeSymbol<*> -> accessedSymbol.publishedApiEffectiveVisibility ?: accessedSymbol.effectiveVisibility
+                else -> shouldNotBeCalled()
+            }
+            return when {
+                recordedEffectiveVisibility.isReachableDueToLocalDispatchReceiver(accessExpression, context) -> EffectiveVisibility.Public
+                recordedEffectiveVisibility == EffectiveVisibility.Local -> EffectiveVisibility.Public
+                else -> recordedEffectiveVisibility
+            }
+        }
+
+        private fun shouldReportNonPublicCallFromPublicInline(
+            accessedDeclarationEffectiveVisibility: EffectiveVisibility,
+            declarationVisibility: Visibility,
+        ): Boolean {
+            val isCalledFunPublicOrPublishedApi = accessedDeclarationEffectiveVisibility.publicApi
+            val isInlineFunPublicOrPublishedApi = inlineFunEffectiveVisibility.publicApi
+            return isInlineFunPublicOrPublishedApi &&
+                    !isCalledFunPublicOrPublishedApi &&
+                    declarationVisibility !== Visibilities.Local
+        }
+
         internal fun checkAccessedDeclaration(
             source: KtSourceElement,
             accessExpression: FirStatement,
@@ -71,37 +103,27 @@
             context: CheckerContext,
             reporter: DiagnosticReporter,
         ): AccessedDeclarationVisibilityData {
-            val recordedEffectiveVisibility = when (accessedSymbol) {
-                is FirCallableSymbol<*> -> accessedSymbol.publishedApiEffectiveVisibility ?: accessedSymbol.effectiveVisibility
-                is FirClassLikeSymbol<*> -> accessedSymbol.publishedApiEffectiveVisibility ?: accessedSymbol.effectiveVisibility
-                else -> shouldNotBeCalled()
-            }
-
-            val accessedDeclarationEffectiveVisibility = when {
-                recordedEffectiveVisibility.isReachableDueToLocalDispatchReceiver(accessExpression, context) -> EffectiveVisibility.Public
-                recordedEffectiveVisibility == EffectiveVisibility.Local -> EffectiveVisibility.Public
-                else -> recordedEffectiveVisibility
-            }
-            val isCalledFunPublicOrPublishedApi = accessedDeclarationEffectiveVisibility.publicApi
-            val isInlineFunPublicOrPublishedApi = inlineFunEffectiveVisibility.publicApi
-            if (isInlineFunPublicOrPublishedApi &&
-                !isCalledFunPublicOrPublishedApi &&
-                declarationVisibility !== Visibilities.Local
-            ) {
-                reporter.reportOn(
-                    source,
-                    getNonPublicCallFromPublicInlineFactory(accessExpression, accessedSymbol, source, context),
-                    accessedSymbol,
-                    inlineFunction.symbol,
-                    context
-                )
-            } else {
-                checkPrivateClassMemberAccess(accessedSymbol, source, context, reporter)
+            val accessedVisibility = accessedDeclarationEffectiveVisibility(accessExpression, accessedSymbol, context)
+            val accessedDataCopyVisibility = accessedSymbol.unwrapDataClassCopyWithPrimaryConstructorOrNull(context.session)
+                ?.effectiveVisibility
+            when {
+                shouldReportNonPublicCallFromPublicInline(accessedVisibility, declarationVisibility) ->
+                    reporter.reportOn(
+                        source,
+                        getNonPublicCallFromPublicInlineFactory(accessExpression, accessedSymbol, source, context),
+                        accessedSymbol,
+                        inlineFunction.symbol,
+                        context
+                    )
+                accessedDataCopyVisibility != null &&
+                        shouldReportNonPublicCallFromPublicInline(accessedDataCopyVisibility, declarationVisibility) ->
+                    reporter.reportOn(source, FirErrors.NON_PUBLIC_DATA_COPY_CALL_FROM_PUBLIC_INLINE, context)
+                else -> checkPrivateClassMemberAccess(accessedSymbol, source, context, reporter)
             }
             return AccessedDeclarationVisibilityData(
-                isInlineFunPublicOrPublishedApi,
-                isCalledFunPublicOrPublishedApi,
-                accessedDeclarationEffectiveVisibility
+                inlineFunEffectiveVisibility.publicApi,
+                accessedVisibility.publicApi,
+                accessedVisibility
             )
         }
 
@@ -573,3 +595,8 @@
         session,
     )
 }
+
+fun FirBasedSymbol<*>.unwrapDataClassCopyWithPrimaryConstructorOrNull(session: FirSession): FirCallableSymbol<*>? =
+    (this as? FirCallableSymbol<*>)?.containingClassLookupTag()?.toSymbol(session)?.let { it as? FirClassSymbol<*> }
+        ?.takeIf { containingClass -> isDataClassCopy(containingClass, session) }
+        ?.primaryConstructorSymbol(session)
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirDataClassCopyUsageWillBecomeInaccessibleChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirDataClassCopyUsageWillBecomeInaccessibleChecker.kt
index 45b1e1b..eb336f8 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirDataClassCopyUsageWillBecomeInaccessibleChecker.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirDataClassCopyUsageWillBecomeInaccessibleChecker.kt
@@ -67,10 +67,11 @@
     }
 }
 
-internal fun FirCallableSymbol<*>.isDataClassCopy(containingClass: FirClassSymbol<*>, session: FirSession): Boolean {
-    if (!DataClassResolver.isCopy(name)) return false
-    val constructor = containingClass.primaryConstructorSymbol(session)
+internal fun FirCallableSymbol<*>.isDataClassCopy(containingClass: FirClassSymbol<*>?, session: FirSession): Boolean {
+    val constructor = containingClass?.primaryConstructorSymbol(session)
     return this is FirNamedFunctionSymbol &&
+            DataClassResolver.isCopy(name) &&
+            containingClass != null &&
             containingClass.isData &&
             containingClass.classKind.isClass &&
             dispatchReceiverType?.classId == containingClass.classId &&
diff --git a/compiler/testData/diagnostics/tests/dataClassNonPublicConstructor/deprecationPhase1_inlineFun.fir.kt b/compiler/testData/diagnostics/tests/dataClassNonPublicConstructor/deprecationPhase1_inlineFun.fir.kt
index 381f36b..bac9dfb 100644
--- a/compiler/testData/diagnostics/tests/dataClassNonPublicConstructor/deprecationPhase1_inlineFun.fir.kt
+++ b/compiler/testData/diagnostics/tests/dataClassNonPublicConstructor/deprecationPhase1_inlineFun.fir.kt
@@ -2,18 +2,18 @@
 // WITH_STDLIB
 data class PrivateInline <!DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING!>private<!> constructor(val value: Int) {
     <!NOTHING_TO_INLINE!>inline<!> fun huh() {
-        copy()
+        <!NON_PUBLIC_DATA_COPY_CALL_FROM_PUBLIC_INLINE_WARNING!>copy<!>()
     }
 }
 
 data class PublishedApiInline @PublishedApi <!DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING!>internal<!> constructor(val value: Int) {
     <!NOTHING_TO_INLINE!>inline<!> fun huh() {
-        copy()
+        <!NON_PUBLIC_DATA_COPY_CALL_FROM_PUBLIC_INLINE_WARNING!>copy<!>()
     }
 }
 
 data class InternalInline <!DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING!>internal<!> constructor(val value: Int) {
     <!NOTHING_TO_INLINE!>inline<!> fun huh() {
-        copy()
+        <!NON_PUBLIC_DATA_COPY_CALL_FROM_PUBLIC_INLINE_WARNING!>copy<!>()
     }
 }
diff --git a/compiler/testData/diagnostics/tests/dataClassNonPublicConstructor/deprecationPhase2_inlineFun.fir.kt b/compiler/testData/diagnostics/tests/dataClassNonPublicConstructor/deprecationPhase2_inlineFun.fir.kt
index 4fc41cc..395f53c 100644
--- a/compiler/testData/diagnostics/tests/dataClassNonPublicConstructor/deprecationPhase2_inlineFun.fir.kt
+++ b/compiler/testData/diagnostics/tests/dataClassNonPublicConstructor/deprecationPhase2_inlineFun.fir.kt
@@ -2,18 +2,18 @@
 // WITH_STDLIB
 data class PrivateInline <!DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_ERROR!>private<!> constructor(val value: Int) {
     <!NOTHING_TO_INLINE!>inline<!> fun huh() {
-        copy()
+        <!NON_PUBLIC_DATA_COPY_CALL_FROM_PUBLIC_INLINE_ERROR!>copy<!>()
     }
 }
 
 data class PublishedApiInline @PublishedApi <!DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_ERROR!>internal<!> constructor(val value: Int) {
     <!NOTHING_TO_INLINE!>inline<!> fun huh() {
-        copy()
+        <!NON_PUBLIC_DATA_COPY_CALL_FROM_PUBLIC_INLINE_ERROR!>copy<!>()
     }
 }
 
 data class InternalInline <!DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_ERROR!>internal<!> constructor(val value: Int) {
     <!NOTHING_TO_INLINE!>inline<!> fun huh() {
-        copy()
+        <!NON_PUBLIC_DATA_COPY_CALL_FROM_PUBLIC_INLINE_ERROR!>copy<!>()
     }
 }