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 {
|
||||
|
||||
@@ -67,12 +67,13 @@ public sealed class KtDeclaredFunctionCall : KtCall() {
|
||||
* fun Int.invoke() {}
|
||||
*/
|
||||
public class KtVariableWithInvokeFunctionCall(
|
||||
public val target: KtVariableLikeSymbol,
|
||||
private val _target: KtVariableLikeSymbol,
|
||||
private val _argumentMapping: LinkedHashMap<KtExpression, KtValueParameterSymbol>,
|
||||
private val _targetFunction: KtCallTarget,
|
||||
private val _substitutor: KtSubstitutor,
|
||||
override val token: ValidityToken
|
||||
) : KtDeclaredFunctionCall() {
|
||||
public val target: KtVariableLikeSymbol get() = withValidityAssertion { _target }
|
||||
override val argumentMapping: LinkedHashMap<KtExpression, KtValueParameterSymbol>
|
||||
get() = withValidityAssertion { _argumentMapping }
|
||||
override val targetFunction: KtCallTarget
|
||||
|
||||
+11
@@ -138,4 +138,15 @@ public abstract class KtValueParameterSymbol : KtVariableLikeSymbol(), KtSymbolW
|
||||
public abstract val isVararg: Boolean
|
||||
|
||||
abstract override fun createPointer(): KtSymbolPointer<KtValueParameterSymbol>
|
||||
|
||||
/**
|
||||
* The name of the value parameter. For a parameter of `FunctionN.invoke()` functions, the name is taken from the function type
|
||||
* notation, if a name is present. For example:
|
||||
* ```
|
||||
* fun foo(x: (item: Int, String) -> Unit) =
|
||||
* x(1, "") // or `x.invoke(1, "")`
|
||||
* ```
|
||||
* The names of the value parameters for `invoke()` are "item" and "p2" (its default parameter name).
|
||||
*/
|
||||
abstract override val name: Name
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
fun call(x: (a: Int, b: String) -> Unit) {
|
||||
<expr>x(1, "")</expr>
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
KtFunctionalTypeVariableCall:
|
||||
target = x: kotlin.Function2<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, @R|kotlin.ParameterName|(name = String(b)) kotlin.String, kotlin.Unit>
|
||||
argumentMapping = { 1 -> (a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int), "" -> (b: @R|kotlin.ParameterName|(name = String(b)) kotlin.String) }
|
||||
targetFunction = kotlin/Function2.invoke(<dispatch receiver>: kotlin.Function2<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, @R|kotlin.ParameterName|(name = String(b)) kotlin.String, kotlin.Unit>, a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int, b: @R|kotlin.ParameterName|(name = String(b)) kotlin.String): kotlin.Unit
|
||||
substitutor = <empty substitutor>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun call(x: Function2<@ParameterName("a") Int, @ParameterName("b") String, Unit>) {
|
||||
<expr>x(1, "")</expr>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
KtFunctionalTypeVariableCall:
|
||||
target = x: kotlin.Function2<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, @R|kotlin.ParameterName|(name = String(b)) kotlin.String, kotlin.Unit>
|
||||
argumentMapping = { 1 -> (a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int), "" -> (b: @R|kotlin.ParameterName|(name = String(b)) kotlin.String) }
|
||||
targetFunction = kotlin/Function2.invoke(<dispatch receiver>: kotlin.Function2<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, @R|kotlin.ParameterName|(name = String(b)) kotlin.String, kotlin.Unit>, a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int, b: @R|kotlin.ParameterName|(name = String(b)) kotlin.String): kotlin.Unit
|
||||
substitutor = <empty substitutor>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// @ParameterName annotation takes precedence over name in function type parameter
|
||||
fun call(x: (notMe: @ParameterName("a") Int, meNeither: @ParameterName("b") String) -> Unit) {
|
||||
<expr>x(1, "")</expr>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
KtFunctionalTypeVariableCall:
|
||||
target = x: kotlin.Function2<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, @R|kotlin.ParameterName|(name = String(b)) kotlin.String, kotlin.Unit>
|
||||
argumentMapping = { 1 -> (a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int), "" -> (b: @R|kotlin.ParameterName|(name = String(b)) kotlin.String) }
|
||||
targetFunction = kotlin/Function2.invoke(<dispatch receiver>: kotlin.Function2<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, @R|kotlin.ParameterName|(name = String(b)) kotlin.String, kotlin.Unit>, a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int, b: @R|kotlin.ParameterName|(name = String(b)) kotlin.String): kotlin.Unit
|
||||
substitutor = <empty substitutor>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
fun <T> T.foo(): (a: T) -> Unit = TODO()
|
||||
|
||||
fun call() {
|
||||
val x = 123.foo()
|
||||
<expr>x(1)</expr>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
KtFunctionalTypeVariableCall:
|
||||
target = val x: kotlin.Function1<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, kotlin.Unit>
|
||||
argumentMapping = { 1 -> (a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int) }
|
||||
targetFunction = kotlin/Function1.invoke(<dispatch receiver>: kotlin.Function1<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, kotlin.Unit>, a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int): kotlin.Unit
|
||||
substitutor = <empty substitutor>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// @ParameterName annotation is ignored when not used in function type
|
||||
fun call(a: @ParameterName("notMe") Int, b: @ParameterName("meNeither") String) {
|
||||
<expr>call(1, "")</expr>
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
KtFunctionCall:
|
||||
argumentMapping = { 1 -> (a: @R|kotlin.ParameterName|(name = String(notMe)) kotlin.Int), "" -> (b: @R|kotlin.ParameterName|(name = String(meNeither)) kotlin.String) }
|
||||
targetFunction = /call(a: @R|kotlin.ParameterName|(name = String(notMe)) kotlin.Int, b: @R|kotlin.ParameterName|(name = String(meNeither)) kotlin.String): kotlin.Unit
|
||||
substitutor = <empty substitutor>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun call(x: (a: Int, String) -> Unit) {
|
||||
<expr>x(1, "")</expr>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
KtFunctionalTypeVariableCall:
|
||||
target = x: kotlin.Function2<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, kotlin.String, kotlin.Unit>
|
||||
argumentMapping = { 1 -> (a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int), "" -> (p2: kotlin.String) }
|
||||
targetFunction = kotlin/Function2.invoke(<dispatch receiver>: kotlin.Function2<@R|kotlin.ParameterName|(name = String(a)) kotlin.Int, kotlin.String, kotlin.Unit>, a: @R|kotlin.ParameterName|(name = String(a)) kotlin.Int, p2: kotlin.String): kotlin.Unit
|
||||
substitutor = <empty substitutor>
|
||||
Reference in New Issue
Block a user