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:
committed by
Dmitriy Novozhilov
parent
afb68d15d6
commit
167dc81d3b
+60
-9
@@ -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) {
|
||||
|
||||
+1
@@ -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()
|
||||
|
||||
+36
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user