FIR/Analysis API: Get parameter name from function type notation or

`@ParameterName` annotation, which is also now added during type
resolution.
This commit is contained in:
Mark Punzalan
2021-10-12 07:51:30 +00:00
committed by Dmitriy Novozhilov
parent afb68d15d6
commit 167dc81d3b
27 changed files with 282 additions and 86 deletions
@@ -6,29 +6,42 @@
package org.jetbrains.kotlin.analysis.api.fir.symbols
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.renderWithType
import org.jetbrains.kotlin.fir.types.arrayElementType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.analysis.api.fir.findPsi
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirModuleResolveState
import org.jetbrains.kotlin.analysis.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.analysis.api.fir.findPsi
import org.jetbrains.kotlin.analysis.api.fir.symbols.annotations.KtFirAnnotationCall
import org.jetbrains.kotlin.analysis.api.fir.symbols.annotations.containsAnnotation
import org.jetbrains.kotlin.analysis.api.fir.symbols.annotations.getAnnotationClassIds
import org.jetbrains.kotlin.analysis.api.fir.symbols.annotations.toAnnotationsList
import org.jetbrains.kotlin.analysis.api.fir.utils.cached
import org.jetbrains.kotlin.analysis.api.fir.utils.firRef
import org.jetbrains.kotlin.analysis.api.fir.utils.weakRef
import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtAnnotationCall
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtTypeAndAnnotations
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtPsiBasedSymbolPointer
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.api.tokens.ValidityToken
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirModuleResolveState
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirOfType
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.withFirDeclaration
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.declarations.getAnnotationsByClassId
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.unwrapArgument
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.renderWithType
import org.jetbrains.kotlin.fir.types.arrayElementType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.customAnnotations
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class KtFirValueParameterSymbol(
fir: FirValueParameter,
@@ -39,7 +52,45 @@ internal class KtFirValueParameterSymbol(
override val firRef = firRef(fir, resolveState)
override val psi: PsiElement? by firRef.withFirAndCache { fir -> fir.findPsi(fir.moduleData.session) }
override val name: Name get() = firRef.withFir { it.name }
override val name: Name by cached {
firRef.withFir { fir ->
val defaultName = fir.name
if (fir.psi != null) return@withFir defaultName
// The case where PSI is null is when calling `invoke()` on a variable with functional type, e.g. `x(1)` below:
//
// fun foo(x: (item: Int) -> Unit) { x(1) }
// fun bar(x: Function1<@ParameterName("item") Int, Unit>) { x(1) }
//
// The function being called is `invoke(p1: Int)` on `Function1<Int, Unit>` which is from the stdlib, and therefore no source
// PSI for the function or its parameters. In that case, we use the `@ParameterName` annotation on the parameter type if present
// and fall back to the parameter names from the `invoke()` function (`p1`, `p2`, etc.).
//
// Note: During type resolution, `@ParameterName` type annotations are added based on the names (which are optional) in the
// function type notation. Therefore the `x` parameter in both example functions above have the same type and type annotations.
fir.withFirDeclaration(resolveState, FirResolvePhase.TYPES) {
val parameterNameAnnotation =
fir.returnTypeRef.coneType.attributes.customAnnotations
.getAnnotationsByClassId(StandardNames.FqNames.parameterNameClassId)
.singleOrNull()?.safeAs<FirAnnotation>() ?: return@withFirDeclaration defaultName
// The parent KtDeclaration is where the variable with functional type and `@ParameterName` annotation is declared.
val parentKtDeclaration =
parameterNameAnnotation.psi?.getNonStrictParentOfType<KtDeclaration>() ?: return@withFirDeclaration defaultName
val parentFirDeclaration = parentKtDeclaration.getOrBuildFirOfType<FirDeclaration>(resolveState)
// Resolve to ARGUMENTS_OF_ANNOTATIONS phase to get `name` argument from mapping.
val parameterNameFromAnnotation =
parentFirDeclaration.withFirDeclaration(resolveState, FirResolvePhase.ARGUMENTS_OF_ANNOTATIONS) {
val nameArgument =
parameterNameAnnotation.argumentMapping.mapping[StandardClassIds.Annotations.ParameterNames.parameterNameName]
nameArgument?.unwrapArgument()?.safeAs<FirConstExpression<*>>()?.value as? String
}
parameterNameFromAnnotation?.let { Name.identifier(it) } ?: defaultName
}
}
}
override val isVararg: Boolean get() = firRef.withFir { it.isVararg }
override val annotatedType: KtTypeAndAnnotations by firRef.withFirAndCache(FirResolvePhase.TYPES) { fir ->
if (fir.isVararg) {
@@ -91,6 +91,7 @@ private fun KtCall.stringRepresentation(): String {
append(": ${annotatedType.type.render()}")
}
is KtValueParameterSymbol -> "${if (isVararg) "vararg " else ""}$name: ${annotatedType.type.render()}"
is KtVariableSymbol -> "${if (isVal) "val" else "var"} $name: ${annotatedType.type.render()}"
is KtSuccessCallTarget -> symbol.stringValue()
is KtErrorCallTarget -> "ERR<${this.diagnostic.defaultMessage}, [${candidates.joinToString { it.stringValue() }}]>"
is Boolean -> toString()
@@ -402,6 +402,42 @@ public class ResolveCallTestGenerated extends AbstractResolveCallTest {
runTest("analysis/analysis-api/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt");
}
@Test
@TestMetadata("variableAsFunctionWithParameterName.kt")
public void testVariableAsFunctionWithParameterName() throws Exception {
runTest("analysis/analysis-api/testData/analysisSession/resolveCall/variableAsFunctionWithParameterName.kt");
}
@Test
@TestMetadata("variableAsFunctionWithParameterNameAnnotation.kt")
public void testVariableAsFunctionWithParameterNameAnnotation() throws Exception {
runTest("analysis/analysis-api/testData/analysisSession/resolveCall/variableAsFunctionWithParameterNameAnnotation.kt");
}
@Test
@TestMetadata("variableAsFunctionWithParameterNameAnnotationConflict.kt")
public void testVariableAsFunctionWithParameterNameAnnotationConflict() throws Exception {
runTest("analysis/analysis-api/testData/analysisSession/resolveCall/variableAsFunctionWithParameterNameAnnotationConflict.kt");
}
@Test
@TestMetadata("variableAsFunctionWithParameterNameGeneric.kt")
public void testVariableAsFunctionWithParameterNameGeneric() throws Exception {
runTest("analysis/analysis-api/testData/analysisSession/resolveCall/variableAsFunctionWithParameterNameGeneric.kt");
}
@Test
@TestMetadata("variableAsFunctionWithParameterNameInNonFunctionType.kt")
public void testVariableAsFunctionWithParameterNameInNonFunctionType() throws Exception {
runTest("analysis/analysis-api/testData/analysisSession/resolveCall/variableAsFunctionWithParameterNameInNonFunctionType.kt");
}
@Test
@TestMetadata("variableAsFunctionWithParameterNameMixed.kt")
public void testVariableAsFunctionWithParameterNameMixed() throws Exception {
runTest("analysis/analysis-api/testData/analysisSession/resolveCall/variableAsFunctionWithParameterNameMixed.kt");
}
@Test
@TestMetadata("variableWithExtensionInvoke.kt")
public void testVariableWithExtensionInvoke() throws Exception {