From af6672c87dc5bf46a69e44ac42bf12b623db27ec Mon Sep 17 00:00:00 2001 From: aleksandrina-streltsova Date: Tue, 25 Jul 2023 17:13:03 +0300 Subject: [PATCH] [FIR] store tower data context for operand in and/or binary expression KTIJ-26113 --- ...eScopeContextForPositionTestGenerated.java | 6 + ...eScopeContextForPositionTestGenerated.java | 6 + ...eScopeContextForPositionTestGenerated.java | 6 + .../smartCastInWhenEntryCondition.kt | 9 + .../smartCastInWhenEntryCondition.pretty.txt | 63 ++++ .../smartCastInWhenEntryCondition.txt | 285 ++++++++++++++++++ .../builder/FirTowerDataContextCollector.kt | 25 +- 7 files changed, 396 insertions(+), 4 deletions(-) create mode 100644 analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.kt create mode 100644 analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.pretty.txt create mode 100644 analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.txt diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/scopeProvider/FirIdeDependentAnalysisSourceModuleScopeContextForPositionTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/scopeProvider/FirIdeDependentAnalysisSourceModuleScopeContextForPositionTestGenerated.java index d1291fc2d1e..aa73dae7504 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/scopeProvider/FirIdeDependentAnalysisSourceModuleScopeContextForPositionTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/scopeProvider/FirIdeDependentAnalysisSourceModuleScopeContextForPositionTestGenerated.java @@ -76,6 +76,12 @@ public class FirIdeDependentAnalysisSourceModuleScopeContextForPositionTestGener runTest("analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/simpleScopeContextForPosition.kt"); } + @Test + @TestMetadata("smartCastInWhenEntryCondition.kt") + public void testSmartCastInWhenEntryCondition() throws Exception { + runTest("analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.kt"); + } + @Test @TestMetadata("syntheticPropertiesScope.kt") public void testSyntheticPropertiesScope() throws Exception { diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/scopeProvider/FirIdeNormalAnalysisSourceModuleScopeContextForPositionTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/scopeProvider/FirIdeNormalAnalysisSourceModuleScopeContextForPositionTestGenerated.java index 0e87ed33215..a1b84b93a76 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/scopeProvider/FirIdeNormalAnalysisSourceModuleScopeContextForPositionTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/components/scopeProvider/FirIdeNormalAnalysisSourceModuleScopeContextForPositionTestGenerated.java @@ -76,6 +76,12 @@ public class FirIdeNormalAnalysisSourceModuleScopeContextForPositionTestGenerate runTest("analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/simpleScopeContextForPosition.kt"); } + @Test + @TestMetadata("smartCastInWhenEntryCondition.kt") + public void testSmartCastInWhenEntryCondition() throws Exception { + runTest("analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.kt"); + } + @Test @TestMetadata("syntheticPropertiesScope.kt") public void testSyntheticPropertiesScope() throws Exception { diff --git a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/scopeProvider/FirStandaloneNormalAnalysisSourceModuleScopeContextForPositionTestGenerated.java b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/scopeProvider/FirStandaloneNormalAnalysisSourceModuleScopeContextForPositionTestGenerated.java index d1348e5739d..c5d986e71fc 100644 --- a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/scopeProvider/FirStandaloneNormalAnalysisSourceModuleScopeContextForPositionTestGenerated.java +++ b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/components/scopeProvider/FirStandaloneNormalAnalysisSourceModuleScopeContextForPositionTestGenerated.java @@ -76,6 +76,12 @@ public class FirStandaloneNormalAnalysisSourceModuleScopeContextForPositionTestG runTest("analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/simpleScopeContextForPosition.kt"); } + @Test + @TestMetadata("smartCastInWhenEntryCondition.kt") + public void testSmartCastInWhenEntryCondition() throws Exception { + runTest("analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.kt"); + } + @Test @TestMetadata("syntheticPropertiesScope.kt") public void testSyntheticPropertiesScope() throws Exception { diff --git a/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.kt b/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.kt new file mode 100644 index 00000000000..ccc62b96d05 --- /dev/null +++ b/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.kt @@ -0,0 +1,9 @@ +class A + +class B + +private fun Any.test(): Int = when { + this is A && a -> 10 + this is B && b -> 2 + else -> 0 +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.pretty.txt b/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.pretty.txt new file mode 100644 index 00000000000..1d171805305 --- /dev/null +++ b/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.pretty.txt @@ -0,0 +1,63 @@ +element: a +implicit receivers: + type: A + owner symbol: KtFirFunctionSymbol + +scopes: + LocalScope, index = 0 + packages: 0 + classifiers: 0 + callables: 0 + constructors: 0 + + TypeScope, index = 1 + packages: 0 + classifiers: 0 + callables: 3 + fun equals(other: kotlin.Any?): kotlin.Boolean + fun hashCode(): kotlin.Int + fun toString(): kotlin.String + constructors: 1 + constructor() + + LocalScope, index = 2 + packages: 0 + classifiers: 0 + callables: 0 + constructors: 0 + + ExplicitSimpleImportingScope, index = 3 + packages: 0 + classifiers: 0 + callables: 0 + constructors: 0 + + PackageMemberScope, index = 4 + packages: 6 + META-INF + java + javax + kotlin + org + sun + classifiers: 2 + class A + class B + callables: 1 + fun kotlin.Any.test(): kotlin.Int + constructors: 0 + + DefaultSimpleImportingScope, index = 5 + + DefaultSimpleImportingScope, index = 6 + + ExplicitStarImportingScope, index = 7 + packages: 0 + classifiers: 0 + callables: 0 + constructors: 0 + + DefaultSimpleImportingScope, index = 8 + + DefaultStarImportingScope, index = 9 + diff --git a/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.txt b/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.txt new file mode 100644 index 00000000000..2affd26cc8f --- /dev/null +++ b/analysis/analysis-api/testData/components/scopeProvider/scopeContextForPosition/smartCastInWhenEntryCondition.txt @@ -0,0 +1,285 @@ +element: a +implicit receivers: + type: KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: A + owner symbol: KtFirFunctionSymbol + +scopes: + LocalScope, index = 0 + packages: 0 + classifiers: 0 + callables: 0 + constructors: 0 + + TypeScope, index = 1 + packages: 0 + classifiers: 0 + callables: 3 + KtFunctionSymbol: + annotationsList: [] + callableIdIfNonLocal: kotlin/Any.equals + contextReceivers: [] + contractEffects: [] + hasStableParameterNames: true + isActual: false + isBuiltinFunctionInvoke: false + isExpect: false + isExtension: false + isExternal: false + isInfix: false + isInline: false + isOperator: true + isOverride: false + isStatic: false + isSuspend: false + modality: OPEN + name: equals + origin: LIBRARY + receiverParameter: null + returnType: KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: kotlin/Boolean + symbolKind: CLASS_MEMBER + typeParameters: [] + valueParameters: [ + KtValueParameterSymbol: + annotationsList: [] + callableIdIfNonLocal: null + contextReceivers: [] + generatedPrimaryConstructorProperty: null + hasDefaultValue: false + isCrossinline: false + isExtension: false + isImplicitLambdaParameter: false + isNoinline: false + isVararg: false + name: other + origin: LIBRARY + receiverParameter: null + returnType: KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: kotlin/Any? + symbolKind: LOCAL + typeParameters: [] + ] + visibility: Public + KtFunctionSymbol: + annotationsList: [] + callableIdIfNonLocal: kotlin/Any.hashCode + contextReceivers: [] + contractEffects: [] + hasStableParameterNames: true + isActual: false + isBuiltinFunctionInvoke: false + isExpect: false + isExtension: false + isExternal: false + isInfix: false + isInline: false + isOperator: false + isOverride: false + isStatic: false + isSuspend: false + modality: OPEN + name: hashCode + origin: LIBRARY + receiverParameter: null + returnType: KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: kotlin/Int + symbolKind: CLASS_MEMBER + typeParameters: [] + valueParameters: [] + visibility: Public + KtFunctionSymbol: + annotationsList: [] + callableIdIfNonLocal: kotlin/Any.toString + contextReceivers: [] + contractEffects: [] + hasStableParameterNames: true + isActual: false + isBuiltinFunctionInvoke: false + isExpect: false + isExtension: false + isExternal: false + isInfix: false + isInline: false + isOperator: false + isOverride: false + isStatic: false + isSuspend: false + modality: OPEN + name: toString + origin: LIBRARY + receiverParameter: null + returnType: KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: kotlin/String + symbolKind: CLASS_MEMBER + typeParameters: [] + valueParameters: [] + visibility: Public + constructors: 1 + KtConstructorSymbol: + annotationsList: [] + callableIdIfNonLocal: null + containingClassIdIfNonLocal: A + contextReceivers: [] + hasStableParameterNames: true + isActual: false + isExpect: false + isExtension: false + isPrimary: true + origin: SOURCE_MEMBER_GENERATED + receiverParameter: null + returnType: KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: A + symbolKind: CLASS_MEMBER + typeParameters: [] + valueParameters: [] + visibility: Public + + LocalScope, index = 2 + packages: 0 + classifiers: 0 + callables: 0 + constructors: 0 + + ExplicitSimpleImportingScope, index = 3 + packages: 0 + classifiers: 0 + callables: 0 + constructors: 0 + + PackageMemberScope, index = 4 + packages: 6 + KtPackageSymbol: + fqName: META-INF + origin: SOURCE + KtPackageSymbol: + fqName: java + origin: SOURCE + KtPackageSymbol: + fqName: javax + origin: SOURCE + KtPackageSymbol: + fqName: kotlin + origin: SOURCE + KtPackageSymbol: + fqName: org + origin: SOURCE + KtPackageSymbol: + fqName: sun + origin: SOURCE + classifiers: 2 + KtNamedClassOrObjectSymbol: + annotationsList: [] + classIdIfNonLocal: A + classKind: CLASS + companionObject: null + contextReceivers: [] + isActual: false + isData: false + isExpect: false + isExternal: false + isFun: false + isInline: false + isInner: false + modality: FINAL + name: A + origin: SOURCE + superTypes: [ + KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: kotlin/Any + ] + symbolKind: TOP_LEVEL + typeParameters: [] + visibility: Public + KtNamedClassOrObjectSymbol: + annotationsList: [] + classIdIfNonLocal: B + classKind: CLASS + companionObject: null + contextReceivers: [] + isActual: false + isData: false + isExpect: false + isExternal: false + isFun: false + isInline: false + isInner: false + modality: FINAL + name: B + origin: SOURCE + superTypes: [ + KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: kotlin/Any + ] + symbolKind: TOP_LEVEL + typeParameters: [] + visibility: Public + callables: 1 + KtFunctionSymbol: + annotationsList: [] + callableIdIfNonLocal: /test + contextReceivers: [] + contractEffects: [] + hasStableParameterNames: true + isActual: false + isBuiltinFunctionInvoke: false + isExpect: false + isExtension: true + isExternal: false + isInfix: false + isInline: false + isOperator: false + isOverride: false + isStatic: false + isSuspend: false + modality: FINAL + name: test + origin: SOURCE + receiverParameter: KtReceiverParameterSymbol: + annotationsList: [] + origin: SOURCE + owningCallableSymbol: KtFunctionSymbol(/test) + type: KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: kotlin/Any + returnType: KtUsualClassType: + annotationsList: [] + ownTypeArguments: [] + type: kotlin/Int + symbolKind: TOP_LEVEL + typeParameters: [] + valueParameters: [] + visibility: Private + constructors: 0 + + DefaultSimpleImportingScope, index = 5 + + DefaultSimpleImportingScope, index = 6 + + ExplicitStarImportingScope, index = 7 + packages: 0 + classifiers: 0 + callables: 0 + constructors: 0 + + DefaultSimpleImportingScope, index = 8 + + DefaultStarImportingScope, index = 9 + diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirTowerDataContextCollector.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirTowerDataContextCollector.kt index 4cbad87d002..592c12384c4 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirTowerDataContextCollector.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/element/builder/FirTowerDataContextCollector.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveContext import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirResolveContextCollector +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.utils.addIfNotNull @@ -115,18 +116,34 @@ internal class FirTowerDataContextAllElementsCollector : FirResolveContextCollec * - a statement in a block * - an initializer of a declaration * - an expression in when entry + * - a right operand in binary expression with operator `&&` or `||` * * Otherwise, invokes this function recursively on the parent. */ -private tailrec fun PsiElement.closestParentExpressionWithSameContextOrSelf(): KtExpression? = - when { - this is KtExpression && (parent is KtBlockExpression || parent is KtDeclarationWithInitializer || isExpressionInWhenEntry) -> this - else -> parent?.closestParentExpressionWithSameContextOrSelf() +private tailrec fun PsiElement.closestParentExpressionWithSameContextOrSelf(): KtExpression? { + if (this is KtExpression) { + if ( + parent is KtBlockExpression || + parent is KtDeclarationWithInitializer || + isExpressionInWhenEntry || + isRightOperandInBinaryLogicOperation + ) return this } + return parent?.closestParentExpressionWithSameContextOrSelf() +} + private val KtExpression.isExpressionInWhenEntry: Boolean get() = this == (parent as? KtWhenEntry)?.expression +private val KtExpression.isRightOperandInBinaryLogicOperation: Boolean + get() { + val binaryLogicOperation = (parent as? KtBinaryExpression) + ?.takeIf { it.operationToken == KtTokens.ANDAND || it.operationToken == KtTokens.OROR } + + return this == binaryLogicOperation?.right + } + /** * Returns true if [element] is considered to be a part of [this] class header. *