From 911e62257abece442dc07961053cf82575a1da8c Mon Sep 17 00:00:00 2001 From: Kirill Rakhman Date: Mon, 19 Jun 2023 15:02:57 +0200 Subject: [PATCH] [FIR] Improve INVISIBLE_REFERENCE message Report the nearest invisible containing declaration in cases where the declaration itself is visible. #KT-53820 Fixed --- .../diagnostics/KtFirDataClassConverters.kt | 2 + .../api/fir/diagnostics/KtFirDiagnostics.kt | 2 + .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 2 + .../resolveCall/privateMember.txt | 2 +- .../delegatedConstructor.txt | 2 +- .../singleCandidate/privateMember.txt | 2 +- .../expresssions/nestedVisibility.fir.txt | 6 +- .../expresssions/privateVisibility.fir.txt | 4 +- .../expresssions/protectedVisibility.fir.txt | 2 +- .../diagnostics/FirDiagnosticsList.kt | 3 + .../fir/analysis/diagnostics/FirErrors.kt | 3 +- .../FirDestructuringDeclarationChecker.kt | 8 +- .../checkers/declaration/FirImportsChecker.kt | 7 +- .../FirVisibilityQualifierChecker.kt | 7 +- .../diagnostics/FirDiagnosticRenderers.kt | 6 +- .../diagnostics/FirErrorsDefaultMessages.kt | 3 +- .../coneDiagnosticToFirDiagnostic.kt | 11 +- .../kotlin/fir/FirVisibilityChecker.kt | 117 ++++++++++-------- .../kotlin/fir/resolve/ResolveUtils.kt | 48 +++++-- .../fir/resolve/calls/VisibilityUtils.kt | 15 ++- .../invisibleClassObjects.diag.txt | 46 +++++++ .../invisibleClassObjects.fir.diag.txt | 27 ++++ .../classObjects/invisibleClassObjects.fir.kt | 5 + .../classObjects/invisibleClassObjects.kt | 5 + 24 files changed, 237 insertions(+), 98 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.diag.txt create mode 100644 compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.fir.diag.txt diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index ff2b0fb173d..dbc280ad1c5 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -256,6 +256,8 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert add(FirErrors.INVISIBLE_REFERENCE) { firDiagnostic -> InvisibleReferenceImpl( firSymbolBuilder.buildSymbol(firDiagnostic.a), + firDiagnostic.b, + firDiagnostic.c, firDiagnostic as KtPsiDiagnostic, token, ) diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 3e32432d275..282bc753b04 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -220,6 +220,8 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { interface InvisibleReference : KtFirDiagnostic { override val diagnosticClass get() = InvisibleReference::class val reference: KtSymbol + val visible: Visibility + val containingDeclaration: ClassId? } interface UnresolvedReference : KtFirDiagnostic { diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index a08cd2e55bf..6aa4baafc8f 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -248,6 +248,8 @@ internal class InvisibleSetterImpl( internal class InvisibleReferenceImpl( override val reference: KtSymbol, + override val visible: Visibility, + override val containingDeclaration: ClassId?, firDiagnostic: KtPsiDiagnostic, token: KtLifetimeToken, ) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.InvisibleReference diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCall/privateMember.txt b/analysis/analysis-api/testData/components/callResolver/resolveCall/privateMember.txt index 70cd2209ba8..d5d6ba35ad8 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCall/privateMember.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCall/privateMember.txt @@ -17,4 +17,4 @@ KtErrorCallInfo: typeArgumentsMapping = {} argumentMapping = {} ] - diagnostic = ERROR \ No newline at end of file + diagnostic = ERROR \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/delegatedConstructor.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/delegatedConstructor.txt index 8550453be68..54984b4ba87 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/delegatedConstructor.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/delegatedConstructor.txt @@ -16,7 +16,7 @@ KtInapplicableCallCandidateInfo: isInBestCandidates = false KtInapplicableCallCandidateInfo: - diagnostic = ERROR + diagnostic = ERROR candidate = KtDelegatedConstructorCall: kind = SUPER_CALL partiallyAppliedSymbol = KtPartiallyAppliedSymbol: diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/privateMember.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/privateMember.txt index 803c2954927..0e8103fe7c5 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/privateMember.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/privateMember.txt @@ -1,5 +1,5 @@ KtInapplicableCallCandidateInfo: - diagnostic = ERROR + diagnostic = ERROR candidate = KtSimpleFunctionCall: isImplicitInvoke = false partiallyAppliedSymbol = KtPartiallyAppliedSymbol: diff --git a/compiler/fir/analysis-tests/testData/resolve/expresssions/nestedVisibility.fir.txt b/compiler/fir/analysis-tests/testData/resolve/expresssions/nestedVisibility.fir.txt index 3492407bc80..25e75892783 100644 --- a/compiler/fir/analysis-tests/testData/resolve/expresssions/nestedVisibility.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/expresssions/nestedVisibility.fir.txt @@ -53,7 +53,7 @@ FILE: nestedVisibility.kt } public final fun foo(): R|kotlin/Unit| { - Q|Outer|.R|/Outer.PrivateNested.PrivateNested#|() + Q|Outer|.R|/Outer.PrivateNested.PrivateNested#|() this@R|/Derived|.super.R|/Outer.PrivateInner.PrivateInner#|() Q|Outer|.R|/Outer.ProtectedNested.ProtectedNested|() this@R|/Derived|.super.R|/Outer.ProtectedInner.ProtectedInner|() @@ -63,9 +63,9 @@ FILE: nestedVisibility.kt } public final fun foo(): R|kotlin/Unit| { - Q|Outer|.R|/Outer.PrivateNested.PrivateNested#|() + Q|Outer|.R|/Outer.PrivateNested.PrivateNested#|() R|/Outer.Outer|().R|/Outer.PrivateInner.PrivateInner#|() - Q|Outer|.R|/Outer.ProtectedNested.ProtectedNested#|() + Q|Outer|.R|/Outer.ProtectedNested.ProtectedNested#|() R|/Outer.Outer|().R|/Outer.ProtectedInner.ProtectedInner#|() Q|Outer|.R|/Outer.PublicNested.PublicNested|() R|/Outer.Outer|().R|/Outer.PublicInner.PublicInner|() diff --git a/compiler/fir/analysis-tests/testData/resolve/expresssions/privateVisibility.fir.txt b/compiler/fir/analysis-tests/testData/resolve/expresssions/privateVisibility.fir.txt index a3c0349ed75..ce1246df6c9 100644 --- a/compiler/fir/analysis-tests/testData/resolve/expresssions/privateVisibility.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/expresssions/privateVisibility.fir.txt @@ -96,11 +96,11 @@ FILE: first.kt R|/Private.Private|().R|/Private.baz|() R|/Private.Private|().R|/Private.Inner.Inner|() R|/Private.Private|().R|/Private.bar#|() - Q|Private|.R|/Private.Nested.Nested#|() + Q|Private|.R|/Private.Nested.Nested#|() Q|Private|.R|/Private.Companion.fromCompanion#|() } FILE: second.kt public final fun secondTest(): R|kotlin/Unit| { R|/foo#|() - R|/Private.Private#|() + R|/Private.Private#|() } diff --git a/compiler/fir/analysis-tests/testData/resolve/expresssions/protectedVisibility.fir.txt b/compiler/fir/analysis-tests/testData/resolve/expresssions/protectedVisibility.fir.txt index 3276fe4acec..49e2fc50341 100644 --- a/compiler/fir/analysis-tests/testData/resolve/expresssions/protectedVisibility.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/expresssions/protectedVisibility.fir.txt @@ -80,7 +80,7 @@ FILE: protectedVisibility.kt R|/Protected.Protected|().R|/Protected.baz|() R|/Protected.Protected|().R|/Protected.Inner.Inner|() R|/Protected.Protected|().R|/Protected.bar#|() - Q|Protected|.R|/Protected.Nested.Nested#|() + Q|Protected|.R|/Protected.Nested.Nested#|() } public open class Generic : R|kotlin/Any| { public constructor(x: R|T|): R|Generic| { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index f86052859e6..e930d76eb9d 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* @@ -103,6 +104,8 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val UNRESOLVED by object : DiagnosticGroup("Unresolved") { val INVISIBLE_REFERENCE by error(PositioningStrategy.REFERENCE_BY_QUALIFIED) { parameter("reference") + parameter("visible") + parameter("containingDeclaration") } val UNRESOLVED_REFERENCE by error(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) { parameter("reference") diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 942a5597088..f010f9e0166 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -52,6 +52,7 @@ import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtAnnotationEntry @@ -147,7 +148,7 @@ object FirErrors { val INVISIBLE_SETTER by error3(SourceElementPositioningStrategies.SELECTOR_BY_QUALIFIED) // Unresolved - val INVISIBLE_REFERENCE by error1>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) + val INVISIBLE_REFERENCE by error3, Visibility, ClassId?>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) val UNRESOLVED_REFERENCE by error1(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED) val UNRESOLVED_LABEL by error0(SourceElementPositioningStrategies.LABEL) val DESERIALIZATION_ERROR by error0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDestructuringDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDestructuringDeclarationChecker.kt index bbb85087bbe..c55cc515c2a 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDestructuringDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDestructuringDeclarationChecker.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.analysis.diagnostics.toInvisibleReferenceDiagnostic import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.FirValueParameter @@ -176,12 +177,7 @@ object FirDestructuringDeclarationChecker : FirPropertyChecker() { } } is ConeVisibilityError -> { - reporter.reportOn( - property.source, - FirErrors.INVISIBLE_REFERENCE, - diagnostic.symbol, - context, - ) + reporter.report(diagnostic.symbol.toInvisibleReferenceDiagnostic(property.source), context) } else -> error("Unhandled error during a component function call") } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirImportsChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirImportsChecker.kt index b6a4f025867..0f04fd846a7 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirImportsChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirImportsChecker.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirDeprecationChecker import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.analysis.diagnostics.toInvisibleReferenceDiagnostic import org.jetbrains.kotlin.fir.analysis.getSourceForImportSegment import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass @@ -75,7 +76,7 @@ object FirImportsChecker : FirFileChecker() { fun reportInvisibleParentClasses(classSymbol: FirRegularClassSymbol, depth: Int) { if (!classSymbol.fir.isVisible(context)) { val source = import.getSourceForImportSegment(indexFromLast = depth) - reporter.reportOn(source, FirErrors.INVISIBLE_REFERENCE, classSymbol, context) + reporter.report(classSymbol.toInvisibleReferenceDiagnostic(source), context) } classSymbol.classId.outerClassId?.resolveToClass(context)?.let { reportInvisibleParentClasses(it, depth + 1) } @@ -87,7 +88,7 @@ object FirImportsChecker : FirFileChecker() { ImportStatus.OK -> return is ImportStatus.Invisible -> { val source = import.getSourceForImportSegment(0) - reporter.reportOn(source, FirErrors.INVISIBLE_REFERENCE, status.symbol, context) + reporter.report(status.symbol.toInvisibleReferenceDiagnostic(source), context) } else -> { val classId = parentClassSymbol.classId.createNestedClassId(importedName) @@ -128,7 +129,7 @@ object FirImportsChecker : FirFileChecker() { resolvedDeclaration?.let { val source = import.getSourceForImportSegment(0) ?: import.source - reporter.reportOn(source, FirErrors.INVISIBLE_REFERENCE, it.symbol, context) + reporter.report(it.symbol.toInvisibleReferenceDiagnostic(source), context) return } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirVisibilityQualifierChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirVisibilityQualifierChecker.kt index 1569d676ef5..53deaa9d20e 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirVisibilityQualifierChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirVisibilityQualifierChecker.kt @@ -6,10 +6,9 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression import org.jetbrains.kotlin.KtFakeSourceElementKind -import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.diagnostics.DiagnosticReporter -import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors -import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.diagnostics.toInvisibleReferenceDiagnostic import org.jetbrains.kotlin.fir.declarations.FirTypeAlias import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType import org.jetbrains.kotlin.fir.expressions.FirErrorResolvedQualifier @@ -45,7 +44,7 @@ object FirVisibilityQualifierChecker : FirResolvedQualifierChecker() { ) ) { if (expression !is FirErrorResolvedQualifier || expression.diagnostic !is ConeVisibilityError) { - reporter.reportOn(expression.source, FirErrors.INVISIBLE_REFERENCE, symbol, context) + reporter.report(symbol.toInvisibleReferenceDiagnostic(expression.source), context) } return diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDiagnosticRenderers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDiagnosticRenderers.kt index 0c4984052fb..be866619756 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDiagnosticRenderers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDiagnosticRenderers.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.renderReadableWithFqNames import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId object FirDiagnosticRenderers { @OptIn(SymbolInternals::class) @@ -148,7 +149,10 @@ object FirDiagnosticRenderers { } val NAME_OF_CONTAINING_DECLARATION_OR_FILE = Renderer { symbol: CallableId -> - val classId = symbol.classId + NAME_OF_DECLARATION_OR_FILE.render(symbol.classId) + } + + val NAME_OF_DECLARATION_OR_FILE = Renderer { classId: ClassId? -> if (classId == null) { "file" } else { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt index 0e585cf14ad..ee016ce3c3b 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FUNC import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOLS_ON_NEWLINE_WITH_INDENT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.MODULE_DATA import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NAME_OF_CONTAINING_DECLARATION_OR_FILE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NAME_OF_DECLARATION_OR_FILE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_CLASS_OR_OBJECT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_CLASS_OR_OBJECT_NAME import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_COLLECTION_OF_TYPES @@ -704,7 +705,7 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() { map.put(NULL_FOR_NONNULL_TYPE, "Null can not be a value of a non-null type") // Unresolved - map.put(INVISIBLE_REFERENCE, "Symbol {0} is invisible", SYMBOL) + map.put(INVISIBLE_REFERENCE, "Cannot access ''{0}'': it is {1} in {2}", SYMBOL, VISIBILITY, NAME_OF_DECLARATION_OR_FILE) map.put( INVISIBLE_SETTER, "Cannot access ''{0}'': it is {1} in {2}", diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt index 3db0e6550f4..740c1fc4edd 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.declarations.utils.isExpect import org.jetbrains.kotlin.fir.declarations.utils.isInfix import org.jetbrains.kotlin.fir.declarations.utils.isInner import org.jetbrains.kotlin.fir.declarations.utils.isOperator +import org.jetbrains.kotlin.fir.declarations.utils.visibility import org.jetbrains.kotlin.fir.diagnostics.* import org.jetbrains.kotlin.fir.resolve.calls.* import org.jetbrains.kotlin.fir.resolve.diagnostics.* @@ -27,6 +28,7 @@ import org.jetbrains.kotlin.fir.resolve.inference.model.ConeArgumentConstraintPo import org.jetbrains.kotlin.fir.resolve.inference.model.ConeExpectedTypeConstraintPosition import org.jetbrains.kotlin.fir.resolve.inference.model.ConeLambdaArgumentConstraintPosition import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag +import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol @@ -40,6 +42,7 @@ import org.jetbrains.kotlin.types.EmptyIntersectionTypeKind import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.runIf +import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled private fun ConeDiagnostic.toKtDiagnostic( source: KtSourceElement, @@ -73,7 +76,7 @@ private fun ConeDiagnostic.toKtDiagnostic( ) } - is ConeVisibilityError -> FirErrors.INVISIBLE_REFERENCE.createOn(source, this.symbol) + is ConeVisibilityError -> symbol.toInvisibleReferenceDiagnostic(source) is ConeInapplicableWrongReceiver -> FirErrors.UNRESOLVED_REFERENCE_WRONG_RECEIVER.createOn(source, this.candidateSymbols) is ConeNoCompanionObject -> FirErrors.NO_COMPANION_OBJECT.createOn(source, this.candidateSymbol as FirClassLikeSymbol<*>) is ConeAmbiguityError -> when { @@ -150,6 +153,12 @@ private fun ConeDiagnostic.toKtDiagnostic( else -> throw IllegalArgumentException("Unsupported diagnostic type: ${this.javaClass}") } +fun FirBasedSymbol<*>.toInvisibleReferenceDiagnostic(source: KtSourceElement?): KtDiagnostic? = when (val symbol = this) { + is FirCallableSymbol<*> -> FirErrors.INVISIBLE_REFERENCE.createOn(source, symbol, symbol.visibility, symbol.callableId.classId) + is FirClassLikeSymbol<*> -> FirErrors.INVISIBLE_REFERENCE.createOn(source, symbol, symbol.visibility, symbol.classId.outerClassId) + else -> shouldNotBeCalled("Unexpected receiver $javaClass") +} + fun ConeDiagnostic.toFirDiagnostics( session: FirSession, source: KtSourceElement, diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt index 91a3848212e..b6c8bef3b43 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt @@ -130,13 +130,7 @@ abstract class FirVisibilityChecker : FirSessionComponent { supertypeSupplier ) } - val parentClass = declaration.containingNonLocalClass( - session, - dispatchReceiver, - containingDeclarations, - supertypeSupplier - ) ?: return true - return generateSequence(parentClass) { it.containingNonLocalClass(session) }.all { parent -> + return declaration.parentDeclarationSequence(session, dispatchReceiver, containingDeclarations, supertypeSupplier)?.all { parent -> isSpecificDeclarationVisible( parent, session, @@ -146,7 +140,7 @@ abstract class FirVisibilityChecker : FirSessionComponent { isCallToPropertySetter, supertypeSupplier ) - } + } ?: true } fun isVisibleForOverriding( @@ -169,51 +163,6 @@ abstract class FirVisibilityChecker : FirSessionComponent { else -> platformOverrideVisibilityCheck(symbolFromDerivedClass, candidateInBaseClass.symbol, candidateInBaseClass.visibility) } - private fun FirMemberDeclaration.containingNonLocalClass( - session: FirSession, - dispatchReceiver: FirExpression?, - containingUseSiteDeclarations: List, - supertypeSupplier: SupertypeSupplier - ): FirClassLikeDeclaration? { - return when (this) { - is FirCallableDeclaration -> { - if (dispatchReceiver != null) { - val baseReceiverType = dispatchReceiverClassTypeOrNull() - if (baseReceiverType != null) { - dispatchReceiver.typeRef.coneType.findClassRepresentation(baseReceiverType, session)?.toSymbol(session)?.fir?.let { - return it - } - } - } - - val containingLookupTag = this.containingClassLookupTag() - val containingClass = containingLookupTag?.toSymbol(session)?.fir - - if (isStatic && containingClass != null) { - containingUseSiteDeclarations.firstNotNullOfOrNull { - if (it !is FirClass) return@firstNotNullOfOrNull null - it.takeIf { it.isSubclassOf(containingLookupTag, session, isStrict = false, supertypeSupplier) } - }?.let { return it } - } - - containingClass - } - is FirClassLikeDeclaration -> containingNonLocalClass(session) - } - } - - private fun FirClassLikeDeclaration.containingNonLocalClass(session: FirSession): FirClassLikeDeclaration? { - return when (this) { - is FirClass -> { - if (isLocal) return null - - this.classId.outerClassId?.let { session.symbolProvider.getClassLikeSymbolByClassId(it)?.fir } - } - // Currently, type aliases are only top-level - is FirTypeAlias -> null - } - } - private fun isSpecificDeclarationVisible( declaration: FirMemberDeclaration, session: FirSession, @@ -521,3 +470,65 @@ fun FirBasedSymbol<*>.getOwnerLookupTag(): ConeClassLikeLookupTag? { fun FirBasedSymbol<*>.isVariableOrNamedFunction(): Boolean { return this is FirVariableSymbol || this is FirNamedFunctionSymbol || this is FirPropertyAccessorSymbol } + + +fun FirMemberDeclaration.parentDeclarationSequence( + session: FirSession, + dispatchReceiver: FirExpression?, + containingDeclarations: List, + supertypeSupplier: SupertypeSupplier = SupertypeSupplier.Default, +): Sequence? { + val parentClass = containingNonLocalClass( + session, + dispatchReceiver, + containingDeclarations, + supertypeSupplier + ) ?: return null + + return generateSequence(parentClass) { it.containingNonLocalClass(session) } +} + +private fun FirMemberDeclaration.containingNonLocalClass( + session: FirSession, + dispatchReceiver: FirExpression?, + containingUseSiteDeclarations: List, + supertypeSupplier: SupertypeSupplier +): FirClassLikeDeclaration? { + return when (this) { + is FirCallableDeclaration -> { + if (dispatchReceiver != null) { + val baseReceiverType = dispatchReceiverClassTypeOrNull() + if (baseReceiverType != null) { + dispatchReceiver.typeRef.coneType.findClassRepresentation(baseReceiverType, session)?.toSymbol(session)?.fir?.let { + return it + } + } + } + + val containingLookupTag = this.containingClassLookupTag() + val containingClass = containingLookupTag?.toSymbol(session)?.fir + + if (isStatic && containingClass != null) { + containingUseSiteDeclarations.firstNotNullOfOrNull { + if (it !is FirClass) return@firstNotNullOfOrNull null + it.takeIf { it.isSubclassOf(containingLookupTag, session, isStrict = false, supertypeSupplier) } + }?.let { return it } + } + + containingClass + } + is FirClassLikeDeclaration -> containingNonLocalClass(session) + } +} + +private fun FirClassLikeDeclaration.containingNonLocalClass(session: FirSession): FirClassLikeDeclaration? { + return when (this) { + is FirClass -> { + if (isLocal) return null + + this.classId.outerClassId?.let { session.symbolProvider.getClassLikeSymbolByClassId(it)?.fir } + } + // Currently, type aliases are only top-level + is FirTypeAlias -> null + } +} 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 0aa2af58cb6..b9371a338cf 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 @@ -21,10 +21,7 @@ import org.jetbrains.kotlin.fir.expressions.impl.FirUnitExpression import org.jetbrains.kotlin.fir.references.* import org.jetbrains.kotlin.fir.references.builder.buildErrorNamedReference import org.jetbrains.kotlin.fir.references.builder.buildResolvedErrorReference -import org.jetbrains.kotlin.fir.resolve.calls.Candidate -import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate -import org.jetbrains.kotlin.fir.resolve.calls.FirPropertyWithExplicitBackingFieldResolvedNamedReference -import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue +import org.jetbrains.kotlin.fir.resolve.calls.* import org.jetbrains.kotlin.fir.resolve.dfa.PropertyStability import org.jetbrains.kotlin.fir.resolve.diagnostics.* import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider @@ -193,7 +190,7 @@ fun BodyResolveComponents.buildResolvedQualifierForClass( typeArgumentsForQualifier: List = emptyList(), diagnostic: ConeDiagnostic? = null, nonFatalDiagnostics: List = emptyList(), - annotations: List = emptyList() + annotations: List = emptyList(), ): FirResolvedQualifier { val classId = regularClass.classId @@ -370,7 +367,7 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: FirBasedSymbol<*>, make } fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo( - qualifiedAccessExpression: FirQualifiedAccessExpression + qualifiedAccessExpression: FirQualifiedAccessExpression, ): FirExpression { val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression) ?: return qualifiedAccessExpression @@ -379,7 +376,7 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo( } fun BodyResolveComponents.transformWhenSubjectExpressionUsingSmartcastInfo( - whenSubjectExpression: FirWhenSubjectExpression + whenSubjectExpression: FirWhenSubjectExpression, ): FirExpression { val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(whenSubjectExpression) ?: return whenSubjectExpression @@ -388,7 +385,7 @@ fun BodyResolveComponents.transformWhenSubjectExpressionUsingSmartcastInfo( } fun BodyResolveComponents.transformDesugaredAssignmentValueUsingSmartcastInfo( - expression: FirDesugaredAssignmentValueReferenceExpression + expression: FirDesugaredAssignmentValueReferenceExpression, ): FirExpression { val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(expression.expressionRef.value) ?: return expression @@ -410,7 +407,7 @@ private fun FirSmartCastExpressionBuilder.applyResultTypeRef() { private fun BodyResolveComponents.transformExpressionUsingSmartcastInfo( expression: T, stability: PropertyStability, - typesFromSmartCast: MutableList + typesFromSmartCast: MutableList, ): FirSmartCastExpression? { val smartcastStability = stability.impliedSmartcastStability ?: if (dataFlowAnalyzer.isAccessToUnstableLocalVariable(expression)) { @@ -477,7 +474,7 @@ private fun BodyResolveComponents.transformExpressionUsingSm fun FirCheckedSafeCallSubject.propagateTypeFromOriginalReceiver( nullableReceiverExpression: FirExpression, session: FirSession, - file: FirFile + file: FirFile, ) { // If the receiver expression is smartcast to `null`, it would have `Nothing?` as its type, which may not have members called by user // code. Hence, we fallback to the type before intersecting with `Nothing?`. @@ -555,7 +552,7 @@ fun FirFunction.getAsForbiddenNamedArgumentsTarget( session: FirSession, // NB: with originScope given this function will try to find overridden declaration with allowed parameter names // for intersection/substitution overrides - originScope: FirTypeScope? = null + originScope: FirTypeScope? = null, ): ForbiddenNamedArgumentsTarget? { if (hasStableParameterNames) return null @@ -602,11 +599,36 @@ fun FirExpression?.isIntegerLiteralOrOperatorCall(): Boolean { fun createConeDiagnosticForCandidateWithError( applicability: CandidateApplicability, - candidate: Candidate + candidate: Candidate, ): ConeDiagnostic { return when (applicability) { CandidateApplicability.HIDDEN -> ConeHiddenCandidateError(candidate) - CandidateApplicability.K2_VISIBILITY_ERROR -> ConeVisibilityError(candidate.symbol) + CandidateApplicability.K2_VISIBILITY_ERROR -> { + val session = candidate.callInfo.session + val declaration = candidate.symbol.fir + if (declaration is FirMemberDeclaration && + session.visibilityChecker.isVisible(declaration, candidate, skipCheckForContainingClassVisibility = true) + ) { + // We can have declarations that are visible by themselves, but some containing declaration is invisible. + // We report the nearest invisible containing declaration, otherwise we'll get a confusing diagnostic like + // Cannot access 'foo', it is public in 'Bar'. + declaration + .parentDeclarationSequence(session, candidate.dispatchReceiver, candidate.callInfo.containingDeclarations) + ?.firstOrNull { + !session.visibilityChecker.isVisible( + it, + session, + candidate.callInfo.containingFile, + candidate.callInfo.containingDeclarations, + dispatchReceiver = null, + skipCheckForContainingClassVisibility = true, + ) + }?.let { + return ConeVisibilityError(it.symbol) + } + } + ConeVisibilityError(candidate.symbol) + } CandidateApplicability.INAPPLICABLE_WRONG_RECEIVER -> ConeInapplicableWrongReceiver(listOf(candidate)) CandidateApplicability.K2_NO_COMPANION_OBJECT -> ConeNoCompanionObject(candidate) else -> ConeInapplicableCandidateError(applicability, candidate) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/VisibilityUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/VisibilityUtils.kt index 7c54eb7aae1..c6f81259183 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/VisibilityUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/VisibilityUtils.kt @@ -29,7 +29,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.runIf fun FirVisibilityChecker.isVisible( declaration: FirMemberDeclaration, callInfo: CallInfo, - dispatchReceiver: FirExpression? + dispatchReceiver: FirExpression?, + skipCheckForContainingClassVisibility: Boolean = false, ): Boolean { val staticQualifierForCallable = runIf( declaration is FirCallableDeclaration && @@ -50,28 +51,30 @@ fun FirVisibilityChecker.isVisible( callInfo.containingDeclarations, dispatchReceiver, staticQualifierClassForCallable = staticQualifierForCallable, - isCallToPropertySetter = callInfo.callSite is FirVariableAssignment + isCallToPropertySetter = callInfo.callSite is FirVariableAssignment, + skipCheckForContainingClassVisibility = skipCheckForContainingClassVisibility, ) } fun FirVisibilityChecker.isVisible( declaration: FirMemberDeclaration, - candidate: Candidate + candidate: Candidate, + skipCheckForContainingClassVisibility: Boolean = false, ): Boolean { val callInfo = candidate.callInfo - if (!isVisible(declaration, callInfo, candidate.dispatchReceiver)) { + if (!isVisible(declaration, callInfo, candidate.dispatchReceiver, skipCheckForContainingClassVisibility)) { val dispatchReceiverWithoutSmartCastType = removeSmartCastTypeForAttemptToFitVisibility(candidate.dispatchReceiver, candidate.callInfo.session) ?: return false - if (!isVisible(declaration, callInfo, dispatchReceiverWithoutSmartCastType)) return false + if (!isVisible(declaration, callInfo, dispatchReceiverWithoutSmartCastType, skipCheckForContainingClassVisibility)) return false candidate.dispatchReceiver = dispatchReceiverWithoutSmartCastType } val backingField = declaration.getBackingFieldIfApplicable() if (backingField != null) { - candidate.hasVisibleBackingField = isVisible(backingField, callInfo, candidate.dispatchReceiver) + candidate.hasVisibleBackingField = isVisible(backingField, callInfo, candidate.dispatchReceiver, skipCheckForContainingClassVisibility) } return true diff --git a/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.diag.txt b/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.diag.txt new file mode 100644 index 00000000000..ee589d65bdd --- /dev/null +++ b/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.diag.txt @@ -0,0 +1,46 @@ +/b.kt:34:10: error: cannot access 'B': it is private in file +import a.B + ^ +/b.kt:35:10: error: cannot access 'B': it is private in file +import a.B.Companion.bar + ^ +/b.kt:37:12: error: cannot access 'Companion': it is private in 'C' +import a.C.Companion.baz + ^ +/b.kt:38:10: error: cannot access 'D': it is private in file +import a.D + ^ +/b.kt:39:10: error: cannot access 'D': it is private in file +import a.D.Companion.quux + ^ +/b.kt:39:12: error: cannot access 'Companion': it is private in 'D' +import a.D.Companion.quux + ^ +/b.kt:43:7: error: cannot access 'B': it is private in file + f(B) + ^ +/b.kt:44:7: error: cannot access 'Companion': it is private in 'C' + f(C) + ^ +/b.kt:45:7: error: cannot access 'Companion': it is private in 'D' + f(D) + ^ +/b.kt:48:5: error: cannot access 'B': it is private in file + B.bar() + ^ +/b.kt:48:7: error: cannot access 'B': it is private in file + B.bar() + ^ +/b.kt:49:7: error: cannot access 'Companion': it is private in 'C' + C.baz() + ^ +/b.kt:50:5: error: cannot access 'D': it is private in file + D.quux() + ^ +/b.kt:50:7: error: cannot access 'Companion': it is private in 'D' + D.quux() + ^ +/b.kt:53:9: error: cannot access 'Companion': it is private in 'C' + a.C.baz() + ^ + diff --git a/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.fir.diag.txt b/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.fir.diag.txt new file mode 100644 index 00000000000..41db714ef04 --- /dev/null +++ b/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.fir.diag.txt @@ -0,0 +1,27 @@ +/b.kt:(96,97): error: Cannot access 'class B : Any': it is private in file + +/b.kt:(107,108): error: Cannot access 'class B : Any': it is private in file + +/b.kt:(145,154): error: Cannot access 'companion object Companion : Any': it is private in 'a/C' + +/b.kt:(168,169): error: Cannot access 'class D : Any': it is private in file + +/b.kt:(179,180): error: Cannot access 'class D : Any': it is private in file + +/b.kt:(181,190): error: Cannot access 'companion object Companion : Any': it is private in 'a/D' + +/b.kt:(225,226): error: Cannot access 'class B : Any': it is private in file + +/b.kt:(243,244): error: Cannot access 'class D : Any': it is private in file + +/b.kt:(263,264): error: Cannot access 'class B : Any': it is private in file + +/b.kt:(265,268): error: Cannot access 'class B : Any': it is private in file + +/b.kt:(277,280): error: Cannot access 'companion object Companion : Any': it is private in 'a/C' + +/b.kt:(287,288): error: Cannot access 'class D : Any': it is private in file + +/b.kt:(289,293): error: Cannot access 'companion object Companion : Any': it is private in 'a/D' + +/b.kt:(319,322): error: Cannot access 'companion object Companion : Any': it is private in 'a/C' diff --git a/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.fir.kt b/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.fir.kt index 58c5aeea7a2..cd6a65634dd 100644 --- a/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.fir.kt +++ b/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.fir.kt @@ -1,3 +1,4 @@ +// RENDER_DIAGNOSTICS_FULL_TEXT //FILE:a.kt package a @@ -29,9 +30,13 @@ private class D { package b import a.A +import a.A.Companion.foo import a.B +import a.B.Companion.bar import a.C +import a.C.Companion.baz import a.D +import a.D.Companion.quux fun test() { f(A) diff --git a/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.kt b/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.kt index 0ff5548191b..80a3c69d35b 100644 --- a/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.kt +++ b/compiler/testData/diagnostics/tests/classObjects/invisibleClassObjects.kt @@ -1,3 +1,4 @@ +// RENDER_DIAGNOSTICS_FULL_TEXT //FILE:a.kt package a @@ -29,9 +30,13 @@ private class D { package b import a.A +import a.A.Companion.foo import a.B +import a.B.Companion.bar import a.C +import a.C.Companion.baz import a.D +import a.D.Companion.quux fun test() { f(A)