diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/FirReferenceResolveHelper.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/FirReferenceResolveHelper.kt index 4b73a5c4893..d7e9765ae13 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/FirReferenceResolveHelper.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/FirReferenceResolveHelper.kt @@ -12,9 +12,6 @@ import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirPackageSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFir import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirSafe -import org.jetbrains.kotlin.fir.FirPackageDirective -import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.builder.buildImport import org.jetbrains.kotlin.fir.declarations.synthetic.FirSyntheticProperty @@ -23,7 +20,6 @@ import org.jetbrains.kotlin.fir.declarations.utils.isCompanion import org.jetbrains.kotlin.fir.declarations.utils.isLocal import org.jetbrains.kotlin.fir.declarations.utils.isStatic import org.jetbrains.kotlin.fir.expressions.* -import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.references.* import org.jetbrains.kotlin.fir.resolve.ScopeSession import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnmatchedTypeArgumentsError @@ -34,6 +30,13 @@ import org.jetbrains.kotlin.fir.scopes.processClassifiersByName import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.analysis.api.fir.getCandidateSymbols +import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession +import org.jetbrains.kotlin.analysis.api.fir.KtSymbolByFirBuilder +import org.jetbrains.kotlin.analysis.api.fir.buildSymbol +import org.jetbrains.kotlin.fir.* +import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.lexer.KtTokens @@ -172,6 +175,25 @@ internal object FirReferenceResolveHelper { return false } + internal fun adjustResolutionExpression(expression: KtElement): KtElement { + // If we are at a super-type constructor call, adjust the resolution expression so that we + // get the constructor instead of the class. + // + // For the example: + // + // class A { + // constructor() + // } + // class B: A() + // + // We want to resolve to the secondary constructor in A. Therefore, we check that the caret is at a supertype + // call entry and if so we resolve the constructor callee expression. + val userType = expression.parent as? KtUserType ?: return expression + val typeReference = userType.parent as? KtTypeReference ?: return expression + val constructorCalleeExpression = typeReference.parent as? KtConstructorCalleeExpression ?: return expression + return if (constructorCalleeExpression.parent is KtSuperTypeCallEntry) constructorCalleeExpression else expression + } + internal fun resolveSimpleNameReference( ref: KtFirSimpleNameReference, @@ -180,7 +202,8 @@ internal object FirReferenceResolveHelper { val expression = ref.expression if (expression.isSyntheticOperatorReference()) return emptyList() val symbolBuilder = analysisSession.firSymbolBuilder - val fir = expression.getOrBuildFir(analysisSession.firResolveState) + val adjustedResolutionExpression = adjustResolutionExpression(expression) + val fir = adjustedResolutionExpression.getOrBuildFir(analysisSession.firResolveState) val session = analysisSession.firResolveState.rootModuleSession return when (fir) { is FirResolvedTypeRef -> getSymbolsForResolvedTypeRef(fir, expression, session, symbolBuilder) @@ -198,12 +221,41 @@ internal object FirReferenceResolveHelper { is FirErrorNamedReference -> getSymbolsByErrorNamedReference(fir, symbolBuilder) is FirVariableAssignment -> getSymbolsByVariableAssignment(fir, session, symbolBuilder) is FirResolvedNamedReference -> getSymbolByResolvedNameReference(fir, expression, analysisSession, session, symbolBuilder) + is FirDelegatedConstructorCall -> + getSymbolByDelegatedConstructorCall(expression, adjustedResolutionExpression, fir, session, symbolBuilder) is FirResolvable -> getSymbolsByResolvable(fir, expression, session, symbolBuilder) is FirNamedArgumentExpression -> getSymbolsByNameArgumentExpression(expression, analysisSession, symbolBuilder) else -> handleUnknownFirElement(expression, analysisSession, session, symbolBuilder) } } + private fun getSymbolByDelegatedConstructorCall( + expression: KtSimpleNameExpression, + adjustedResolutionExpression: KtElement, + fir: FirDelegatedConstructorCall, + session: FirSession, + symbolBuilder: KtSymbolByFirBuilder + ): Collection { + if (expression != adjustedResolutionExpression) { + // Type alias detection. + // + // If we adjusted resolution to get a constructor instead of a class, we need to undo that + // if the class is defined as a type alias. We can detect that situation when the constructed type + // is different from the return type of the constructor. + // + // TODO: This seems a little indirect. Is there a better way to do this? For FE1.0 there is + // a special `TypeAliasConstructorDescriptor` for this case. For FIR there is + // FirConstructor.originalConstructorIfTypeAlias but that doesn't seem to help here as it + // is null for the constructors we get. + val constructedType = fir.constructedTypeRef.coneType + val constructorReturnType = (fir.calleeReference.resolvedSymbol as? FirConstructorSymbol)?.resolvedReturnTypeRef?.type + if (constructedType.classId != constructorReturnType?.classId) { + return getSymbolsForResolvedTypeRef(fir.constructedTypeRef as FirResolvedTypeRef, expression, session, symbolBuilder) + } + } + return getSymbolsByResolvable(fir, expression, session, symbolBuilder) + } + private fun getSymbolsForPackageDirective( expression: KtSimpleNameExpression, symbolBuilder: KtSymbolByFirBuilder diff --git a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirLibrarySourceReferenceResolveTestGenerated.java b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirLibrarySourceReferenceResolveTestGenerated.java index 25e29dca702..4df383eb9c9 100644 --- a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirLibrarySourceReferenceResolveTestGenerated.java +++ b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirLibrarySourceReferenceResolveTestGenerated.java @@ -394,6 +394,18 @@ public class FirLibrarySourceReferenceResolveTestGenerated extends AbstractRefer runTest("analysis/analysis-api/testData/referenceResolve/SeveralOverrides.kt"); } + @Test + @TestMetadata("SuperTypePrimaryConstructor.kt") + public void testSuperTypePrimaryConstructor() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.kt"); + } + + @Test + @TestMetadata("SuperTypeSecondaryConstructor.kt") + public void testSuperTypeSecondaryConstructor() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.kt"); + } + @Test @TestMetadata("superWithLabel_caretAtLabel.kt") public void testSuperWithLabel_caretAtLabel() throws Exception { diff --git a/analysis/analysis-api/testData/referenceResolve/ResolveClass.kt b/analysis/analysis-api/testData/referenceResolve/ResolveClass.kt index 8889c2175db..679a6e0c841 100644 --- a/analysis/analysis-api/testData/referenceResolve/ResolveClass.kt +++ b/analysis/analysis-api/testData/referenceResolve/ResolveClass.kt @@ -1,4 +1,14 @@ +// The frontend resolves to the Test class. +// +// The analysis API adjusts the resolution to be the constructor as that is what the IDE expects. +// +// In this particular case, there is no PSI element for the constructor. Therefore PSI +// resolution `KtReference.resolve()` returns the KtClass for Test whereas symbol resolution +// `KtReference.resolveToSymbol` returns the KtConstructorSymbol for the Test constructor. +// +// This test is testing symbol resolution, so the resolution result is +// the constructor in Test. + open class Test class SomeTest : Test() - diff --git a/analysis/analysis-api/testData/referenceResolve/ResolveClass.txt b/analysis/analysis-api/testData/referenceResolve/ResolveClass.txt index 99abdb25c31..5c22742ff8e 100644 --- a/analysis/analysis-api/testData/referenceResolve/ResolveClass.txt +++ b/analysis/analysis-api/testData/referenceResolve/ResolveClass.txt @@ -1,2 +1,2 @@ Resolved to: -0: (in ROOT) open class Test \ No newline at end of file +0: (in Test) constructor() diff --git a/analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.kt b/analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.kt new file mode 100644 index 00000000000..b5df1e4ecfa --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.kt @@ -0,0 +1,3 @@ +class A(i: Int) + +class B: A(42) diff --git a/analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.txt b/analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.txt new file mode 100644 index 00000000000..ea96d745b33 --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.txt @@ -0,0 +1,2 @@ +Resolved to: +0: (in A) constructor(i: kotlin.Int) diff --git a/analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.kt b/analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.kt new file mode 100644 index 00000000000..d5a1592d4e7 --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.kt @@ -0,0 +1,5 @@ +class A { + constructor() +} + +class B: A() diff --git a/analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.txt b/analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.txt new file mode 100644 index 00000000000..9c4691675b4 --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.txt @@ -0,0 +1,2 @@ +Resolved to: +0: (in A) constructor()