FIR DFA: exit delegate expression only after accessor completion

This commit fixes DFA problem (see test) when we accidentally try
to merge incoming flows from member property (val ... by cached ...) and
the following function. While completing lambda of buildList { ... }
inside by cached we accidentally assume that delegate is already left
and add the lambda into exitsFromCompletedPostponedAnonymousFunction.
This commit fixes the problem by exiting delegate later, after completion.
This commit is contained in:
Mikhail Glukhikh
2022-01-17 14:18:58 +03:00
committed by teamcity
parent c324bbfc37
commit 98cdc95cd0
6 changed files with 214 additions and 48 deletions
@@ -5884,6 +5884,12 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis
public void testWeakHashMap() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/weakHashMap.kt");
}
@Test
@TestMetadata("WithValidityAssertion.kt")
public void testWithValidityAssertion() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/WithValidityAssertion.kt");
}
}
@Nested
@@ -0,0 +1,84 @@
FILE: WithValidityAssertion.kt
public abstract interface ValidityToken : R|kotlin/Any| {
public abstract fun assertIsValid(): R|kotlin/Unit|
}
public abstract interface ValidityTokenOwner : R|kotlin/Any| {
public abstract val token: R|ValidityToken|
public get(): R|ValidityToken|
}
public final inline fun R|ValidityTokenOwner|.assertIsValid(): R|kotlin/Unit| {
this@R|/assertIsValid|.R|/ValidityTokenOwner.token|.R|/ValidityToken.assertIsValid|()
}
public final inline fun <R> R|ValidityTokenOwner|.withValidityAssertion(action: R|() -> R|): R|R| {
this@R|/withValidityAssertion|.R|/assertIsValid|()
^withValidityAssertion R|<local>/action|.R|SubstitutionOverride<kotlin/Function0.invoke: R|R|>|()
}
public final class ValidityAwareCachedValue<T> : R|kotlin/properties/ReadOnlyProperty<kotlin/Any, T>| {
public constructor<T>(token: R|ValidityToken|, init: R|() -> T|): R|ValidityAwareCachedValue<T>| {
super<R|kotlin/Any|>()
}
private final val token: R|ValidityToken| = R|<local>/token|
private get(): R|ValidityToken|
private final val lazyValue: R|kotlin/Lazy<T>| = R|kotlin/lazy|<R|T|>(Q|kotlin/LazyThreadSafetyMode|.R|kotlin/LazyThreadSafetyMode.PUBLICATION|, R|<local>/init|)
private get(): R|kotlin/Lazy<T>|
@R|kotlin/Suppress|(names = vararg(String(UNCHECKED_CAST))) public final override operator fun getValue(thisRef: R|kotlin/Any|, property: R|kotlin/reflect/KProperty<*>|): R|T| {
this@R|/ValidityAwareCachedValue|.R|/ValidityAwareCachedValue.token|.R|/ValidityToken.assertIsValid|()
^getValue this@R|/ValidityAwareCachedValue|.R|/ValidityAwareCachedValue.lazyValue|.R|SubstitutionOverride<kotlin/Lazy.value: R|T|>|
}
}
internal final fun <T> R|ValidityTokenOwner|.cached(init: R|() -> T|): R|ValidityAwareCachedValue<T>| {
^cached R|/ValidityAwareCachedValue.ValidityAwareCachedValue|<R|T|>(this@R|/cached|.R|/ValidityTokenOwner.token|, R|<local>/init|)
}
public final typealias KtScopeNameFilter = R|(kotlin/String) -> kotlin/Boolean|
public abstract class KtFirNonStarImportingScope : R|ValidityTokenOwner| {
public constructor(firScope: R|FirScope|, builder: R|KtSymbolByFirBuilder|, token: R|ValidityToken|): R|KtFirNonStarImportingScope| {
super<R|kotlin/Any|>()
}
private final val firScope: R|FirScope| = R|<local>/firScope|
private get(): R|FirScope|
private final val builder: R|KtSymbolByFirBuilder| = R|<local>/builder|
private get(): R|KtSymbolByFirBuilder|
public open override val token: R|ValidityToken| = R|<local>/token|
public get(): R|ValidityToken|
private final val imports: R|kotlin/collections/List<kotlin/String>|by this@R|/KtFirNonStarImportingScope|.R|/cached|<R|kotlin/collections/List<kotlin/String>|>(<L> = cached@fun <anonymous>(): R|kotlin/collections/List<kotlin/String>| <inline=NoInline> {
^ R|kotlin/collections/buildList|<R|kotlin/String|>(<L> = buildList@fun R|kotlin/collections/MutableList<kotlin/String>|.<anonymous>(): R|kotlin/Unit| <inline=Inline, kind=EXACTLY_ONCE> {
this@R|/KtFirNonStarImportingScope|.R|/KtFirNonStarImportingScope.getCallableNames|().R|kotlin/collections/forEach|<R|kotlin/String|>(<L> = forEach@fun <anonymous>(it: R|kotlin/String|): R|kotlin/Unit| <inline=Inline, kind=UNKNOWN> {
this@R|special/anonymous|.R|SubstitutionOverride<kotlin/collections/MutableList.add: R|kotlin/Boolean|>|(R|<local>/it|)
}
)
}
)
}
)
private get(): R|kotlin/collections/List<kotlin/String>| {
^ this@R|/KtFirNonStarImportingScope|.D|/KtFirNonStarImportingScope.imports|.R|SubstitutionOverride</ValidityAwareCachedValue.getValue: R|Stub (chain inference): TypeVariable(_T)|>|(this@R|/KtFirNonStarImportingScope|, ::R|/KtFirNonStarImportingScope.imports|)
}
public final fun getCallableSymbols(nameFilter: R|KtScopeNameFilter|): R|kotlin/sequences/Sequence<KtCallableSymbol>| {
^getCallableSymbols this@R|/KtFirNonStarImportingScope|.R|/withValidityAssertion|<R|kotlin/sequences/Sequence<KtCallableSymbol>|>(<L> = withValidityAssertion@fun <anonymous>(): R|kotlin/sequences/Sequence<KtCallableSymbol>| <inline=Inline, kind=UNKNOWN> {
^ this@R|/KtFirNonStarImportingScope|.R|/KtFirNonStarImportingScope.firScope|.R|/FirScope.getCallableSymbols|(this@R|/KtFirNonStarImportingScope|.R|/KtFirNonStarImportingScope.getCallableNames|().R|kotlin/collections/filter|<R|kotlin/String|>(R|<local>/nameFilter|), this@R|/KtFirNonStarImportingScope|.R|/KtFirNonStarImportingScope.builder|)
}
)
}
public abstract fun getCallableNames(): R|kotlin/collections/Set<kotlin/String>|
}
public abstract interface FirScope : R|kotlin/Any| {
public abstract fun getCallableSymbols(callableNames: R|kotlin/collections/Collection<kotlin/String>|, builder: R|KtSymbolByFirBuilder|): R|kotlin/sequences/Sequence<KtCallableSymbol>|
}
public abstract interface KtCallableSymbol : R|kotlin/Any| {
}
public abstract interface KtSymbolByFirBuilder : R|kotlin/Any| {
}
@@ -0,0 +1,66 @@
import kotlin.reflect.KProperty
import kotlin.properties.ReadOnlyProperty
interface ValidityToken {
fun assertIsValid()
}
interface ValidityTokenOwner {
val token: ValidityToken
}
<!NOTHING_TO_INLINE!>inline<!> fun ValidityTokenOwner.assertIsValid() {
token.assertIsValid()
}
inline fun <R> ValidityTokenOwner.withValidityAssertion(action: () -> R): R {
assertIsValid()
return action()
}
class ValidityAwareCachedValue<T>(
private val token: ValidityToken,
init: () -> T
) : ReadOnlyProperty<Any, T> {
private val lazyValue = lazy(LazyThreadSafetyMode.PUBLICATION, init)
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any, property: KProperty<*>): T {
token.assertIsValid()
return lazyValue.value
}
}
internal fun <T> ValidityTokenOwner.cached(init: () -> T) = ValidityAwareCachedValue(token, init)
public typealias KtScopeNameFilter = (String) -> Boolean
abstract class KtFirNonStarImportingScope(
private val firScope: FirScope,
private val builder: KtSymbolByFirBuilder,
override val token: ValidityToken,
) : ValidityTokenOwner {
private val imports: List<String> by cached {
buildList {
getCallableNames().forEach {
add(it)
}
}
}
fun getCallableSymbols(nameFilter: KtScopeNameFilter): Sequence<KtCallableSymbol> = withValidityAssertion {
firScope.getCallableSymbols(getCallableNames().filter(nameFilter), builder)
}
abstract fun getCallableNames(): Set<String>
}
interface FirScope {
fun getCallableSymbols(callableNames: Collection<String>, builder: KtSymbolByFirBuilder): Sequence<KtCallableSymbol>
}
interface KtCallableSymbol
interface KtSymbolByFirBuilder
@@ -5884,6 +5884,12 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest {
public void testWeakHashMap() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/weakHashMap.kt");
}
@Test
@TestMetadata("WithValidityAssertion.kt")
public void testWithValidityAssertion() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/WithValidityAssertion.kt");
}
}
@Nested
@@ -5884,6 +5884,12 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
public void testWeakHashMap() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/weakHashMap.kt");
}
@Test
@TestMetadata("WithValidityAssertion.kt")
public void testWithValidityAssertion() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/WithValidityAssertion.kt");
}
}
@Nested
@@ -276,6 +276,8 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor
property.transformAccessors()
val completedCalls = completeCandidates()
dataFlowAnalyzer.exitDelegateExpression()
val finalSubstitutor = createFinalSubstitutor()
// Replace stub types with corresponding type variable types
@@ -301,62 +303,58 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor
data: ResolutionMode,
): FirStatement {
dataFlowAnalyzer.enterDelegateExpression()
try {
// First, resolve delegate expression in dependent context
val delegateExpression =
wrappedDelegateExpression.expression.transformSingle(transformer, ResolutionMode.ContextDependent)
// First, resolve delegate expression in dependent context
val delegateExpression =
wrappedDelegateExpression.expression.transformSingle(transformer, ResolutionMode.ContextDependent)
// Second, replace result type of delegate expression with stub type if delegate not yet resolved
if (delegateExpression is FirQualifiedAccess) {
val calleeReference = delegateExpression.calleeReference
if (calleeReference is FirNamedReferenceWithCandidate) {
val system = calleeReference.candidate.system
system.notFixedTypeVariables.forEach {
system.markPostponedVariable(it.value.typeVariable)
}
val typeVariableTypeToStubType = context.inferenceSession.createSyntheticStubTypes(system)
val substitutor = createTypeSubstitutorByTypeConstructor(typeVariableTypeToStubType, session.typeContext)
val delegateExpressionTypeRef = delegateExpression.typeRef
val stubTypeSubstituted = substitutor.substituteOrNull(delegateExpressionTypeRef.coneType)
delegateExpression.replaceTypeRef(delegateExpressionTypeRef.withReplacedConeType(stubTypeSubstituted))
}
}
val provideDelegateCall = wrappedDelegateExpression.delegateProvider as FirFunctionCall
// Resolve call for provideDelegate, without completion
provideDelegateCall.transformSingle(this, ResolutionMode.ContextIndependent)
// If we got successful candidate for provideDelegate, let's select it
val provideDelegateCandidate = provideDelegateCall.candidate()
if (provideDelegateCandidate != null && provideDelegateCandidate.isSuccessful) {
val system = provideDelegateCandidate.system
// Second, replace result type of delegate expression with stub type if delegate not yet resolved
if (delegateExpression is FirQualifiedAccess) {
val calleeReference = delegateExpression.calleeReference
if (calleeReference is FirNamedReferenceWithCandidate) {
val system = calleeReference.candidate.system
system.notFixedTypeVariables.forEach {
system.markPostponedVariable(it.value.typeVariable)
}
val typeVariableTypeToStubType = context.inferenceSession.createSyntheticStubTypes(system)
val substitutor = createTypeSubstitutorByTypeConstructor(typeVariableTypeToStubType, session.typeContext)
val stubTypeSubstituted = substitutor.substituteOrSelf(provideDelegateCandidate.substitutor.substituteOrSelf(components.typeFromCallee(provideDelegateCall).type))
provideDelegateCall.replaceTypeRef(provideDelegateCall.typeRef.resolvedTypeFromPrototype(stubTypeSubstituted))
return provideDelegateCall
val delegateExpressionTypeRef = delegateExpression.typeRef
val stubTypeSubstituted = substitutor.substituteOrNull(delegateExpressionTypeRef.coneType)
delegateExpression.replaceTypeRef(delegateExpressionTypeRef.withReplacedConeType(stubTypeSubstituted))
}
if (provideDelegateCall.calleeReference is FirResolvedNamedReference) {
return provideDelegateCall
}
// Otherwise, rollback
(provideDelegateCall as? FirFunctionCall)?.let { dataFlowAnalyzer.dropSubgraphFromCall(it) }
// Select delegate expression otherwise
return delegateExpression
.approximateIfIsIntegerConst()
} finally {
dataFlowAnalyzer.exitDelegateExpression()
}
val provideDelegateCall = wrappedDelegateExpression.delegateProvider as FirFunctionCall
// Resolve call for provideDelegate, without completion
provideDelegateCall.transformSingle(this, ResolutionMode.ContextIndependent)
// If we got successful candidate for provideDelegate, let's select it
val provideDelegateCandidate = provideDelegateCall.candidate()
if (provideDelegateCandidate != null && provideDelegateCandidate.isSuccessful) {
val system = provideDelegateCandidate.system
system.notFixedTypeVariables.forEach {
system.markPostponedVariable(it.value.typeVariable)
}
val typeVariableTypeToStubType = context.inferenceSession.createSyntheticStubTypes(system)
val substitutor = createTypeSubstitutorByTypeConstructor(typeVariableTypeToStubType, session.typeContext)
val stubTypeSubstituted = substitutor.substituteOrSelf(provideDelegateCandidate.substitutor.substituteOrSelf(components.typeFromCallee(provideDelegateCall).type))
provideDelegateCall.replaceTypeRef(provideDelegateCall.typeRef.resolvedTypeFromPrototype(stubTypeSubstituted))
return provideDelegateCall
}
if (provideDelegateCall.calleeReference is FirResolvedNamedReference) {
return provideDelegateCall
}
// Otherwise, rollback
(provideDelegateCall as? FirFunctionCall)?.let { dataFlowAnalyzer.dropSubgraphFromCall(it) }
// Select delegate expression otherwise
return delegateExpression
.approximateIfIsIntegerConst()
}
private fun transformLocalVariable(variable: FirProperty): FirProperty {