KT-60341 [Analysis API] Provide a type for a FirNameReference only when it refers to an actual property/variable

We cannot always return null, because in such case some expressions
would become not fully explorable from the types perspective (see
the documentation on the `getCorrespondingTypeIfPossible`)

`FirNamedReference` might appear when resolving method references (like
`foo::bar`), but also when IJ Platform tries to resolve other parts of
the Kotlin PSI, notably a `KtNameReferenceExpression` in a
function call (`bar` in `foo.bar(baz)` expression).

N.B. FE10 implementation does not support returning `null` as a type -
currently it always returns `Unit` type in case it cannot figure out
the actual type. This issue should probably be tackled together with
KT-60166, so that both implementations are more or less aligned

^KT-60341 Fixed
^KT-59077 Fixed
^KTIJ-25745 Fixed
This commit is contained in:
Roman Golyshev
2023-06-03 00:21:17 +02:00
committed by teamcity
parent 19a60d6b85
commit b8052761db
66 changed files with 870 additions and 30 deletions
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.analysis.api.fir.components
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.components.KtExpressionTypeProvider
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.utils.getReferencedElementType
import org.jetbrains.kotlin.analysis.api.fir.utils.unwrap
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
import org.jetbrains.kotlin.analysis.api.types.KtErrorType
@@ -33,7 +32,7 @@ import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.addToStdlib.applyIf
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.psi.psiUtil.getOutermostParenthesizerOrThis
internal class KtFirExpressionTypeProvider(
override val analysisSession: KtFirAnalysisSession,
@@ -71,7 +70,7 @@ internal class KtFirExpressionTypeProvider(
}
}
is FirExpression -> fir.typeRef.coneType.asKtType()
is FirNamedReference -> fir.getReferencedElementType().asKtType()
is FirNamedReference -> fir.getCorrespondingTypeIfPossible()?.asKtType()
is FirStatement -> with(analysisSession) { builtinTypes.UNIT }
is FirTypeRef, is FirImport, is FirPackageDirective, is FirLabel, is FirTypeParameterRef -> null
@@ -95,6 +94,49 @@ internal class KtFirExpressionTypeProvider(
}
}
/**
* It only makes sense to provide type for the references which reference some actual properties/variables.
*
* In cases when the name reference references a function (a REAL function, not a functional type variable), it does not
* make sense to provide any type for it.
*
* ---
*
* Why not just always provide null for name references? In such case, the following case would be a problem:
*
* ```kt
* fun usage(action: String.(Int) -> String) {
* "hello".<expr>action</expr>(10)
* }
* ```
*
* The user might want to know the type of the `action` callback. If we always return null for the named references,
* we won't be able to handle this request, and just return null. So the user will only be able to see the type
* of the whole expression instead, and that is not what he wants.
*/
private fun FirNamedReference.getCorrespondingTypeIfPossible(): ConeKotlinType? =
findOuterPropertyAccessExpression()?.typeRef?.coneType
/**
* Finds an outer expression for [this] named reference in cases when it is a part of a property access.
*
* Otherwise, return null.
*/
private fun FirNamedReference.findOuterPropertyAccessExpression(): FirExpression? {
val referenceExpression = psi as? KtExpression ?: return null
val outerExpression = referenceExpression.getOutermostParenthesizerOrThis().parent as? KtElement ?: return null
return when (val outerFirElement = outerExpression.getOrBuildFir(firResolveSession)) {
is FirVariableAssignment -> outerFirElement.lValue
is FirPropertyAccessExpression -> outerFirElement
is FirImplicitInvokeCall -> outerFirElement.explicitReceiver
is FirSafeCallExpression -> {
if (outerFirElement.selector is FirPropertyAccessExpression) outerFirElement else null
}
else -> null
}
}
private fun getReturnTypeForArrayStyleAssignmentTarget(
expression: KtExpression,
fir: FirFunctionCall
@@ -12,24 +12,16 @@ import org.jetbrains.kotlin.analysis.api.KtNonConstantInitializerValue
import org.jetbrains.kotlin.analysis.api.components.KtConstantEvaluationMode
import org.jetbrains.kotlin.analysis.api.fir.evaluate.FirAnnotationValueConverter
import org.jetbrains.kotlin.analysis.api.fir.evaluate.FirCompileTimeConstantEvaluator
import org.jetbrains.kotlin.analysis.api.fir.getCandidateSymbols
import org.jetbrains.kotlin.analysis.api.types.KtTypeNullability
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.classKind
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.types.ConeErrorType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeNullability
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
@@ -50,18 +42,6 @@ internal fun KtExpression.unwrap(): KtExpression {
} ?: this
}
internal fun FirNamedReference.getReferencedElementType(): ConeKotlinType {
val symbols = when (this) {
is FirResolvedNamedReference -> listOf(resolvedSymbol)
is FirErrorNamedReference -> getCandidateSymbols()
else -> error("Unexpected ${this::class}")
}
val firCallableDeclaration = symbols.singleOrNull()?.fir as? FirCallableDeclaration
?: return ConeErrorType(ConeUnresolvedNameError(name))
return firCallableDeclaration.symbol.resolvedReturnType
}
internal fun KtTypeNullability.toConeNullability() = when (this) {
KtTypeNullability.NULLABLE -> ConeNullability.NULLABLE
KtTypeNullability.NON_NULLABLE -> ConeNullability.NOT_NULL
@@ -301,4 +301,146 @@ public class FirIdeDependentAnalysisSourceModuleHLExpressionTypeTestGenerated ex
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/assignment/readArrayElement.kt");
}
}
@Nested
@TestMetadata("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference")
@TestDataPath("$PROJECT_ROOT")
public class NameReference {
@Test
public void testAllFilesPresentInNameReference() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("assignment.kt")
public void testAssignment() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/assignment.kt");
}
@Test
@TestMetadata("assignment_qualified.kt")
public void testAssignment_qualified() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/assignment_qualified.kt");
}
@Test
@TestMetadata("callableReference.kt")
public void testCallableReference() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/callableReference.kt");
}
@Test
@TestMetadata("capturedBoundType.kt")
public void testCapturedBoundType() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/capturedBoundType.kt");
}
@Test
@TestMetadata("functionCall.kt")
public void testFunctionCall() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionCall.kt");
}
@Test
@TestMetadata("functionCall_invalid.kt")
public void testFunctionCall_invalid() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionCall_invalid.kt");
}
@Test
@TestMetadata("functionCall_safeAccess.kt")
public void testFunctionCall_safeAccess() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionCall_safeAccess.kt");
}
@Test
@TestMetadata("functionalType.kt")
public void testFunctionalType() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionalType.kt");
}
@Test
@TestMetadata("functionalType_parens_1.kt")
public void testFunctionalType_parens_1() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionalType_parens_1.kt");
}
@Test
@TestMetadata("functionalType_parens_2.kt")
public void testFunctionalType_parens_2() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionalType_parens_2.kt");
}
@Test
@TestMetadata("functionalType_withReceiver.kt")
public void testFunctionalType_withReceiver() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionalType_withReceiver.kt");
}
@Test
@TestMetadata("innerType_constructor.kt")
public void testInnerType_constructor() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/innerType_constructor.kt");
}
@Test
@TestMetadata("innerType_constructor_invalid.kt")
public void testInnerType_constructor_invalid() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/innerType_constructor_invalid.kt");
}
@Test
@TestMetadata("invokeCallOnObject1.kt")
public void testInvokeCallOnObject1() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/invokeCallOnObject1.kt");
}
@Test
@TestMetadata("invokeCallOnObject2.kt")
public void testInvokeCallOnObject2() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/invokeCallOnObject2.kt");
}
@Test
@TestMetadata("nestedType_constructor.kt")
public void testNestedType_constructor() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/nestedType_constructor.kt");
}
@Test
@TestMetadata("nestedType_constructor_invalid.kt")
public void testNestedType_constructor_invalid() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/nestedType_constructor_invalid.kt");
}
@Test
@TestMetadata("nestedType_object.kt")
public void testNestedType_object() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/nestedType_object.kt");
}
@Test
@TestMetadata("nestedType_object_extensionInvoke.kt")
public void testNestedType_object_extensionInvoke() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/nestedType_object_extensionInvoke.kt");
}
@Test
@TestMetadata("propertyCall.kt")
public void testPropertyCall() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/propertyCall.kt");
}
@Test
@TestMetadata("propertyCall_genericExtension.kt")
public void testPropertyCall_genericExtension() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/propertyCall_genericExtension.kt");
}
@Test
@TestMetadata("propertyCall_safeAccess.kt")
public void testPropertyCall_safeAccess() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/propertyCall_safeAccess.kt");
}
}
}
@@ -301,4 +301,146 @@ public class FirIdeNormalAnalysisSourceModuleHLExpressionTypeTestGenerated exten
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/assignment/readArrayElement.kt");
}
}
@Nested
@TestMetadata("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference")
@TestDataPath("$PROJECT_ROOT")
public class NameReference {
@Test
public void testAllFilesPresentInNameReference() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("assignment.kt")
public void testAssignment() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/assignment.kt");
}
@Test
@TestMetadata("assignment_qualified.kt")
public void testAssignment_qualified() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/assignment_qualified.kt");
}
@Test
@TestMetadata("callableReference.kt")
public void testCallableReference() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/callableReference.kt");
}
@Test
@TestMetadata("capturedBoundType.kt")
public void testCapturedBoundType() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/capturedBoundType.kt");
}
@Test
@TestMetadata("functionCall.kt")
public void testFunctionCall() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionCall.kt");
}
@Test
@TestMetadata("functionCall_invalid.kt")
public void testFunctionCall_invalid() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionCall_invalid.kt");
}
@Test
@TestMetadata("functionCall_safeAccess.kt")
public void testFunctionCall_safeAccess() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionCall_safeAccess.kt");
}
@Test
@TestMetadata("functionalType.kt")
public void testFunctionalType() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionalType.kt");
}
@Test
@TestMetadata("functionalType_parens_1.kt")
public void testFunctionalType_parens_1() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionalType_parens_1.kt");
}
@Test
@TestMetadata("functionalType_parens_2.kt")
public void testFunctionalType_parens_2() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionalType_parens_2.kt");
}
@Test
@TestMetadata("functionalType_withReceiver.kt")
public void testFunctionalType_withReceiver() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/functionalType_withReceiver.kt");
}
@Test
@TestMetadata("innerType_constructor.kt")
public void testInnerType_constructor() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/innerType_constructor.kt");
}
@Test
@TestMetadata("innerType_constructor_invalid.kt")
public void testInnerType_constructor_invalid() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/innerType_constructor_invalid.kt");
}
@Test
@TestMetadata("invokeCallOnObject1.kt")
public void testInvokeCallOnObject1() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/invokeCallOnObject1.kt");
}
@Test
@TestMetadata("invokeCallOnObject2.kt")
public void testInvokeCallOnObject2() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/invokeCallOnObject2.kt");
}
@Test
@TestMetadata("nestedType_constructor.kt")
public void testNestedType_constructor() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/nestedType_constructor.kt");
}
@Test
@TestMetadata("nestedType_constructor_invalid.kt")
public void testNestedType_constructor_invalid() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/nestedType_constructor_invalid.kt");
}
@Test
@TestMetadata("nestedType_object.kt")
public void testNestedType_object() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/nestedType_object.kt");
}
@Test
@TestMetadata("nestedType_object_extensionInvoke.kt")
public void testNestedType_object_extensionInvoke() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/nestedType_object_extensionInvoke.kt");
}
@Test
@TestMetadata("propertyCall.kt")
public void testPropertyCall() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/propertyCall.kt");
}
@Test
@TestMetadata("propertyCall_genericExtension.kt")
public void testPropertyCall_genericExtension() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/propertyCall_genericExtension.kt");
}
@Test
@TestMetadata("propertyCall_safeAccess.kt")
public void testPropertyCall_safeAccess() throws Exception {
runTest("analysis/analysis-api/testData/components/expressionTypeProvider/expressionType/nameReference/propertyCall_safeAccess.kt");
}
}
}
@@ -46,6 +46,12 @@ public class FirIdeDependentAnalysisSourceModuleAnalysisApiExpressionPsiTypeProv
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("capturedBoundType.kt")
public void testCapturedBoundType() throws Exception {
runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/capturedBoundType.kt");
}
@Test
@TestMetadata("class_object_call.kt")
public void testClass_object_call() throws Exception {
@@ -58,6 +64,12 @@ public class FirIdeDependentAnalysisSourceModuleAnalysisApiExpressionPsiTypeProv
runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/class_object_constructor.kt");
}
@Test
@TestMetadata("KTIJ25461.kt")
public void testKTIJ25461() throws Exception {
runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/KTIJ25461.kt");
}
@Test
@TestMetadata("localClassWithUnresolvedSuperType.kt")
public void testLocalClassWithUnresolvedSuperType() throws Exception {
@@ -46,6 +46,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProvide
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("capturedBoundType.kt")
public void testCapturedBoundType() throws Exception {
runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/capturedBoundType.kt");
}
@Test
@TestMetadata("class_object_call.kt")
public void testClass_object_call() throws Exception {
@@ -58,6 +64,12 @@ public class FirIdeNormalAnalysisSourceModuleAnalysisApiExpressionPsiTypeProvide
runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/class_object_constructor.kt");
}
@Test
@TestMetadata("KTIJ25461.kt")
public void testKTIJ25461() throws Exception {
runTest("analysis/analysis-api/testData/components/psiTypeProvider/psiType/forExpression/KTIJ25461.kt");
}
@Test
@TestMetadata("localClassWithUnresolvedSuperType.kt")
public void testLocalClassWithUnresolvedSuperType() throws Exception {