FIR checker: report SMARTCAST_IMPOSSIBLE for local variables
This commit is contained in:
committed by
TeamCityServer
parent
092750e215
commit
0ecc752813
+18
@@ -27510,6 +27510,18 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/assignmentConversion.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/assignmentConversion.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("capturedByAtLeastOnce.kt")
|
||||||
|
public void testCapturedByAtLeastOnce() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/capturedByAtLeastOnce.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("capturedByMultipleLambdas.kt")
|
||||||
|
public void testCapturedByMultipleLambdas() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/capturedByMultipleLambdas.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("doWhileWithMiddleBreak.kt")
|
@TestMetadata("doWhileWithMiddleBreak.kt")
|
||||||
public void testDoWhileWithMiddleBreak() throws Exception {
|
public void testDoWhileWithMiddleBreak() throws Exception {
|
||||||
@@ -35721,6 +35733,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
|
|||||||
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
|
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("lambdaInCallArgs.kt")
|
||||||
|
public void testLambdaInCallArgs() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("lazyDeclaresAndModifies.kt")
|
@TestMetadata("lazyDeclaresAndModifies.kt")
|
||||||
public void testLazyDeclaresAndModifies() throws Exception {
|
public void testLazyDeclaresAndModifies() throws Exception {
|
||||||
|
|||||||
+18
@@ -27510,6 +27510,18 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/assignmentConversion.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/assignmentConversion.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("capturedByAtLeastOnce.kt")
|
||||||
|
public void testCapturedByAtLeastOnce() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/capturedByAtLeastOnce.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("capturedByMultipleLambdas.kt")
|
||||||
|
public void testCapturedByMultipleLambdas() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/capturedByMultipleLambdas.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("doWhileWithMiddleBreak.kt")
|
@TestMetadata("doWhileWithMiddleBreak.kt")
|
||||||
public void testDoWhileWithMiddleBreak() throws Exception {
|
public void testDoWhileWithMiddleBreak() throws Exception {
|
||||||
@@ -35721,6 +35733,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
|
|||||||
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
|
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("lambdaInCallArgs.kt")
|
||||||
|
public void testLambdaInCallArgs() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("lazyDeclaresAndModifies.kt")
|
@TestMetadata("lazyDeclaresAndModifies.kt")
|
||||||
public void testLazyDeclaresAndModifies() throws Exception {
|
public void testLazyDeclaresAndModifies() throws Exception {
|
||||||
|
|||||||
@@ -253,6 +253,12 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
|||||||
): FirQualifiedAccessExpression {
|
): FirQualifiedAccessExpression {
|
||||||
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression)
|
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression)
|
||||||
?: return qualifiedAccessExpression
|
?: return qualifiedAccessExpression
|
||||||
|
val smartcastStability = stability.impliedSmartcastStability
|
||||||
|
?: if (dataFlowAnalyzer.isAccessToUnstableLocalVariable(qualifiedAccessExpression)) {
|
||||||
|
SmartcastStability.CAPTURED_VARIABLE
|
||||||
|
} else {
|
||||||
|
SmartcastStability.STABLE_VALUE
|
||||||
|
}
|
||||||
val originalType = qualifiedAccessExpression.resultType.coneType
|
val originalType = qualifiedAccessExpression.resultType.coneType
|
||||||
// For example, if (x == null) { ... },
|
// For example, if (x == null) { ... },
|
||||||
// we don't want to smartcast to Nothing?, but we want to record the nullability to its own kind of node.
|
// we don't want to smartcast to Nothing?, but we want to record the nullability to its own kind of node.
|
||||||
@@ -276,8 +282,7 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
|||||||
smartcastType = intersectedTypeRefWithoutNullableNothing
|
smartcastType = intersectedTypeRefWithoutNullableNothing
|
||||||
// NB: Nothing? in types from smartcast in DFA is recorded here (and the expression kind itself).
|
// NB: Nothing? in types from smartcast in DFA is recorded here (and the expression kind itself).
|
||||||
this.typesFromSmartCast = typesFromSmartCast
|
this.typesFromSmartCast = typesFromSmartCast
|
||||||
// TODO: differentiate capture local variable
|
this.smartcastStability = smartcastStability
|
||||||
this.smartcastStability = stability.impliedSmartcastStability ?: SmartcastStability.STABLE_VALUE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val allTypes = typesFromSmartCast.also {
|
val allTypes = typesFromSmartCast.also {
|
||||||
@@ -295,8 +300,7 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
|||||||
originalExpression = qualifiedAccessExpression
|
originalExpression = qualifiedAccessExpression
|
||||||
smartcastType = intersectedTypeRef
|
smartcastType = intersectedTypeRef
|
||||||
this.typesFromSmartCast = typesFromSmartCast
|
this.typesFromSmartCast = typesFromSmartCast
|
||||||
// TODO: differentiate capture local variable
|
this.smartcastStability = smartcastStability
|
||||||
this.smartcastStability = stability.impliedSmartcastStability ?: SmartcastStability.STABLE_VALUE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+50
-6
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
import org.jetbrains.kotlin.name.StandardClassIds
|
import org.jetbrains.kotlin.name.StandardClassIds
|
||||||
import org.jetbrains.kotlin.types.ConstantValueKind
|
import org.jetbrains.kotlin.types.ConstantValueKind
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
|
||||||
class DataFlowAnalyzerContext<FLOW : Flow>(
|
class DataFlowAnalyzerContext<FLOW : Flow>(
|
||||||
val graphBuilder: ControlFlowGraphBuilder,
|
val graphBuilder: ControlFlowGraphBuilder,
|
||||||
@@ -52,6 +53,8 @@ class DataFlowAnalyzerContext<FLOW : Flow>(
|
|||||||
var variableStorage = variableStorage
|
var variableStorage = variableStorage
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
internal var firLocalVariableAssignmentAnalyzer: FirLocalVariableAssignmentAnalyzer? = null
|
||||||
|
|
||||||
private var assignmentCounter = 0
|
private var assignmentCounter = 0
|
||||||
|
|
||||||
fun newAssignmentIndex(): Int {
|
fun newAssignmentIndex(): Int {
|
||||||
@@ -66,11 +69,12 @@ class DataFlowAnalyzerContext<FLOW : Flow>(
|
|||||||
flowOnNodes = mutableMapOf()
|
flowOnNodes = mutableMapOf()
|
||||||
|
|
||||||
preliminaryLoopVisitor.resetState()
|
preliminaryLoopVisitor.resetState()
|
||||||
|
firLocalVariableAssignmentAnalyzer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <FLOW : Flow> empty(session: FirSession): DataFlowAnalyzerContext<FLOW> =
|
fun <FLOW : Flow> empty(session: FirSession): DataFlowAnalyzerContext<FLOW> =
|
||||||
DataFlowAnalyzerContext<FLOW>(
|
DataFlowAnalyzerContext(
|
||||||
ControlFlowGraphBuilder(), VariableStorage(session),
|
ControlFlowGraphBuilder(), VariableStorage(session),
|
||||||
mutableMapOf(), mutableMapOf(), PreliminaryLoopVisitor()
|
mutableMapOf(), mutableMapOf(), PreliminaryLoopVisitor()
|
||||||
)
|
)
|
||||||
@@ -162,6 +166,10 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
|
|
||||||
// ----------------------------------- Requests -----------------------------------
|
// ----------------------------------- Requests -----------------------------------
|
||||||
|
|
||||||
|
fun isAccessToUnstableLocalVariable(qualifiedAccessExpression: FirQualifiedAccessExpression): Boolean {
|
||||||
|
return context.firLocalVariableAssignmentAnalyzer?.isAccessToUnstableLocalVariable(qualifiedAccessExpression) == true
|
||||||
|
}
|
||||||
|
|
||||||
fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
|
fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
|
||||||
/*
|
/*
|
||||||
* DataFlowAnalyzer holds variables only for declarations that have some smartcast (or can have)
|
* DataFlowAnalyzer holds variables only for declarations that have some smartcast (or can have)
|
||||||
@@ -209,6 +217,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
enterAnonymousFunction(function)
|
enterAnonymousFunction(function)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// All non-lambda function are treated as concurrent since we do not make any assumption about when and how it's invoked.
|
||||||
|
getOrCreateLocalVariableAssignmentAnalyzer(function)?.enterLocalFunction(function)
|
||||||
|
|
||||||
val (functionEnterNode, localFunctionNode, previousNode) = graphBuilder.enterFunction(function)
|
val (functionEnterNode, localFunctionNode, previousNode) = graphBuilder.enterFunction(function)
|
||||||
localFunctionNode?.mergeIncomingFlow()
|
localFunctionNode?.mergeIncomingFlow()
|
||||||
functionEnterNode.mergeIncomingFlow(shouldForkFlow = previousNode != null)
|
functionEnterNode.mergeIncomingFlow(shouldForkFlow = previousNode != null)
|
||||||
@@ -218,6 +229,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
if (function is FirAnonymousFunction) {
|
if (function is FirAnonymousFunction) {
|
||||||
return exitAnonymousFunction(function)
|
return exitAnonymousFunction(function)
|
||||||
}
|
}
|
||||||
|
// All non-lambda function are treated as concurrent since we do not make any assumption about when and how it's invoked.
|
||||||
|
getOrCreateLocalVariableAssignmentAnalyzer(function)?.exitLocalFunction(function)
|
||||||
|
|
||||||
val (node, graph) = graphBuilder.exitFunction(function)
|
val (node, graph) = graphBuilder.exitFunction(function)
|
||||||
node.mergeIncomingFlow()
|
node.mergeIncomingFlow()
|
||||||
if (!graphBuilder.isTopLevel()) {
|
if (!graphBuilder.isTopLevel()) {
|
||||||
@@ -237,6 +251,10 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
// ----------------------------------- Anonymous function -----------------------------------
|
// ----------------------------------- Anonymous function -----------------------------------
|
||||||
|
|
||||||
private fun enterAnonymousFunction(anonymousFunction: FirAnonymousFunction) {
|
private fun enterAnonymousFunction(anonymousFunction: FirAnonymousFunction) {
|
||||||
|
getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.apply {
|
||||||
|
finishPostponedAnonymousFunction()
|
||||||
|
enterLocalFunction(anonymousFunction)
|
||||||
|
}
|
||||||
val (postponedLambdaEnterNode, functionEnterNode) = graphBuilder.enterAnonymousFunction(anonymousFunction)
|
val (postponedLambdaEnterNode, functionEnterNode) = graphBuilder.enterAnonymousFunction(anonymousFunction)
|
||||||
// TODO: questionable
|
// TODO: questionable
|
||||||
postponedLambdaEnterNode?.mergeIncomingFlow()
|
postponedLambdaEnterNode?.mergeIncomingFlow()
|
||||||
@@ -244,18 +262,20 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): FirControlFlowGraphReference {
|
private fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): FirControlFlowGraphReference {
|
||||||
|
getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.exitLocalFunction(
|
||||||
|
anonymousFunction
|
||||||
|
)
|
||||||
val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(anonymousFunction)
|
val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(anonymousFunction)
|
||||||
// TODO: questionable
|
// TODO: questionable
|
||||||
postponedLambdaExitNode?.mergeIncomingFlow()
|
postponedLambdaExitNode?.mergeIncomingFlow()
|
||||||
functionExitNode.mergeIncomingFlow()
|
functionExitNode.mergeIncomingFlow()
|
||||||
exitCapturingStatement(anonymousFunction)
|
|
||||||
return FirControlFlowGraphReferenceImpl(graph)
|
return FirControlFlowGraphReferenceImpl(graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visitPostponedAnonymousFunction(anonymousFunction: FirAnonymousFunction) {
|
fun visitPostponedAnonymousFunction(anonymousFunction: FirAnonymousFunction) {
|
||||||
|
getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.visitPostponedAnonymousFunction(anonymousFunction)
|
||||||
val (enterNode, exitNode) = graphBuilder.visitPostponedAnonymousFunction(anonymousFunction)
|
val (enterNode, exitNode) = graphBuilder.visitPostponedAnonymousFunction(anonymousFunction)
|
||||||
enterNode.mergeIncomingFlow()
|
enterNode.mergeIncomingFlow()
|
||||||
enterCapturingStatement(enterNode, anonymousFunction)
|
|
||||||
exitNode.mergeIncomingFlow()
|
exitNode.mergeIncomingFlow()
|
||||||
enterNode.flow = enterNode.flow.fork()
|
enterNode.flow = enterNode.flow.fork()
|
||||||
}
|
}
|
||||||
@@ -865,8 +885,19 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
graphBuilder.enterCall()
|
graphBuilder.enterCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun enterFunctionCall(functionCall: FirFunctionCall) {
|
||||||
|
val lambdaArgs = functionCall.arguments.filterIsInstance<FirAnonymousFunction>()
|
||||||
|
if (lambdaArgs.size > 1) {
|
||||||
|
getOrCreateLocalVariableAssignmentAnalyzer(lambdaArgs.first())?.enterFunctionCallWithMultipleLambdaArgs(lambdaArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(PrivateForInline::class)
|
@OptIn(PrivateForInline::class)
|
||||||
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) {
|
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) {
|
||||||
|
val lambdaArgs = functionCall.arguments.filterIsInstance<FirAnonymousFunction>()
|
||||||
|
if (lambdaArgs.size > 1) {
|
||||||
|
getOrCreateLocalVariableAssignmentAnalyzer(lambdaArgs.first())?.enterFunctionCallWithMultipleLambdaArgs(lambdaArgs)
|
||||||
|
}
|
||||||
if (ignoreFunctionCalls) {
|
if (ignoreFunctionCalls) {
|
||||||
graphBuilder.exitIgnoredCall(functionCall)
|
graphBuilder.exitIgnoredCall(functionCall)
|
||||||
return
|
return
|
||||||
@@ -1025,9 +1056,12 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
|
|
||||||
variableStorage.getOrCreateRealVariable(flow, initializer.symbol, initializer)
|
variableStorage.getOrCreateRealVariable(flow, initializer.symbol, initializer)
|
||||||
?.let { initializerVariable ->
|
?.let { initializerVariable ->
|
||||||
// TODO: handle capture variable
|
val isInitializerStable = initializerVariable.stability == PropertyStability.STABLE_VALUE ||
|
||||||
if ((initializerVariable.stability == PropertyStability.STABLE_VALUE || initializerVariable.stability == PropertyStability.LOCAL_VAR) &&
|
(initializerVariable.stability == PropertyStability.LOCAL_VAR &&
|
||||||
(propertyVariable.stability == PropertyStability.STABLE_VALUE || propertyVariable.stability == PropertyStability.LOCAL_VAR)
|
initializer is FirQualifiedAccessExpression &&
|
||||||
|
!isAccessToUnstableLocalVariable(initializer))
|
||||||
|
|
||||||
|
if (isInitializerStable && (propertyVariable.stability == PropertyStability.STABLE_VALUE || propertyVariable.stability == PropertyStability.LOCAL_VAR)
|
||||||
) {
|
) {
|
||||||
logicSystem.addLocalVariableAlias(
|
logicSystem.addLocalVariableAlias(
|
||||||
flow, propertyVariable,
|
flow, propertyVariable,
|
||||||
@@ -1293,6 +1327,16 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
|
|
||||||
// ------------------------------------------------------ Utils ------------------------------------------------------
|
// ------------------------------------------------------ Utils ------------------------------------------------------
|
||||||
|
|
||||||
|
private fun getOrCreateLocalVariableAssignmentAnalyzer(firFunction: FirFunction<*>): FirLocalVariableAssignmentAnalyzer? {
|
||||||
|
// Only return analyzer for nested functions so that we won't waste time on functions that don't contain any lambda or local
|
||||||
|
// function.
|
||||||
|
val rootFunction = components.containingDeclarations.firstIsInstanceOrNull<FirFunction<*>>() ?: return null
|
||||||
|
if (rootFunction == firFunction) return null
|
||||||
|
return context.firLocalVariableAssignmentAnalyzer ?: FirLocalVariableAssignmentAnalyzer.analyzeFunction(rootFunction).also {
|
||||||
|
context.firLocalVariableAssignmentAnalyzer = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var CFGNode<*>.flow: FLOW
|
private var CFGNode<*>.flow: FLOW
|
||||||
get() = context.flowOnNodes.getValue(this.origin)
|
get() = context.flowOnNodes.getValue(this.origin)
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|||||||
+359
@@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.fir.resolve.dfa
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||||
|
import org.jetbrains.kotlin.fir.FirElement
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.fir.references.FirNamedReference
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.FirLocalVariableAssignmentAnalyzer.Companion.MiniFlow.Companion.join
|
||||||
|
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper that checks if an access to a local variable access is stable.
|
||||||
|
*
|
||||||
|
* To determine the stability of an access, call [isAccessToUnstableLocalVariable]. Note that the class contains mutable states. So
|
||||||
|
* [isAccessToUnstableLocalVariable] only works for an access during the natural FIR tree traversal. This class will not work if one
|
||||||
|
* queries after the traversal is done.
|
||||||
|
**/
|
||||||
|
internal class FirLocalVariableAssignmentAnalyzer(
|
||||||
|
private val assignedLocalVariablesByFunction: Map<FirFunction<*>, AssignedLocalVariables>
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Stack storing concurrent lambda arguments for the current visited anonymous function. For example
|
||||||
|
* ```
|
||||||
|
* callWithMultipleLambdaExactlyOnceEach(
|
||||||
|
* l1 = { x.length },
|
||||||
|
* l2 = { x = null }
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
* From the call, it's nondeterministic whether `l1` runs before `l2` or vice versa. So when handling `l1`, we must mark all variables
|
||||||
|
* touched in `l2` unstable.
|
||||||
|
*/
|
||||||
|
private val concurrentLambdaArgsStack: MutableList<Set<FirAnonymousFunction>> = mutableListOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack whose element tracks all concurrently modified variables in execution paths other than this one. It's a stack because after
|
||||||
|
* exiting a local function, we must restore the variables to the state before entering this local function. Initially, there is an
|
||||||
|
* empty set for the root function when we starts the analysis.
|
||||||
|
*/
|
||||||
|
private val concurrentlyAssignedLocalVariablesStack: MutableList<MutableSet<FirProperty>> = mutableListOf(mutableSetOf())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary storage that tracks concurrently modified variables during function call resolution. For example, consider the following,
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* foo(bar { x= null }, x.length)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Sometimes during resolution, when stability of `x` is retrieved with [isAccessToUnstableLocalVariable], the resolution of `foo` and
|
||||||
|
* `bar` is not yet finished. Hence, the lambda arg passed to `bar` is not traversed. In this case, the resolution logic first calls
|
||||||
|
* [visitPostponedAnonymousFunction], then [isAccessToUnstableLocalVariable]. Next, it calls [enterLocalFunction] and starts traversing
|
||||||
|
* the lambda passed to `bar`.
|
||||||
|
*/
|
||||||
|
private val ephemeralConcurrentlyAssignedLocalVariables: MutableSet<FirProperty> = mutableSetOf()
|
||||||
|
|
||||||
|
/** Checks whether the given access is an unstable access to a local variable at this moment. */
|
||||||
|
fun isAccessToUnstableLocalVariable(qualifiedAccessExpression: FirQualifiedAccessExpression): Boolean {
|
||||||
|
val property = qualifiedAccessExpression.referredPropertySymbol?.fir ?: return false
|
||||||
|
return property in ephemeralConcurrentlyAssignedLocalVariables || property in concurrentlyAssignedLocalVariablesStack.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visitPostponedAnonymousFunction(anonymousFunction: FirAnonymousFunction) {
|
||||||
|
// Postponed anonymous function is visited before the current function call with lambda is resolved. Hence, the invocationKind is
|
||||||
|
// always null and hence there is no need to check it. In addition, since multiple lambda can be passed, we accumulate the
|
||||||
|
// effects by appending to `ephemeralConcurrentlyAssignedLocalVariables`. After the function call is resolved,
|
||||||
|
// `exitAnonymousFunction` will be invoked at some point to properly set up the `persistentConcurrentlyAssignedLocalVariables`.
|
||||||
|
assignedLocalVariablesByFunction[anonymousFunction]?.insideLocalFunction?.let {
|
||||||
|
ephemeralConcurrentlyAssignedLocalVariables.addAll(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finishPostponedAnonymousFunction() {
|
||||||
|
// Clear the temporarily assigned local variables in `visitPostponedAnonymousFunction`.
|
||||||
|
ephemeralConcurrentlyAssignedLocalVariables.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enterLocalFunction(function: FirFunction<*>) {
|
||||||
|
val eventOccurrencesRange: EventOccurrencesRange? = (function as? FirAnonymousFunction)?.invocationKind
|
||||||
|
// carry on concurrently modified variables from the current scope
|
||||||
|
val concurrentlyAssignedLocalVariables = concurrentlyAssignedLocalVariablesStack.last().toMutableSet()
|
||||||
|
concurrentlyAssignedLocalVariablesStack.add(concurrentlyAssignedLocalVariables)
|
||||||
|
val concurrentLambdasInCurrentCall = concurrentLambdaArgsStack.lastOrNull()
|
||||||
|
if (concurrentLambdasInCurrentCall != null && function in concurrentLambdasInCurrentCall) {
|
||||||
|
concurrentLambdasInCurrentCall.filter { it != function }.forEach { otherLambda ->
|
||||||
|
assignedLocalVariablesByFunction[otherLambda]?.insideLocalFunction?.let {
|
||||||
|
concurrentlyAssignedLocalVariables += it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (eventOccurrencesRange) {
|
||||||
|
EventOccurrencesRange.AT_LEAST_ONCE,
|
||||||
|
EventOccurrencesRange.MORE_THAN_ONCE -> assignedLocalVariablesByFunction[function]?.insideLocalFunction?.let {
|
||||||
|
concurrentlyAssignedLocalVariables += it
|
||||||
|
}
|
||||||
|
// Add both inside and outside since this local function may be invoked multiple times concurrently.
|
||||||
|
EventOccurrencesRange.UNKNOWN, null -> assignedLocalVariablesByFunction[function]?.all?.let {
|
||||||
|
concurrentlyAssignedLocalVariables += it
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// no additional stuff to do for other cases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exitLocalFunction(function: FirFunction<*>) {
|
||||||
|
val eventOccurrencesRange: EventOccurrencesRange? = (function as? FirAnonymousFunction)?.invocationKind
|
||||||
|
concurrentlyAssignedLocalVariablesStack.removeLast()
|
||||||
|
when (eventOccurrencesRange) {
|
||||||
|
EventOccurrencesRange.UNKNOWN, null -> assignedLocalVariablesByFunction[function]?.insideLocalFunction?.let {
|
||||||
|
concurrentlyAssignedLocalVariablesStack.last() += it
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// no additional stuff to do for other cases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enterFunctionCallWithMultipleLambdaArgs(lambdaArgs: List<FirAnonymousFunction>) {
|
||||||
|
concurrentLambdaArgsStack.add(lambdaArgs.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exitFunctionCallWithMultipleLambdaArgs() {
|
||||||
|
concurrentLambdaArgsStack.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Computes assigned local variables in each execution path. This analyzer runs before BODY_RESOLVE. Hence, it works on
|
||||||
|
* syntactical information only.
|
||||||
|
*
|
||||||
|
* # Note on implementation detail
|
||||||
|
*
|
||||||
|
* The analyzer constructs a mini control flow graph that captures forking of execution path. Any conditional branches, declaration of
|
||||||
|
* lambda and local functions are forks. The mini CFG does not care about loop structures and effectively treats it as a linear sequence of
|
||||||
|
* statements. This is sufficient for the purpose of collecting unstable local variables. Similarly for try/catch/finally constructs.
|
||||||
|
*
|
||||||
|
* Also, for simplicity, all conditionals are treated as non-exhaustive. Hence, a fallback edge is always added along a conditional
|
||||||
|
* structure.
|
||||||
|
*
|
||||||
|
* While building the mini CFG, inside each node, we collect local variables that are assigned later in the execution path.
|
||||||
|
*
|
||||||
|
* For example, consider the following code.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* fun test() {
|
||||||
|
* var x: Int = 0
|
||||||
|
* var y: Int = 0
|
||||||
|
* var z: Int = 0
|
||||||
|
* if (true) {
|
||||||
|
* run {
|
||||||
|
* x = 1
|
||||||
|
* var a = 1
|
||||||
|
* a = 2
|
||||||
|
* }
|
||||||
|
* } else {
|
||||||
|
* x = 2
|
||||||
|
* y = 2
|
||||||
|
* }
|
||||||
|
* z = 3
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The generated mini CFG looks like the following, with assigned local variables annotated after each node in curly brackets.
|
||||||
|
*
|
||||||
|
* ┌───────┐
|
||||||
|
* │ if │ {x y z a}
|
||||||
|
* └─┬─┬─┬─┘
|
||||||
|
* │ │ │ fallback
|
||||||
|
* │ │ └──────────────────────────────────────┐
|
||||||
|
* │ │ false │
|
||||||
|
* │ └─────────────────────────┐ │
|
||||||
|
* │ true │ │
|
||||||
|
* ┌─┴─────┐ ┌───┴────┐ │
|
||||||
|
* │ run │ {x z a} │ else │ {x y z} │
|
||||||
|
* │ │ │ branch │ │
|
||||||
|
* └─┬───┬─┘ └───┬────┘ │
|
||||||
|
* │ │ normal execution │ │
|
||||||
|
* │ └─────────────┐ │ │
|
||||||
|
* │ lambda arg │ │ │
|
||||||
|
* ┌─┴──────┐ ┌───┴───┐ │ │
|
||||||
|
* │ lambda │ {x} │ empty │ {z} │ │
|
||||||
|
* │ body │ │ │ │ │
|
||||||
|
* └────────┘ └───┬───┘ │ │
|
||||||
|
* ┌─────────────────┘ │ │
|
||||||
|
* │ ┌─────────────────────────┘ │
|
||||||
|
* │ │ ┌──────────────────────────────────────┘
|
||||||
|
* ┌─┴─┴─┴─┐
|
||||||
|
* │ after │ {z}
|
||||||
|
* │ if │
|
||||||
|
* └───────┘
|
||||||
|
*
|
||||||
|
* Some notes on why each node contains what it contains:
|
||||||
|
*
|
||||||
|
* - changes to `z` is captured and back-propagated to all earlier nodes as desired.
|
||||||
|
*
|
||||||
|
* - "lambda body" node does not contain `a` because `a` is declared inside the function. Such declarations are removed in
|
||||||
|
* [MiniCfgBuilder.handleFunctionFork] after the lambda function is processed. However, the parent nodes contain `a` because
|
||||||
|
* [MiniCfgBuilder.recordAssignment] propagates `a` during traversal. The extra `a` won't do any harm since `a` can never be
|
||||||
|
* referenced outside the lambda. It's possible to track the scope at each node and remove the unneeded `a` in "if" and "run"
|
||||||
|
* nodes. But doing that seems to be more expensive than simply letting it propagate.
|
||||||
|
*
|
||||||
|
* - "run" node does not contain `y` as desired since the if true and false branches are mutually exclusive.
|
||||||
|
*
|
||||||
|
* By the way, since local variables are not resolved at this point, we manually track local variable declarations and resolve them along
|
||||||
|
* the way so that shadowed names are handled correctly.
|
||||||
|
*/
|
||||||
|
fun analyzeFunction(rootFunction: FirFunction<*>): FirLocalVariableAssignmentAnalyzer {
|
||||||
|
return FirLocalVariableAssignmentAnalyzer(computeAssignedLocalVariables(rootFunction))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes a mini CFG and returns the map tracking assigned local variables at each potentially concurrent local/lambda function.
|
||||||
|
*/
|
||||||
|
private fun computeAssignedLocalVariables(firFunction: FirFunction<*>): Map<FirFunction<*>, AssignedLocalVariables> {
|
||||||
|
val startFlow = MiniFlow.start()
|
||||||
|
val data = MiniCfgBuilder.MiniCfgData(startFlow)
|
||||||
|
MiniCfgBuilder().visitElement(firFunction, data)
|
||||||
|
return data.localFunctionToAssignedLocalVariables
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssignedLocalVariables(val outsideLocalFunction: Set<FirProperty>, val insideLocalFunction: Set<FirProperty>) {
|
||||||
|
val all get() = outsideLocalFunction + insideLocalFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MiniFlow(val parents: Set<MiniFlow>) {
|
||||||
|
val assignedLocalVariables: MutableSet<FirProperty> = mutableSetOf()
|
||||||
|
|
||||||
|
fun fork(): MiniFlow = MiniFlow(setOf(this))
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun start() = MiniFlow(emptySet())
|
||||||
|
fun Set<MiniFlow>.join(): MiniFlow = MiniFlow(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class MiniCfgBuilder : FirVisitor<Unit, MiniCfgBuilder.MiniCfgData>() {
|
||||||
|
override fun visitElement(element: FirElement, data: MiniCfgData) {
|
||||||
|
element.acceptChildren(this, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: MiniCfgData) {
|
||||||
|
handleFunctionFork(anonymousFunction, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: MiniCfgData) {
|
||||||
|
handleFunctionFork(simpleFunction, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFunctionFork(function: FirFunction<*>, data: MiniCfgData) {
|
||||||
|
val currentFlow = data.flow ?: return
|
||||||
|
val functionFork = currentFlow.fork()
|
||||||
|
data.flow = functionFork
|
||||||
|
function.acceptChildren(this, data)
|
||||||
|
|
||||||
|
// Only retain local variables declared above the current scope. This way, any local variables declared inside the
|
||||||
|
// function will effectively be treated as distinct variables and, hence, stable (Of course, for nested lambda, things would
|
||||||
|
// just work because inside the lambda assigned local variables are tracked by different nodes).
|
||||||
|
functionFork.assignedLocalVariables.retainAll(data.variableDeclarations.flatMap { it.values })
|
||||||
|
// Create another fork for the normal execution
|
||||||
|
val normalExecution = currentFlow.fork()
|
||||||
|
data.localFunctionToAssignedLocalVariables[function] =
|
||||||
|
AssignedLocalVariables(normalExecution.assignedLocalVariables, functionFork.assignedLocalVariables)
|
||||||
|
data.flow = normalExecution
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitWhenExpression(whenExpression: FirWhenExpression, data: MiniCfgData) {
|
||||||
|
val flow = data.flow ?: return
|
||||||
|
val visitor = this
|
||||||
|
with(whenExpression) {
|
||||||
|
calleeReference.accept(visitor, data)
|
||||||
|
val subjectVariable = this.subjectVariable
|
||||||
|
if (subjectVariable != null) {
|
||||||
|
subjectVariable.accept(visitor, data)
|
||||||
|
} else {
|
||||||
|
subject?.accept(visitor, data)
|
||||||
|
}
|
||||||
|
val childFlows = branches.mapNotNull {
|
||||||
|
data.flow = flow.fork()
|
||||||
|
it.accept(visitor, data)
|
||||||
|
data.flow
|
||||||
|
}
|
||||||
|
// Also collect `flow` here for the synthetic fallback flow when none of the branch executes.
|
||||||
|
data.flow = (childFlows + flow).toSet().join()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitReturnExpression(returnExpression: FirReturnExpression, data: MiniCfgData) {
|
||||||
|
// TODO: consider to also handle `throw`, which would require keeping track of all `try`, `catch` and `finally` constructs.
|
||||||
|
data.flow = null
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
override fun visitFunctionCall(functionCall: FirFunctionCall, data: MiniCfgData) {
|
||||||
|
val visitor = this
|
||||||
|
with(functionCall) {
|
||||||
|
setOfNotNull(explicitReceiver, dispatchReceiver, extensionReceiver).forEach { it.accept(visitor, data) }
|
||||||
|
// Delay processing of lambda args because lambda body are evaluated after all arguments have been evaluated.
|
||||||
|
val (postponedFunctionArgs, normalArgs) = argumentList.arguments.partition { it is FirAnonymousFunction }
|
||||||
|
normalArgs.forEach { it.accept(visitor, data) }
|
||||||
|
postponedFunctionArgs.forEach { it.accept(visitor, data) }
|
||||||
|
calleeReference.accept(visitor, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitBlock(block: FirBlock, data: MiniCfgData) {
|
||||||
|
data.variableDeclarations.addLast(mutableMapOf())
|
||||||
|
super.visitBlock(block, data)
|
||||||
|
data.variableDeclarations.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitProperty(property: FirProperty, data: MiniCfgData) {
|
||||||
|
if (property.isLocal) {
|
||||||
|
data.variableDeclarations.last()[property.name] = property
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitVariableAssignment(variableAssignment: FirVariableAssignment, data: MiniCfgData) {
|
||||||
|
val flow = data.flow ?: return
|
||||||
|
val name = (variableAssignment.lValue as? FirNamedReference)?.name ?: return
|
||||||
|
flow.recordAssignment(name, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitAssignmentOperatorStatement(assignmentOperatorStatement: FirAssignmentOperatorStatement, data: MiniCfgData) {
|
||||||
|
val flow = data.flow ?: return
|
||||||
|
val lhs = assignmentOperatorStatement.leftArgument as? FirQualifiedAccessExpression ?: return
|
||||||
|
if (lhs.explicitReceiver != null) return
|
||||||
|
val name = (lhs.calleeReference as? FirNamedReference)?.name ?: return
|
||||||
|
flow.recordAssignment(name, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MiniFlow.recordAssignment(name: Name, data: MiniCfgData) {
|
||||||
|
val property = data.resolveLocalVariable(name) ?: return
|
||||||
|
recordAssignment(property, mutableSetOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MiniFlow.recordAssignment(property: FirProperty, visited: MutableSet<MiniFlow>) {
|
||||||
|
if (this in visited) return
|
||||||
|
visited += this
|
||||||
|
assignedLocalVariables += property
|
||||||
|
// Back-propagate the assignment to all parent flows.
|
||||||
|
parents.forEach { it.recordAssignment(property, visited) }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MiniCfgData(var flow: MiniFlow?) {
|
||||||
|
val variableDeclarations: ArrayDeque<MutableMap<Name, FirProperty>> = ArrayDeque(listOf(mutableMapOf()))
|
||||||
|
val localFunctionToAssignedLocalVariables: MutableMap<FirFunction<*>, AssignedLocalVariables> = mutableMapOf()
|
||||||
|
fun resolveLocalVariable(name: Name): FirProperty? {
|
||||||
|
return variableDeclarations.asReversed().firstNotNullOfOrNull { it[name] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -283,6 +283,7 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
|
|||||||
functionCall.transformAnnotations(transformer, data)
|
functionCall.transformAnnotations(transformer, data)
|
||||||
functionCall.transformSingle(InvocationKindTransformer, null)
|
functionCall.transformSingle(InvocationKindTransformer, null)
|
||||||
functionCall.transformTypeArguments(transformer, ResolutionMode.ContextIndependent)
|
functionCall.transformTypeArguments(transformer, ResolutionMode.ContextIndependent)
|
||||||
|
dataFlowAnalyzer.enterFunctionCall(functionCall)
|
||||||
val (completeInference, callCompleted) =
|
val (completeInference, callCompleted) =
|
||||||
try {
|
try {
|
||||||
val initialExplicitReceiver = functionCall.explicitReceiver
|
val initialExplicitReceiver = functionCall.explicitReceiver
|
||||||
@@ -421,12 +422,14 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
|
|||||||
val lhsIsVar = lhsVariable?.isVar == true
|
val lhsIsVar = lhsVariable?.isVar == true
|
||||||
|
|
||||||
fun chooseAssign(): FirStatement {
|
fun chooseAssign(): FirStatement {
|
||||||
|
dataFlowAnalyzer.enterFunctionCall(resolvedAssignCall)
|
||||||
callCompleter.completeCall(resolvedAssignCall, noExpectedType)
|
callCompleter.completeCall(resolvedAssignCall, noExpectedType)
|
||||||
dataFlowAnalyzer.exitFunctionCall(resolvedAssignCall, callCompleted = true)
|
dataFlowAnalyzer.exitFunctionCall(resolvedAssignCall, callCompleted = true)
|
||||||
return resolvedAssignCall
|
return resolvedAssignCall
|
||||||
}
|
}
|
||||||
|
|
||||||
fun chooseOperator(): FirStatement {
|
fun chooseOperator(): FirStatement {
|
||||||
|
dataFlowAnalyzer.enterFunctionCall(resolvedAssignCall)
|
||||||
callCompleter.completeCall(
|
callCompleter.completeCall(
|
||||||
resolvedOperatorCall,
|
resolvedOperatorCall,
|
||||||
lhsVariable?.returnTypeRef ?: noExpectedType,
|
lhsVariable?.returnTypeRef ?: noExpectedType,
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ inline val FirPropertyAccessor.allowsToHaveFakeOverride: Boolean
|
|||||||
get() = !Visibilities.isPrivate(visibility) && visibility != Visibilities.InvisibleFake
|
get() = !Visibilities.isPrivate(visibility) && visibility != Visibilities.InvisibleFake
|
||||||
|
|
||||||
inline val FirClassLikeDeclaration<*>.isLocal get() = symbol.classId.isLocal
|
inline val FirClassLikeDeclaration<*>.isLocal get() = symbol.classId.isLocal
|
||||||
inline val FirSimpleFunction.isLocal get() = status.visibility == Visibilities.Local
|
inline val FirCallableMemberDeclaration<*>.isLocal get() = status.visibility == Visibilities.Local
|
||||||
|
|
||||||
fun FirRegularClassBuilder.addDeclaration(declaration: FirDeclaration) {
|
fun FirRegularClassBuilder.addDeclaration(declaration: FirDeclaration) {
|
||||||
declarations += declaration
|
declarations += declaration
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ fun use() {
|
|||||||
// Write to x is AFTER
|
// Write to x is AFTER
|
||||||
x.hashCode()
|
x.hashCode()
|
||||||
// No smart cast should be here!
|
// No smart cast should be here!
|
||||||
foo(bar { x = null }, x<!UNSAFE_CALL!>.<!>hashCode())
|
foo(bar { x = null }, <!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode())
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ fun foo(arg: Int?) {
|
|||||||
var x = arg
|
var x = arg
|
||||||
if (x == null) return
|
if (x == null) return
|
||||||
run {
|
run {
|
||||||
// Not safe: x = null later in the owner
|
// Safe: since `run` is in-place
|
||||||
x.hashCode()
|
x.hashCode()
|
||||||
}
|
}
|
||||||
x = null
|
x = null
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ fun foo(arg: Int?) {
|
|||||||
var x = arg
|
var x = arg
|
||||||
if (x == null) return
|
if (x == null) return
|
||||||
run {
|
run {
|
||||||
// Not safe: x = null later in the owner
|
// Safe: since `run` is in-place
|
||||||
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
||||||
}
|
}
|
||||||
x = null
|
x = null
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
fun trans(n: Int, f: () -> Boolean) = if (f()) n else null
|
|
||||||
|
|
||||||
fun foo() {
|
|
||||||
var i: Int? = 5
|
|
||||||
if (i != null) {
|
|
||||||
class Changing {
|
|
||||||
fun bar() {
|
|
||||||
i = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i.hashCode()
|
|
||||||
Changing().bar()
|
|
||||||
i.hashCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
fun trans(n: Int, f: () -> Boolean) = if (f()) n else null
|
fun trans(n: Int, f: () -> Boolean) = if (f()) n else null
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
fun trans(n: Int, f: () -> Boolean) = if (f()) n else null
|
|
||||||
|
|
||||||
fun foo() {
|
|
||||||
var i: Int? = 5
|
|
||||||
if (i != null) {
|
|
||||||
fun can(): Boolean {
|
|
||||||
i = null
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
i.hashCode()
|
|
||||||
trans(i, ::can)
|
|
||||||
i.hashCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
fun trans(n: Int, f: () -> Boolean) = if (f()) n else null
|
fun trans(n: Int, f: () -> Boolean) = if (f()) n else null
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ fun foo() {
|
|||||||
i = null
|
i = null
|
||||||
}
|
}
|
||||||
}.bar()
|
}.bar()
|
||||||
i.hashCode()
|
<!SMARTCAST_IMPOSSIBLE!>i<!>.hashCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -3,8 +3,8 @@ fun foo(arg: Int?) {
|
|||||||
var x = arg
|
var x = arg
|
||||||
if (x == null) return
|
if (x == null) return
|
||||||
run {
|
run {
|
||||||
// Unsafe because of owner modification
|
// Stable because `run` is in-place
|
||||||
x<!UNSAFE_CALL!>.<!>hashCode()
|
x.hashCode()
|
||||||
x = null
|
x = null
|
||||||
}
|
}
|
||||||
if (x != null) x = 42
|
if (x != null) x = 42
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ fun foo(arg: Int?) {
|
|||||||
var x = arg
|
var x = arg
|
||||||
if (x == null) return
|
if (x == null) return
|
||||||
run {
|
run {
|
||||||
// Unsafe because of owner modification
|
// Stable because `run` is in-place
|
||||||
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
||||||
x = null
|
x = null
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
class My {
|
class My {
|
||||||
|
|
||||||
val y: Int
|
val y: Int
|
||||||
get() {
|
get() {
|
||||||
var x: Int?
|
var x: Int?
|
||||||
x = 3
|
x = 3
|
||||||
|
|||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
import kotlin.contracts.*
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||||
|
inline fun atLeastOnce(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.AT_LEAST_ONCE)
|
||||||
|
}
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||||
|
inline fun atMostOnce(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
|
||||||
|
}
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||||
|
inline fun exactlyOnce(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
var s: String? = null
|
||||||
|
s = ""
|
||||||
|
atLeastOnce {
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>s<!>.length // unstable since lambda can be called twice
|
||||||
|
s = null
|
||||||
|
var s2: String? = null
|
||||||
|
s2 = ""
|
||||||
|
s2.length // local variable declared inside lambda is stable
|
||||||
|
s2 = null
|
||||||
|
}
|
||||||
|
s = ""
|
||||||
|
exactlyOnce {
|
||||||
|
s.length // stable since lambda can be called only once
|
||||||
|
s = null
|
||||||
|
var s2: String? = null
|
||||||
|
s2 = ""
|
||||||
|
s2.length // local variable declared inside lambda is stable
|
||||||
|
s2 = null
|
||||||
|
}
|
||||||
|
s = ""
|
||||||
|
atMostOnce {
|
||||||
|
s.length // stable since lambda can be called at most once
|
||||||
|
s = null
|
||||||
|
var s2: String? = null
|
||||||
|
s2 = ""
|
||||||
|
s2.length // local variable declared inside lambda is stable
|
||||||
|
s2 = null
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
import kotlin.contracts.*
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||||
|
inline fun atLeastOnce(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.AT_LEAST_ONCE)
|
||||||
|
}
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||||
|
inline fun atMostOnce(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
|
||||||
|
}
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||||
|
inline fun exactlyOnce(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
var s: String? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>null<!>
|
||||||
|
s = ""
|
||||||
|
atLeastOnce {
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>s<!>.length // unstable since lambda can be called twice
|
||||||
|
s = null
|
||||||
|
var s2: String? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>null<!>
|
||||||
|
s2 = ""
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>s2<!>.length // local variable declared inside lambda is stable
|
||||||
|
s2 = null
|
||||||
|
}
|
||||||
|
s = ""
|
||||||
|
exactlyOnce {
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>s<!>.length // stable since lambda can be called only once
|
||||||
|
s = null
|
||||||
|
var s2: String? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>null<!>
|
||||||
|
s2 = ""
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>s2<!>.length // local variable declared inside lambda is stable
|
||||||
|
s2 = null
|
||||||
|
}
|
||||||
|
s = ""
|
||||||
|
atMostOnce {
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>s<!>.length // stable since lambda can be called at most once
|
||||||
|
s = null
|
||||||
|
var s2: String? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>null<!>
|
||||||
|
s2 = ""
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>s2<!>.length // local variable declared inside lambda is stable
|
||||||
|
s2 = null
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
@kotlin.Suppress(names = {"EXPERIMENTAL_API_USAGE_ERROR"}) public inline fun atLeastOnce(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
|
||||||
|
CallsInPlace(block, AT_LEAST_ONCE)
|
||||||
|
|
||||||
|
@kotlin.Suppress(names = {"EXPERIMENTAL_API_USAGE_ERROR"}) public inline fun atMostOnce(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
|
||||||
|
CallsInPlace(block, AT_MOST_ONCE)
|
||||||
|
|
||||||
|
@kotlin.Suppress(names = {"EXPERIMENTAL_API_USAGE_ERROR"}) public inline fun exactlyOnce(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
|
||||||
|
CallsInPlace(block, EXACTLY_ONCE)
|
||||||
|
|
||||||
|
public fun test(): kotlin.Unit
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import kotlin.contracts.*
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||||
|
fun foo(f1: () -> Unit, f2: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(f1, InvocationKind.EXACTLY_ONCE)
|
||||||
|
callsInPlace(f2, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
f2()
|
||||||
|
f1()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
var s: String? = null
|
||||||
|
s = ""
|
||||||
|
foo(
|
||||||
|
{ <!SMARTCAST_IMPOSSIBLE!>s<!>.length }, // unstable since lambda evaluation order is indeterministic
|
||||||
|
{ s = null },
|
||||||
|
)
|
||||||
|
s = ""
|
||||||
|
foo(
|
||||||
|
{ s = null },
|
||||||
|
{ <!SMARTCAST_IMPOSSIBLE!>s<!>.length }, // unstable since lambda evaluation order is indeterministic
|
||||||
|
)
|
||||||
|
s = ""
|
||||||
|
foo(
|
||||||
|
{ s.length }, // stable
|
||||||
|
{ s.length }, // stable
|
||||||
|
)
|
||||||
|
s = null
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import kotlin.contracts.*
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||||
|
fun foo(f1: () -> Unit, f2: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(f1, InvocationKind.EXACTLY_ONCE)
|
||||||
|
callsInPlace(f2, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
f2()
|
||||||
|
f1()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
var s: String? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>null<!>
|
||||||
|
s = ""
|
||||||
|
foo(
|
||||||
|
{ <!SMARTCAST_IMPOSSIBLE!>s<!>.length }, // unstable since lambda evaluation order is indeterministic
|
||||||
|
{ s = null },
|
||||||
|
)
|
||||||
|
s = ""
|
||||||
|
foo(
|
||||||
|
{ s = null },
|
||||||
|
{ <!SMARTCAST_IMPOSSIBLE!>s<!>.length }, // unstable since lambda evaluation order is indeterministic
|
||||||
|
)
|
||||||
|
s = ""
|
||||||
|
foo(
|
||||||
|
{ <!SMARTCAST_IMPOSSIBLE!>s<!>.length }, // stable
|
||||||
|
{ <!SMARTCAST_IMPOSSIBLE!>s<!>.length }, // stable
|
||||||
|
)
|
||||||
|
s = null
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
@kotlin.Suppress(names = {"EXPERIMENTAL_API_USAGE_ERROR"}) public fun foo(/*0*/ f1: () -> kotlin.Unit, /*1*/ f2: () -> kotlin.Unit): kotlin.Unit
|
||||||
|
CallsInPlace(f1, EXACTLY_ONCE)
|
||||||
|
CallsInPlace(f2, EXACTLY_ONCE)
|
||||||
|
|
||||||
|
public fun test(): kotlin.Unit
|
||||||
|
|
||||||
+1
-1
@@ -5,5 +5,5 @@ fun foo(x: Int, f: () -> Unit, y: Int) {}
|
|||||||
fun bar() {
|
fun bar() {
|
||||||
var x: Int?
|
var x: Int?
|
||||||
x = 4
|
x = 4
|
||||||
foo(x, { x = null; x<!UNSAFE_CALL!>.<!>hashCode() }, <!ARGUMENT_TYPE_MISMATCH!>x<!>)
|
foo(x, { x = null; x<!UNSAFE_CALL!>.<!>hashCode() }, <!SMARTCAST_IMPOSSIBLE!>x<!>)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
-5
@@ -27,10 +27,10 @@ fun baz(s: String?) {
|
|||||||
var x = s
|
var x = s
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
run {
|
run {
|
||||||
x.hashCode()
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
x<!UNSAFE_CALL!>.<!>hashCode()
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
||||||
x = null
|
x = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,11 +40,11 @@ fun gaz(s: String?) {
|
|||||||
var x = s
|
var x = s
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
run {
|
run {
|
||||||
x<!UNSAFE_CALL!>.<!>hashCode()
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
||||||
x = null
|
x = null
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
x<!UNSAFE_CALL!>.<!>hashCode()
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ fun gav(s: String?) {
|
|||||||
var x = s
|
var x = s
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
run {
|
run {
|
||||||
x.hashCode()
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.hashCode()
|
||||||
}
|
}
|
||||||
x = null
|
x = null
|
||||||
}
|
}
|
||||||
|
|||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
public fun foo() {
|
|
||||||
var s: String? = ""
|
|
||||||
fun closure(): Int {
|
|
||||||
if (s == "") {
|
|
||||||
s = null
|
|
||||||
return -1
|
|
||||||
} else if (s == null) {
|
|
||||||
return -2
|
|
||||||
} else {
|
|
||||||
return s.length // Here smartcast is possible, at least in principle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (s != null) {
|
|
||||||
System.out.println(closure())
|
|
||||||
System.out.println(s.length) // Here smartcast is not possible due to a closure predecessor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
public fun foo() {
|
public fun foo() {
|
||||||
var s: String? = ""
|
var s: String? = ""
|
||||||
fun closure(): Int {
|
fun closure(): Int {
|
||||||
@@ -14,4 +15,4 @@ public fun foo() {
|
|||||||
System.out.println(closure())
|
System.out.println(closure())
|
||||||
System.out.println(<!SMARTCAST_IMPOSSIBLE!>s<!>.length) // Here smartcast is not possible due to a closure predecessor
|
System.out.println(<!SMARTCAST_IMPOSSIBLE!>s<!>.length) // Here smartcast is not possible due to a closure predecessor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-15
@@ -1,15 +0,0 @@
|
|||||||
// See also KT-7186
|
|
||||||
|
|
||||||
fun IntArray.forEachIndexed( op: (i: Int, value: Int) -> Unit) {
|
|
||||||
for (i in 0..this.size)
|
|
||||||
op(i, this[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
fun max(a: IntArray): Int? {
|
|
||||||
var maxI: Int? = null
|
|
||||||
a.forEachIndexed { i, value ->
|
|
||||||
if (maxI == null || value >= a[maxI])
|
|
||||||
maxI = i
|
|
||||||
}
|
|
||||||
return maxI
|
|
||||||
}
|
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
// See also KT-7186
|
// See also KT-7186
|
||||||
|
|
||||||
fun IntArray.forEachIndexed( op: (i: Int, value: Int) -> Unit) {
|
fun IntArray.forEachIndexed( op: (i: Int, value: Int) -> Unit) {
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// KT-7186: False "Type mismatch" error
|
|
||||||
|
|
||||||
fun indexOfMax(a: IntArray): Int? {
|
|
||||||
var maxI: Int? = null
|
|
||||||
a.forEachIndexed { i, value ->
|
|
||||||
if (maxI == null || value >= a[maxI]) {
|
|
||||||
maxI = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxI
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
// KT-7186: False "Type mismatch" error
|
// KT-7186: False "Type mismatch" error
|
||||||
|
|
||||||
fun indexOfMax(a: IntArray): Int? {
|
fun indexOfMax(a: IntArray): Int? {
|
||||||
|
|||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
fun foo(x: Int, y: Any?, z: Int) {}
|
||||||
|
|
||||||
|
fun myRun(block: () -> Unit): Any? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test_1() {
|
||||||
|
var x: String? = null
|
||||||
|
if (x != null) {
|
||||||
|
foo(
|
||||||
|
x.length, // stable smartcast
|
||||||
|
run { x = "" },
|
||||||
|
x.length // stable smartcast
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test_2() {
|
||||||
|
var x: String? = null
|
||||||
|
if (x != null) {
|
||||||
|
foo(
|
||||||
|
x.length, // stable smartcast
|
||||||
|
myRun { x = "" },
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // unstable smartcast
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test_3() {
|
||||||
|
var x: String? = null
|
||||||
|
if (x != null) {
|
||||||
|
foo(
|
||||||
|
x.length, // stable smartcast
|
||||||
|
{ x = "" },
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // stable smartcast
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
fun foo(x: Int, y: Any?, z: Int) {}
|
||||||
|
|
||||||
|
fun myRun(block: () -> Unit): Any? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test_1() {
|
||||||
|
var x: String? = null
|
||||||
|
if (x != null) {
|
||||||
|
foo(
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>x<!>.length, // stable smartcast
|
||||||
|
run { x = "" },
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // stable smartcast
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test_2() {
|
||||||
|
var x: String? = null
|
||||||
|
if (x != null) {
|
||||||
|
foo(
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>x<!>.length, // stable smartcast
|
||||||
|
myRun { x = "" },
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // unstable smartcast
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test_3() {
|
||||||
|
var x: String? = null
|
||||||
|
if (x != null) {
|
||||||
|
foo(
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>x<!>.length, // stable smartcast
|
||||||
|
{ x = "" },
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // stable smartcast
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun foo(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.Any?, /*2*/ z: kotlin.Int): kotlin.Unit
|
||||||
|
public fun myRun(/*0*/ block: () -> kotlin.Unit): kotlin.Any?
|
||||||
|
public fun test_1(): kotlin.Unit
|
||||||
|
public fun test_2(): kotlin.Unit
|
||||||
|
public fun test_3(): kotlin.Unit
|
||||||
|
|
||||||
Generated
+18
@@ -27600,6 +27600,18 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/assignmentConversion.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/assignmentConversion.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("capturedByAtLeastOnce.kt")
|
||||||
|
public void testCapturedByAtLeastOnce() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/capturedByAtLeastOnce.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("capturedByMultipleLambdas.kt")
|
||||||
|
public void testCapturedByMultipleLambdas() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/capturedByMultipleLambdas.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("doWhileWithMiddleBreak.kt")
|
@TestMetadata("doWhileWithMiddleBreak.kt")
|
||||||
public void testDoWhileWithMiddleBreak() throws Exception {
|
public void testDoWhileWithMiddleBreak() throws Exception {
|
||||||
@@ -35817,6 +35829,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
|
|||||||
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
|
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("lambdaInCallArgs.kt")
|
||||||
|
public void testLambdaInCallArgs() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("lazyDeclaresAndModifies.kt")
|
@TestMetadata("lazyDeclaresAndModifies.kt")
|
||||||
public void testLazyDeclaresAndModifies() throws Exception {
|
public void testLazyDeclaresAndModifies() throws Exception {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ fun case_3(x: Interface1?) {
|
|||||||
var y = x
|
var y = x
|
||||||
y as Interface2
|
y as Interface2
|
||||||
fun foo() {
|
fun foo() {
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("Interface1? & Interface1 & Interface2")!>y<!>.itest2()
|
<!DEBUG_INFO_EXPRESSION_TYPE("Interface1?"), SMARTCAST_IMPOSSIBLE!>y<!>.itest2()
|
||||||
}
|
}
|
||||||
y = null
|
y = null
|
||||||
foo()
|
foo()
|
||||||
|
|||||||
+18
@@ -27510,6 +27510,18 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
|
|||||||
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/assignmentConversion.kt");
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/assignmentConversion.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("capturedByAtLeastOnce.kt")
|
||||||
|
public void testCapturedByAtLeastOnce() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/capturedByAtLeastOnce.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("capturedByMultipleLambdas.kt")
|
||||||
|
public void testCapturedByMultipleLambdas() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/capturedByMultipleLambdas.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("doWhileWithMiddleBreak.kt")
|
@TestMetadata("doWhileWithMiddleBreak.kt")
|
||||||
public void testDoWhileWithMiddleBreak() throws Exception {
|
public void testDoWhileWithMiddleBreak() throws Exception {
|
||||||
@@ -35657,6 +35669,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
|
|||||||
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
|
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/kt10463.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("lambdaInCallArgs.kt")
|
||||||
|
public void testLambdaInCallArgs() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("lazyDeclaresAndModifies.kt")
|
@TestMetadata("lazyDeclaresAndModifies.kt")
|
||||||
public void testLazyDeclaresAndModifies() throws Exception {
|
public void testLazyDeclaresAndModifies() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user