From d13e117a58f5f80d0df6c5fb57d69d977c4311e0 Mon Sep 17 00:00:00 2001 From: Mads Ager Date: Fri, 14 Jan 2022 13:25:15 +0100 Subject: [PATCH] Change FIR analysis API resolution for super type calls. The IDE expects the super type calls to resolve to the constructor invoked and not the class. The frontend itself resolves to the class, so an extra layer is needed to adjust that to the constructor. --- .../references/FirReferenceResolveHelper.kt | 62 +++++++++++++++++-- ...rySourceReferenceResolveTestGenerated.java | 12 ++++ .../testData/referenceResolve/ResolveClass.kt | 12 +++- .../referenceResolve/ResolveClass.txt | 2 +- .../SuperTypePrimaryConstructor.kt | 3 + .../SuperTypePrimaryConstructor.txt | 2 + .../SuperTypeSecondaryConstructor.kt | 5 ++ .../SuperTypeSecondaryConstructor.txt | 2 + 8 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.kt create mode 100644 analysis/analysis-api/testData/referenceResolve/SuperTypePrimaryConstructor.txt create mode 100644 analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.kt create mode 100644 analysis/analysis-api/testData/referenceResolve/SuperTypeSecondaryConstructor.txt 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()