From 9575d4d24297581dbc0bc2e3f668b11301ed60de Mon Sep 17 00:00:00 2001 From: "Denis.Zharkov" Date: Tue, 7 Sep 2021 13:09:05 +0300 Subject: [PATCH] FIR: Rework qualifier-in-expression resolution Get rid of stateful FirQualifiedNameResolver Resolve a.b.c-like calls step-by-step using just recursion ^KT-48363 Fixed --- ...irOldFrontendDiagnosticsTestGenerated.java | 6 + ...DiagnosticsWithLightTreeTestGenerated.java | 6 + .../jetbrains/kotlin/fir/FirCallResolver.kt | 102 +++++----- .../kotlin/fir/FirQualifiedNameResolver.kt | 109 ----------- .../kotlin/fir/QualifiedNameResolution.kt | 182 ++++++++++++++++++ .../fir/resolve/calls/CandidateFactory.kt | 1 - .../resolve/providers/impl/FirProviderImpl.kt | 11 +- .../FirAbstractBodyResolveTransformer.kt | 3 - .../FirExpressionsResolveTransformer.kt | 41 +++- ...irAnnotationArgumentsResolveTransformer.kt | 7 +- .../tests/PackageInExpressionPosition.fir.kt | 2 +- .../variableVsImportedClassifier.kt | 31 +++ .../test/runners/DiagnosticTestGenerated.java | 6 + ...stPartOfQualifiedCallWithNestedClasses.txt | 4 +- ...edPartOfQualifiedCallWithNestedClasses.txt | 4 +- ...CompilerTestFE10TestdataTestGenerated.java | 6 + ...ssObjectClassLiteralReferenceWithField.txt | 2 +- 17 files changed, 341 insertions(+), 182 deletions(-) delete mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirQualifiedNameResolver.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/QualifiedNameResolution.kt create mode 100644 compiler/testData/diagnostics/tests/qualifiedExpression/variableVsImportedClassifier.kt diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 8900eb78074..f87bd495ea0 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -22641,6 +22641,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/qualifiedExpression/TypeWithError.kt"); } + @Test + @TestMetadata("variableVsImportedClassifier.kt") + public void testVariableVsImportedClassifier() throws Exception { + runTest("compiler/testData/diagnostics/tests/qualifiedExpression/variableVsImportedClassifier.kt"); + } + @Test @TestMetadata("visibleClassVsQualifiedClass.kt") public void testVisibleClassVsQualifiedClass() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index 7c290d662b0..08c91323276 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -22641,6 +22641,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/qualifiedExpression/TypeWithError.kt"); } + @Test + @TestMetadata("variableVsImportedClassifier.kt") + public void testVariableVsImportedClassifier() throws Exception { + runTest("compiler/testData/diagnostics/tests/qualifiedExpression/variableVsImportedClassifier.kt"); + } + @Test @TestMetadata("visibleClassVsQualifiedClass.kt") public void testVisibleClassVsQualifiedClass() throws Exception { 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 0ba0482d6e6..62f5d89a8d3 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt @@ -49,7 +49,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs class FirCallResolver( private val components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents, - private val qualifiedResolver: FirQualifiedNameResolver, ) { private val session = components.session private val overloadByLambdaReturnTypeResolver = FirOverloadByLambdaReturnTypeResolver(components) @@ -72,7 +71,6 @@ class FirCallResolver( @OptIn(PrivateForInline::class) fun resolveCallAndSelectCandidate(functionCall: FirFunctionCall): FirFunctionCall { - qualifiedResolver.reset() @Suppress("NAME_SHADOWING") val functionCall = if (needTransformArguments) { components.context.withIncrementedQualifierPartIndex { functionCall.transformExplicitReceiver() } @@ -138,13 +136,22 @@ class FirCallResolver( explicitReceiver as? FirQualifiedAccessExpression ?: return transformExplicitReceiver(transformer, ResolutionMode.ContextIndependent) as Q - val callee = - explicitReceiver.calleeReference as? FirSuperReference - ?: return transformExplicitReceiver(transformer, ResolutionMode.ContextIndependent) as Q + (explicitReceiver.calleeReference as? FirSuperReference)?.let { + transformer.transformSuperReceiver(it, explicitReceiver, this) + return this + } - transformer.transformSuperReceiver(callee, explicitReceiver, this) + if (explicitReceiver is FirPropertyAccessExpression) { + this.replaceExplicitReceiver( + transformer.transformQualifiedAccessExpression( + explicitReceiver, ResolutionMode.ContextIndependent, + isUsedAsReceiver = true + ) as FirExpression + ) + return this + } - return this + return transformExplicitReceiver(transformer, ResolutionMode.ContextIndependent) as Q } private data class ResolutionResult( @@ -202,12 +209,15 @@ class FirCallResolver( return ResolutionResult(info, result.currentApplicability, reducedCandidates) } - fun resolveVariableAccessAndSelectCandidate(qualifiedAccess: T): FirStatement { - return resolveVariableAccessAndSelectCandidateImpl(qualifiedAccess) { true } + fun resolveVariableAccessAndSelectCandidate(qualifiedAccess: T, isUsedAsReceiver: Boolean): FirStatement { + return resolveVariableAccessAndSelectCandidateImpl(qualifiedAccess, isUsedAsReceiver) { true } } - fun resolveOnlyEnumOrQualifierAccessAndSelectCandidate(qualifiedAccess: FirQualifiedAccessExpression): FirStatement { - return resolveVariableAccessAndSelectCandidateImpl(qualifiedAccess) accept@{ candidates -> + fun resolveOnlyEnumOrQualifierAccessAndSelectCandidate( + qualifiedAccess: FirQualifiedAccessExpression, + isUsedAsReceiver: Boolean, + ): FirStatement { + return resolveVariableAccessAndSelectCandidateImpl(qualifiedAccess, isUsedAsReceiver) accept@{ candidates -> val symbol = candidates.singleOrNull()?.symbol ?: return@accept false symbol is FirEnumEntrySymbol || symbol is FirRegularClassSymbol } @@ -215,30 +225,48 @@ class FirCallResolver( private fun resolveVariableAccessAndSelectCandidateImpl( qualifiedAccess: T, + isUsedAsReceiver: Boolean, acceptCandidates: (Collection) -> Boolean ): FirStatement { val callee = qualifiedAccess.calleeReference as? FirSimpleNamedReference ?: return qualifiedAccess return components.context.withIncrementedQualifierPartIndex { - qualifiedResolver.initProcessingQualifiedAccess(callee, qualifiedAccess.typeArguments) - @Suppress("NAME_SHADOWING") val qualifiedAccess = qualifiedAccess.transformExplicitReceiver() - qualifiedResolver.replacedQualifier(qualifiedAccess)?.let { resolvedQualifierPart -> - return@withIncrementedQualifierPartIndex resolvedQualifierPart + val nonFatalDiagnosticFromExpression = (qualifiedAccess as? FirPropertyAccessExpression)?.nonFatalDiagnostics + + if (isUsedAsReceiver) { + (qualifiedAccess.explicitReceiver as? FirResolvedQualifier) + ?.continueQualifier( + callee, + qualifiedAccess.source, + qualifiedAccess.typeArguments, + nonFatalDiagnosticFromExpression, + components + ) + ?.let { return it } } var result = collectCandidates(qualifiedAccess, callee.name) if (qualifiedAccess.explicitReceiver == null) { - if (!result.applicability.isSuccess) { - // We should run QualifierResolver if no successful candidates are available - // Otherwise expression (even ambiguous) beat qualifier - qualifiedResolver.tryResolveAsQualifier(qualifiedAccess.source)?.let { resolvedQualifier -> - return@withIncrementedQualifierPartIndex resolvedQualifier - } + // Even if we successfully resolved to some companion/named object, we should re-try with qualifier resolution + // import D.* + // class A { + // object B + // } + // class D { + // object A + // } + // fun main() { + // A // should resolved to D.A + // A.B // should be resolved to A.B + // } + if (!result.applicability.isSuccess || (isUsedAsReceiver && result.candidates.all { it.symbol is FirRegularClassSymbol })) { + components.resolveRootPartOfQualifier( + callee, qualifiedAccess.source, qualifiedAccess.typeArguments, nonFatalDiagnosticFromExpression, + )?.let { return it } } - qualifiedResolver.reset() } var functionCallExpected = false @@ -283,14 +311,14 @@ class FirCallResolver( referencedSymbol is FirClassLikeSymbol<*> -> { return@withIncrementedQualifierPartIndex components.buildResolvedQualifierForClass( referencedSymbol, - nameReference.source, + qualifiedAccess.source, qualifiedAccess.typeArguments, diagnostic, nonFatalDiagnostics = extractNonFatalDiagnostics( nameReference.source, qualifiedAccess.explicitReceiver, referencedSymbol, - (qualifiedAccess as? FirPropertyAccessExpression)?.nonFatalDiagnostics + nonFatalDiagnosticFromExpression, ) ) } @@ -314,32 +342,6 @@ class FirCallResolver( } } - private fun extractNonFatalDiagnostics( - source: FirSourceElement?, - explicitReceiver: FirExpression?, - symbol: FirClassLikeSymbol<*>, - extraNotFatalDiagnostics: List? - ): List { - val prevDiagnostics = (explicitReceiver as? FirResolvedQualifier)?.nonFatalDiagnostics ?: emptyList() - var result: MutableList? = null - - val deprecation = symbol.fir.deprecation?.forUseSite() - if (deprecation != null) { - result = mutableListOf() - result.addAll(prevDiagnostics) - result.add(ConeDeprecated(source, symbol, deprecation)) - } - if (extraNotFatalDiagnostics != null && extraNotFatalDiagnostics.isNotEmpty()) { - if (result == null) { - result = mutableListOf() - result.addAll(prevDiagnostics) - } - result.addAll(extraNotFatalDiagnostics) - } - - return result?.toList() ?: prevDiagnostics - } - fun resolveCallableReference( constraintSystemBuilder: ConstraintSystemBuilder, resolvedCallableReferenceAtom: ResolvedCallableReferenceAtom, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirQualifiedNameResolver.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirQualifiedNameResolver.kt deleted file mode 100644 index 9c6c88f7b88..00000000000 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirQualifiedNameResolver.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.fir - -import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess -import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier -import org.jetbrains.kotlin.fir.expressions.FirStatement -import org.jetbrains.kotlin.fir.expressions.builder.buildResolvedQualifier -import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference -import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents -import org.jetbrains.kotlin.fir.resolve.symbolProvider -import org.jetbrains.kotlin.fir.resolve.transformers.PackageOrClass -import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType -import org.jetbrains.kotlin.fir.resolve.transformers.resolveToPackageOrClass -import org.jetbrains.kotlin.fir.resolve.typeForQualifier -import org.jetbrains.kotlin.fir.types.FirTypeProjection -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name - -const val ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE = "_root_ide_package_" - -class FirQualifiedNameResolver(private val components: BodyResolveComponents) { - private val session = components.session - private var qualifierStack = mutableListOf() - private var qualifierPartsToDrop = 0 - - private class NameWithTypeArguments(val name: Name, val typeArguments: List) - - fun reset() { - qualifierStack.clear() - qualifierPartsToDrop = 0 - } - - - /** - * NB: 0 if current 'qualifiedAccess.safe || callee.name.isSpecial', 1 if current is fine, 2 if potential qualifier - * a.b.c - * ^ here stack will be ['c', 'b'], so possible - * a.b?.c - * ^ here stack will be ['b'], so impossible - * a?.b.c - * ^ here stack will be [], so impossible - */ - fun isPotentialQualifierPartPosition() = qualifierStack.size > 1 - - fun initProcessingQualifiedAccess(callee: FirSimpleNamedReference, typeArguments: List) { - if (callee.name.isSpecial) { - qualifierStack.clear() - } else { - qualifierStack.add(NameWithTypeArguments(callee.name, typeArguments.toList())) - } - } - - fun replacedQualifier(qualifiedAccess: FirQualifiedAccess): FirStatement? = - if (qualifierPartsToDrop > 0) { - qualifierPartsToDrop-- - if (qualifierPartsToDrop == 0) { - reset() - } - qualifiedAccess.explicitReceiver ?: qualifiedAccess - } else { - null - } - - fun tryResolveAsQualifier(source: FirSourceElement?): FirResolvedQualifier? { - if (qualifierStack.isEmpty()) { - return null - } - val symbolProvider = session.symbolProvider - var qualifierParts = qualifierStack.asReversed().map { it.name.asString() } - - val fakeRootIdePrefixIsPresent = qualifierParts.firstOrNull() == ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE - if (fakeRootIdePrefixIsPresent) { - qualifierParts = qualifierParts.drop(1) - } - var resolved: PackageOrClass? - do { - resolved = resolveToPackageOrClass( - symbolProvider, - FqName.fromSegments(qualifierParts) - ) - if (resolved == null) - qualifierParts = qualifierParts.dropLast(1) - } while (resolved == null && qualifierParts.isNotEmpty()) - - if (resolved != null) { - qualifierPartsToDrop = qualifierParts.size - 1 - // we would need to drop fake package too - if (fakeRootIdePrefixIsPresent) { - qualifierPartsToDrop += 1 - } - - return buildResolvedQualifier { - this.source = source?.getWholeQualifierSourceIfPossible(qualifierPartsToDrop) - packageFqName = resolved.packageFqName - relativeClassFqName = resolved.relativeClassFqName - symbol = resolved.classSymbol - typeArguments.addAll(qualifierStack.take(qualifierParts.size).flatMap { it.typeArguments }) - }.apply { - resultType = components.typeForQualifier(this) - } - } - - return null - } -} diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/QualifiedNameResolution.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/QualifiedNameResolution.kt new file mode 100644 index 00000000000..16b4cf8c48f --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/QualifiedNameResolution.kt @@ -0,0 +1,182 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir + +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic +import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier +import org.jetbrains.kotlin.fir.expressions.builder.buildResolvedQualifier +import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference +import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents +import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDeprecated +import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType +import org.jetbrains.kotlin.fir.resolve.typeForQualifier +import org.jetbrains.kotlin.fir.scopes.getSingleClassifier +import org.jetbrains.kotlin.fir.scopes.impl.createCurrentScopeList +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.types.FirTypeProjection +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +const val ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE = "_root_ide_package_" + +fun BodyResolveComponents.resolveRootPartOfQualifier( + namedReference: FirSimpleNamedReference, + source: FirSourceElement?, + typeArguments: List, + nonFatalDiagnosticsFromExpression: List?, +): FirResolvedQualifier? { + val name = namedReference.name + if (name.asString() == ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE) { + return buildResolvedQualifier { + this.source = source + packageFqName = FqName.ROOT + this.nonFatalDiagnostics.addAll(nonFatalDiagnosticsFromExpression.orEmpty()) + }.apply { + resultType = typeForQualifier(this) + } + } + + for (scope in createCurrentScopeList()) { + scope.getSingleClassifier(name)?.let { + if (it is FirRegularClassSymbol) { + val classId = it.classId + return buildResolvedQualifier { + this.source = source + packageFqName = classId.packageFqName + relativeClassFqName = classId.relativeClassName + symbol = it + this.typeArguments.addAll(typeArguments) + this.nonFatalDiagnostics.addAll( + extractNonFatalDiagnostics( + source, + explicitReceiver = null, + it, + extraNotFatalDiagnostics = nonFatalDiagnosticsFromExpression + ) + ) + }.apply { + resultType = typeForQualifier(this) + } + } + } + } + + return FqName.ROOT.continueQualifierInPackage(name, typeArguments, nonFatalDiagnosticsFromExpression, this, source) +} + +fun FirResolvedQualifier.continueQualifier( + namedReference: FirSimpleNamedReference, + source: FirSourceElement?, + typeArguments: List, + nonFatalDiagnosticsFromExpression: List?, + components: BodyResolveComponents, +): FirResolvedQualifier? { + val name = namedReference.name + symbol?.let { outerClassSymbol -> + val firClass = outerClassSymbol.fir + if (firClass !is FirClass) return null + return firClass.scopeProvider.getNestedClassifierScope(firClass, components.session, components.scopeSession) + ?.getSingleClassifier(name) + ?.takeIf { it is FirClassLikeSymbol<*> } + ?.let { nestedClassSymbol -> + buildResolvedQualifier { + this.source = source + packageFqName = this@continueQualifier.packageFqName + relativeClassFqName = this@continueQualifier.relativeClassFqName?.child(name) + symbol = nestedClassSymbol as FirClassLikeSymbol<*> + + val outerTypeArguments = this.typeArguments.toList() + this.typeArguments.clear() + this.typeArguments.addAll(typeArguments) + this.typeArguments.addAll(outerTypeArguments) + this.nonFatalDiagnostics.addAll(nonFatalDiagnosticsFromExpression.orEmpty()) + this.nonFatalDiagnostics.addAll( + extractNonFatalDiagnostics( + source, + explicitReceiver = null, + nestedClassSymbol, + extraNotFatalDiagnostics = this@continueQualifier.nonFatalDiagnostics + ) + ) + }.apply { + resultType = components.typeForQualifier(this) + } + } + } + + return packageFqName.continueQualifierInPackage(name, typeArguments, nonFatalDiagnosticsFromExpression, components, source) +} + +private fun FqName.continueQualifierInPackage( + name: Name, + typeArguments: List, + nonFatalDiagnosticsFromExpression: List?, + components: BodyResolveComponents, + source: FirSourceElement? +): FirResolvedQualifier? { + val childFqName = this.child(name) + if (components.symbolProvider.getPackage(childFqName) != null) { + return buildResolvedQualifier { + this.source = source + packageFqName = childFqName + this.typeArguments.addAll(typeArguments) + this.nonFatalDiagnostics.addAll(nonFatalDiagnosticsFromExpression.orEmpty()) + }.apply { + resultType = components.typeForQualifier(this) + } + } + + val classId = ClassId.topLevel(childFqName) + val symbol = components.symbolProvider.getClassLikeSymbolByClassId(classId) ?: return null + + return buildResolvedQualifier { + this.source = source + packageFqName = this@continueQualifierInPackage + relativeClassFqName = classId.relativeClassName + this.symbol = symbol + this.typeArguments.addAll(typeArguments) + this.nonFatalDiagnostics.addAll( + extractNonFatalDiagnostics( + source, + explicitReceiver = null, + symbol, + extraNotFatalDiagnostics = nonFatalDiagnosticsFromExpression + ) + ) + }.apply { + resultType = components.typeForQualifier(this) + } +} + +internal fun extractNonFatalDiagnostics( + source: FirSourceElement?, + explicitReceiver: FirExpression?, + symbol: FirClassLikeSymbol<*>, + extraNotFatalDiagnostics: List? +): List { + val prevDiagnostics = (explicitReceiver as? FirResolvedQualifier)?.nonFatalDiagnostics ?: emptyList() + var result: MutableList? = null + + val deprecation = symbol.fir.deprecation?.forUseSite() + if (deprecation != null) { + result = mutableListOf() + result.addAll(prevDiagnostics) + result.add(ConeDeprecated(source, symbol, deprecation)) + } + if (extraNotFatalDiagnostics != null && extraNotFatalDiagnostics.isNotEmpty()) { + if (result == null) { + result = mutableListOf() + result.addAll(prevDiagnostics) + } + result.addAll(extraNotFatalDiagnostics) + } + + return result?.toList() ?: prevDiagnostics +} diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateFactory.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateFactory.kt index 0e008d9c4e7..964f83cf24e 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateFactory.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateFactory.kt @@ -75,7 +75,6 @@ class CandidateFactory private constructor( result.addDiagnostic(Unsupported("References to variables aren't supported yet", callSite.calleeReference.source)) } } else if (objectsByName && - context.bodyResolveContext.qualifierPartIndexFromEnd == 0 && symbol is FirRegularClassSymbol && symbol.classKind != ClassKind.OBJECT && symbol.companionObjectSymbol == null 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 0e4ed44b27e..4a2570107f7 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 @@ -16,10 +16,7 @@ import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProviderInternals import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor -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.name.* @ThreadSafeMutableState class FirProviderImpl(val session: FirSession, val kotlinScopeProvider: FirKotlinScopeProvider) : FirProvider() { @@ -75,7 +72,7 @@ class FirProviderImpl(val session: FirSession, val kotlinScopeProvider: FirKotli } override fun getPackage(fqName: FqName): FqName? { - if (getFirFilesByPackage(fqName).isNotEmpty()) return fqName + if (fqName in state.allSubPackages) return fqName return null } } @@ -100,6 +97,7 @@ class FirProviderImpl(val session: FirSession, val kotlinScopeProvider: FirKotli private fun recordFile(file: FirFile, state: State) { val packageName = file.packageFqName state.fileMap.merge(packageName, listOf(file)) { a, b -> a + b } + generateSequence(packageName) { it.parentOrNull() }.forEach(state.allSubPackages::add) file.acceptChildren(FirRecorder, FirRecorderData(state, file, session.nameConflictsTracker)) } @@ -179,6 +177,7 @@ class FirProviderImpl(val session: FirSession, val kotlinScopeProvider: FirKotli private class State { val fileMap = mutableMapOf>() + val allSubPackages = mutableSetOf() val classifierMap = mutableMapOf() val classifierContainerFileMap = mutableMapOf() val classesInPackage = mutableMapOf>() @@ -189,6 +188,7 @@ class FirProviderImpl(val session: FirSession, val kotlinScopeProvider: FirKotli fun setFrom(other: State) { fileMap.clear() + allSubPackages.clear() classifierMap.clear() classifierContainerFileMap.clear() functionMap.clear() @@ -197,6 +197,7 @@ class FirProviderImpl(val session: FirSession, val kotlinScopeProvider: FirKotli callableContainerMap.clear() fileMap.putAll(other.fileMap) + allSubPackages.addAll(other.allSubPackages) classifierMap.putAll(other.classifierMap) classifierContainerFileMap.putAll(other.classifierContainerFileMap) functionMap.putAll(other.functionMap) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformer.kt index c7f831a4707..acc5256d968 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformer.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.fir.resolve.transformers.body.resolve import org.jetbrains.kotlin.fir.FirCallResolver -import org.jetbrains.kotlin.fir.FirQualifiedNameResolver import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.PrivateForInline import org.jetbrains.kotlin.fir.declarations.FirDeclaration @@ -101,10 +100,8 @@ abstract class FirAbstractBodyResolveTransformer(phase: FirResolvePhase) : FirAb override val resolutionStageRunner: ResolutionStageRunner = ResolutionStageRunner() - private val qualifiedResolver: FirQualifiedNameResolver = FirQualifiedNameResolver(this) override val callResolver: FirCallResolver = FirCallResolver( this, - qualifiedResolver ) val typeResolverTransformer = FirSpecificTypeResolverTransformer( session diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt index 71e0bb71b53..857f1d53ab4 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef import org.jetbrains.kotlin.fir.visitors.FirDefaultTransformer +import org.jetbrains.kotlin.fir.visitors.FirTransformer import org.jetbrains.kotlin.fir.visitors.TransformData import org.jetbrains.kotlin.fir.visitors.transformSingle import org.jetbrains.kotlin.name.Name @@ -77,6 +78,12 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform override fun transformQualifiedAccessExpression( qualifiedAccessExpression: FirQualifiedAccessExpression, data: ResolutionMode, + ): FirStatement = transformQualifiedAccessExpression(qualifiedAccessExpression, data, isUsedAsReceiver = false) + + fun transformQualifiedAccessExpression( + qualifiedAccessExpression: FirQualifiedAccessExpression, + data: ResolutionMode, + isUsedAsReceiver: Boolean, ): FirStatement { qualifiedAccessExpression.transformAnnotations(this, data) qualifiedAccessExpression.transformTypeArguments(transformer, ResolutionMode.ContextIndependent) @@ -130,7 +137,7 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform qualifiedAccessExpression } else -> { - val transformedCallee = resolveQualifiedAccessAndSelectCandidate(qualifiedAccessExpression) + val transformedCallee = resolveQualifiedAccessAndSelectCandidate(qualifiedAccessExpression, isUsedAsReceiver) // NB: here we can get raw expression because of dropped qualifiers (see transform callee), // so candidate existence must be checked before calling completion if (transformedCallee is FirQualifiedAccessExpression && transformedCallee.candidate() != null) { @@ -163,8 +170,11 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform return transformQualifiedAccessExpression(propertyAccessExpression, data) } - protected open fun resolveQualifiedAccessAndSelectCandidate(qualifiedAccessExpression: FirQualifiedAccessExpression): FirStatement { - return callResolver.resolveVariableAccessAndSelectCandidate(qualifiedAccessExpression) + protected open fun resolveQualifiedAccessAndSelectCandidate( + qualifiedAccessExpression: FirQualifiedAccessExpression, + isUsedAsReceiver: Boolean, + ): FirStatement { + return callResolver.resolveVariableAccessAndSelectCandidate(qualifiedAccessExpression, isUsedAsReceiver) } fun transformSuperReceiver( @@ -752,7 +762,7 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform ): FirStatement { // val resolvedAssignment = transformCallee(variableAssignment) variableAssignment.transformAnnotations(transformer, ResolutionMode.ContextIndependent) - val resolvedAssignment = callResolver.resolveVariableAccessAndSelectCandidate(variableAssignment) + val resolvedAssignment = callResolver.resolveVariableAccessAndSelectCandidate(variableAssignment, isUsedAsReceiver = false) val result = if (resolvedAssignment is FirVariableAssignment) { val completeAssignment = callCompleter.completeCall(resolvedAssignment, noExpectedType).result // TODO: check completeAssignment.transformRValue( @@ -778,7 +788,14 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform callableReferenceAccess.transformAnnotations(transformer, data) val explicitReceiver = callableReferenceAccess.explicitReceiver val transformedLHS = context.withIncrementedQualifierPartIndex { - explicitReceiver?.transformSingle(this, ResolutionMode.ContextIndependent)?.apply { + + if (explicitReceiver is FirPropertyAccessExpression) { + transformQualifiedAccessExpression( + explicitReceiver, ResolutionMode.ContextIndependent, isUsedAsReceiver = true + ) as FirExpression + } else { + explicitReceiver?.transformSingle(this, ResolutionMode.ContextIndependent) + }.apply { if (this is FirResolvedQualifier && callableReferenceAccess.hasQuestionMarkAtLHS) { replaceIsNullableLHSForCallableReference(true) } @@ -811,7 +828,19 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform } val transformedGetClassCall = context.withIncrementedQualifierPartIndex { - transformExpression(getClassCall, dataWithExpectedType) as FirGetClassCall + val argument = getClassCall.argument + val replacedArgument: FirExpression = + if (argument is FirPropertyAccessExpression) + transformQualifiedAccessExpression(argument, dataWithExpectedType, isUsedAsReceiver = true) as FirExpression + else + argument.transform(this, dataWithExpectedType) + + getClassCall.argumentList.transformArguments(object : FirTransformer() { + @Suppress("UNCHECKED_CAST") + override fun transformElement(element: E, data: Nothing?): E = replacedArgument as E + }, null) + + getClassCall } val typeOfExpression = when (val lhs = transformedGetClassCall.argument) { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/plugin/FirAnnotationArgumentsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/plugin/FirAnnotationArgumentsResolveTransformer.kt index fce998d1565..d8d0f1d633c 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/plugin/FirAnnotationArgumentsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/plugin/FirAnnotationArgumentsResolveTransformer.kt @@ -156,8 +156,11 @@ private class FirExpressionsResolveTransformerForSpecificAnnotations( return calleeReference !is FirErrorNamedReference } - override fun resolveQualifiedAccessAndSelectCandidate(qualifiedAccessExpression: FirQualifiedAccessExpression): FirStatement { - return callResolver.resolveOnlyEnumOrQualifierAccessAndSelectCandidate(qualifiedAccessExpression) + override fun resolveQualifiedAccessAndSelectCandidate( + qualifiedAccessExpression: FirQualifiedAccessExpression, + isUsedAsReceiver: Boolean, + ): FirStatement { + return callResolver.resolveOnlyEnumOrQualifierAccessAndSelectCandidate(qualifiedAccessExpression, isUsedAsReceiver) } override fun transformFunctionCall(functionCall: FirFunctionCall, data: ResolutionMode): FirStatement { diff --git a/compiler/testData/diagnostics/tests/PackageInExpressionPosition.fir.kt b/compiler/testData/diagnostics/tests/PackageInExpressionPosition.fir.kt index 9e55bb3f6a0..7e893e84337 100644 --- a/compiler/testData/diagnostics/tests/PackageInExpressionPosition.fir.kt +++ b/compiler/testData/diagnostics/tests/PackageInExpressionPosition.fir.kt @@ -6,7 +6,7 @@ val s = java val ss = System val sss = X val x = "${System}" -val xs = java.lang +val xs = java.lang val xss = java.lang.System val xsss = foo.X val xssss = foo diff --git a/compiler/testData/diagnostics/tests/qualifiedExpression/variableVsImportedClassifier.kt b/compiler/testData/diagnostics/tests/qualifiedExpression/variableVsImportedClassifier.kt new file mode 100644 index 00000000000..aa648b606a4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/qualifiedExpression/variableVsImportedClassifier.kt @@ -0,0 +1,31 @@ +// FIR_IDENTICAL +// SKIP_TXT +// FILE: test/W.java +package test; + +public class W { + public void foo() {} + public static void bar() {} +} + +// FILE: main.kt +package main1 +import test.W + +val W: W = W() + +fun main() { + W.foo() + W.bar() + W().foo() +} + +// FILE: main2.kt +package main2 +import test.W + +fun main() { + W.foo() + W.bar() + W().foo() +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index b997e398093..976aac48fa1 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -22647,6 +22647,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/qualifiedExpression/TypeWithError.kt"); } + @Test + @TestMetadata("variableVsImportedClassifier.kt") + public void testVariableVsImportedClassifier() throws Exception { + runTest("compiler/testData/diagnostics/tests/qualifiedExpression/variableVsImportedClassifier.kt"); + } + @Test @TestMetadata("visibleClassVsQualifiedClass.kt") public void testVisibleClassVsQualifiedClass() throws Exception { diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/getOrBuildFir/qualifiedExpressions/firstPartOfQualifiedCallWithNestedClasses.txt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/getOrBuildFir/qualifiedExpressions/firstPartOfQualifiedCallWithNestedClasses.txt index ee485601e78..51e244efac6 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/getOrBuildFir/qualifiedExpressions/firstPartOfQualifiedCallWithNestedClasses.txt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/getOrBuildFir/qualifiedExpressions/firstPartOfQualifiedCallWithNestedClasses.txt @@ -1,5 +1,5 @@ KT element: KtNameReferenceExpression -FIR element: FirFunctionCallImpl +FIR element: FirResolvedQualifierImpl FIR element rendered: -Q|A.B|.R|/A.B.C.C|() +Q|A.B| diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/getOrBuildFir/qualifiedExpressions/qualifiedPartOfQualifiedCallWithNestedClasses.txt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/getOrBuildFir/qualifiedExpressions/qualifiedPartOfQualifiedCallWithNestedClasses.txt index aed76db4cbf..cabc5b10f1e 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/getOrBuildFir/qualifiedExpressions/qualifiedPartOfQualifiedCallWithNestedClasses.txt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/getOrBuildFir/qualifiedExpressions/qualifiedPartOfQualifiedCallWithNestedClasses.txt @@ -1,5 +1,5 @@ KT element: KtDotQualifiedExpression -FIR element: FirFunctionCallImpl +FIR element: FirResolvedQualifierImpl FIR element rendered: -Q|A.B|.R|/A.B.C.C|() +Q|A.B| diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index c240a6991c3..7c323d6de2c 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -22641,6 +22641,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/qualifiedExpression/TypeWithError.kt"); } + @Test + @TestMetadata("variableVsImportedClassifier.kt") + public void testVariableVsImportedClassifier() throws Exception { + runTest("compiler/testData/diagnostics/tests/qualifiedExpression/variableVsImportedClassifier.kt"); + } + @Test @TestMetadata("visibleClassVsQualifiedClass.kt") public void testVisibleClassVsQualifiedClass() throws Exception { diff --git a/idea/idea-frontend-fir/testData/referenceResolve/ClassObjectClassLiteralReferenceWithField.txt b/idea/idea-frontend-fir/testData/referenceResolve/ClassObjectClassLiteralReferenceWithField.txt index 130d40a039c..bac3d45f118 100644 --- a/idea/idea-frontend-fir/testData/referenceResolve/ClassObjectClassLiteralReferenceWithField.txt +++ b/idea/idea-frontend-fir/testData/referenceResolve/ClassObjectClassLiteralReferenceWithField.txt @@ -1,2 +1,2 @@ Resolved to: -0: (in t.A.Named) val i: kotlin.Int +0: (in t) class A