FIR DFA: add smartcast invalidation for init block assignments
This commit is contained in:
+5
-8
@@ -202,18 +202,15 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
// ----------------------------------- Classes -----------------------------------
|
// ----------------------------------- Classes -----------------------------------
|
||||||
|
|
||||||
fun enterClass(klass: FirClass, buildGraph: Boolean) {
|
fun enterClass(klass: FirClass, buildGraph: Boolean) {
|
||||||
val (outerNode, enterNode) = graphBuilder.enterClass(klass, buildGraph) ?: return
|
val (outerNode, enterNode) = graphBuilder.enterClass(klass, buildGraph)
|
||||||
outerNode?.mergeIncomingFlow()
|
outerNode?.mergeIncomingFlow()
|
||||||
enterNode.mergeIncomingFlow()
|
enterNode?.mergeIncomingFlow()
|
||||||
|
context.variableAssignmentAnalyzer.enterClass(klass)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exitClass(): ControlFlowGraph? {
|
fun exitClass(): ControlFlowGraph? {
|
||||||
// TODO: support capturing of mutable properties
|
context.variableAssignmentAnalyzer.exitClass()
|
||||||
// var x: String?
|
val (node, graph) = graphBuilder.exitClass()
|
||||||
// x = ""
|
|
||||||
// class C { init { x = maybeNull() } }
|
|
||||||
// // C will be initialized at first use - x is no longer safe to smart cast
|
|
||||||
val (node, graph) = graphBuilder.exitClass() ?: return null
|
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
node.mergeIncomingFlow()
|
node.mergeIncomingFlow()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+86
-63
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.fir.expressions.*
|
|||||||
import org.jetbrains.kotlin.fir.references.FirNamedReference
|
import org.jetbrains.kotlin.fir.references.FirNamedReference
|
||||||
import org.jetbrains.kotlin.fir.references.FirReference
|
import org.jetbrains.kotlin.fir.references.FirReference
|
||||||
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
|
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||||
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
@@ -24,10 +25,10 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
* queries after the traversal is done.
|
* queries after the traversal is done.
|
||||||
**/
|
**/
|
||||||
internal class FirLocalVariableAssignmentAnalyzer {
|
internal class FirLocalVariableAssignmentAnalyzer {
|
||||||
private var rootFunction: FirFunction? = null
|
private var rootFunction: FirFunctionSymbol<*>? = null
|
||||||
private var assignedLocalVariablesByFunction: Map<FirFunctionSymbol<*>, FunctionFork>? = null
|
private var assignedLocalVariablesByDeclaration: Map<FirBasedSymbol<*>, Fork>? = null
|
||||||
|
|
||||||
private val functionScopes: Stack<Pair<FunctionFork?, MutableSet<FirProperty>>> = stackOf()
|
private val scopes: Stack<Pair<Fork?, MutableSet<FirProperty>>> = stackOf()
|
||||||
|
|
||||||
// Example of control-flow-postponed lambdas: callBoth({ a.x }, { a = null })
|
// Example of control-flow-postponed lambdas: callBoth({ a.x }, { a = null })
|
||||||
// Lambdas are called in an unknown order, so control flow edges to both of them go from before the call.
|
// Lambdas are called in an unknown order, so control flow edges to both of them go from before the call.
|
||||||
@@ -40,24 +41,24 @@ internal class FirLocalVariableAssignmentAnalyzer {
|
|||||||
// from the result of `run` to the result of `genericFunction` so smart casts should be prohibited in `a.x`.
|
// from the result of `run` to the result of `genericFunction` so smart casts should be prohibited in `a.x`.
|
||||||
//
|
//
|
||||||
// This mirrors `ControlFlowGraphBuilder.postponedLambdaExits`.
|
// This mirrors `ControlFlowGraphBuilder.postponedLambdaExits`.
|
||||||
private val postponedLambdas: Stack<MutableMap<FunctionFork, Boolean /* data-flow only */>> = stackOf()
|
private val postponedLambdas: Stack<MutableMap<Fork, Boolean /* data-flow only */>> = stackOf()
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
rootFunction = null
|
rootFunction = null
|
||||||
assignedLocalVariablesByFunction = null
|
assignedLocalVariablesByDeclaration = null
|
||||||
postponedLambdas.reset()
|
postponedLambdas.reset()
|
||||||
functionScopes.reset()
|
scopes.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks whether the given access is an unstable access to a local variable at this moment. */
|
/** Checks whether the given access is an unstable access to a local variable at this moment. */
|
||||||
@OptIn(DfaInternals::class)
|
@OptIn(DfaInternals::class)
|
||||||
fun isAccessToUnstableLocalVariable(fir: FirExpression): Boolean {
|
fun isAccessToUnstableLocalVariable(fir: FirExpression): Boolean {
|
||||||
if (assignedLocalVariablesByFunction == null) return false
|
if (assignedLocalVariablesByDeclaration == null) return false
|
||||||
|
|
||||||
val realFir = fir.unwrapElement() as? FirQualifiedAccessExpression ?: return false
|
val realFir = fir.unwrapElement() as? FirQualifiedAccessExpression ?: return false
|
||||||
val property = realFir.calleeReference.toResolvedPropertySymbol()?.fir ?: return false
|
val property = realFir.calleeReference.toResolvedPropertySymbol()?.fir ?: return false
|
||||||
// Have data => have a root function => functionScopes is not empty.
|
// Have data => have a root function => `scopes` is not empty.
|
||||||
return property in functionScopes.top().second || postponedLambdas.all().any { lambdas ->
|
return property in scopes.top().second || postponedLambdas.all().any { lambdas ->
|
||||||
// Control-flow-postponed lambdas' assignments should be in `functionScopes.top()`.
|
// Control-flow-postponed lambdas' assignments should be in `functionScopes.top()`.
|
||||||
// The reason we can't check them here is that one of the entries may be the lambda
|
// The reason we can't check them here is that one of the entries may be the lambda
|
||||||
// that is currently being analyzed, and assignments in it are, in fact, totally fine.
|
// that is currently being analyzed, and assignments in it are, in fact, totally fine.
|
||||||
@@ -65,22 +66,54 @@ internal class FirLocalVariableAssignmentAnalyzer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInfoForLocalFunction(symbol: FirFunctionSymbol<*>): FunctionFork? {
|
private fun getInfoForDeclaration(symbol: FirBasedSymbol<*>): Fork? {
|
||||||
val root = rootFunction ?: return null
|
val root = rootFunction ?: return null
|
||||||
if (root.symbol == symbol) return null
|
if (root == symbol) return null
|
||||||
val cachedMap = assignedLocalVariablesByFunction ?: run {
|
val cachedMap = assignedLocalVariablesByDeclaration ?: run {
|
||||||
val data = MiniCfgBuilder.MiniCfgData()
|
val data = MiniCfgBuilder.MiniCfgData()
|
||||||
MiniCfgBuilder().visitElement(root, data)
|
MiniCfgBuilder().visitElement(root.fir, data)
|
||||||
data.functionForks.also { assignedLocalVariablesByFunction = it }
|
data.forks.also { assignedLocalVariablesByDeclaration = it }
|
||||||
}
|
}
|
||||||
return cachedMap[symbol]
|
return cachedMap[symbol]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enterFunction(function: FirFunction) {
|
private fun enterScope(symbol: FirBasedSymbol<*>, evaluatedInPlace: Boolean): Pair<Fork?, MutableSet<FirProperty>> {
|
||||||
val prohibitSmartCasts = functionScopes.topOrNull()?.second?.toMutableSet()
|
val currentInfo = getInfoForDeclaration(symbol)
|
||||||
?: mutableSetOf<FirProperty>().also { rootFunction = function }
|
val prohibitInThisScope = scopes.top().second.toMutableSet()
|
||||||
|
scopes.push(currentInfo to prohibitInThisScope)
|
||||||
|
if (!evaluatedInPlace) {
|
||||||
|
for ((outerInfo, prohibitInOuterScope) in scopes.all()) {
|
||||||
|
// The callable may be stored and then called later
|
||||||
|
// => any access of the variables it touches is no longer smartcastable ever,
|
||||||
|
// including within the callable itself (can recurse).
|
||||||
|
//
|
||||||
|
// TODO: this incorrectly affects separate branches that are visited after this one:
|
||||||
|
// if (p is Something) {
|
||||||
|
// if (condition)) {
|
||||||
|
// foo { p = whatever }
|
||||||
|
// p.memberOfSomething // Bad
|
||||||
|
// } else {
|
||||||
|
// p.memberOfSomething // Marked as an error, but actually OK
|
||||||
|
// }
|
||||||
|
// p.memberOfSomething // Bad
|
||||||
|
// }
|
||||||
|
// FE1.0 has the same behavior.
|
||||||
|
currentInfo?.assignedInside?.let(prohibitInOuterScope::addAll)
|
||||||
|
// => any write to a variable outside the callable invalidates smart casts inside it
|
||||||
|
outerInfo?.assignedLater?.let(prohibitInThisScope::addAll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scopes.top()
|
||||||
|
}
|
||||||
|
|
||||||
val info = getInfoForLocalFunction(function.symbol)
|
fun enterFunction(function: FirFunction) {
|
||||||
|
if (rootFunction == null) {
|
||||||
|
rootFunction = function.symbol
|
||||||
|
scopes.push(null to mutableSetOf())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val (info, prohibitSmartCasts) =
|
||||||
|
enterScope(function.symbol, function is FirAnonymousFunction && function.invocationKind.isInPlace)
|
||||||
for (concurrentLambdas in postponedLambdas.all()) {
|
for (concurrentLambdas in postponedLambdas.all()) {
|
||||||
for ((otherLambda, dataFlowOnly) in concurrentLambdas) {
|
for ((otherLambda, dataFlowOnly) in concurrentLambdas) {
|
||||||
if (!dataFlowOnly && otherLambda != info) {
|
if (!dataFlowOnly && otherLambda != info) {
|
||||||
@@ -88,51 +121,35 @@ internal class FirLocalVariableAssignmentAnalyzer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (function !is FirAnonymousFunction || !function.invocationKind.isInPlace) {
|
|
||||||
// The function may be stored and then called later =>
|
|
||||||
for ((outerInfo, prohibitInOuterScope) in functionScopes.all()) {
|
|
||||||
if (info != null) {
|
|
||||||
// => any access of the variables it touches is no longer smartcastable ever.
|
|
||||||
//
|
|
||||||
// TODO: this incorrectly affects separate branches that are visited after this one:
|
|
||||||
// if (p is Something) {
|
|
||||||
// if (condition)) {
|
|
||||||
// foo { p = whatever }
|
|
||||||
// p.memberOfSomething // Bad
|
|
||||||
// } else {
|
|
||||||
// p.memberOfSomething // Marked as an error, but actually OK
|
|
||||||
// }
|
|
||||||
// p.memberOfSomething // Bad
|
|
||||||
// }
|
|
||||||
// FE1.0 has the same behavior.
|
|
||||||
prohibitInOuterScope.addAll(info.assignedInside)
|
|
||||||
}
|
|
||||||
if (outerInfo != null) {
|
|
||||||
// => any write to a variable outside the function invalidates smart casts inside it
|
|
||||||
prohibitSmartCasts += outerInfo.assignedLater
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (info != null) {
|
|
||||||
prohibitSmartCasts.addAll(info.assignedLater)
|
|
||||||
// => it may be called twice in parallel, so it can't even trust its own assignments
|
|
||||||
prohibitSmartCasts.addAll(info.assignedInside)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
functionScopes.push(info to prohibitSmartCasts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exitFunction() {
|
fun exitFunction() {
|
||||||
functionScopes.pop()
|
scopes.pop()
|
||||||
if (functionScopes.isEmpty) {
|
if (scopes.isEmpty) {
|
||||||
rootFunction = null
|
rootFunction = null
|
||||||
assignedLocalVariablesByFunction = null
|
assignedLocalVariablesByDeclaration = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun enterClass(klass: FirClass) {
|
||||||
|
if (rootFunction == null) return
|
||||||
|
val (info, prohibitSmartCasts) = enterScope(klass.symbol, klass is FirAnonymousObject)
|
||||||
|
if (klass is FirAnonymousObject && info != null) {
|
||||||
|
// Assignments in initializers and methods invalidate smart casts in other members.
|
||||||
|
// TODO: initializers shouldn't invalidate smart casts in themselves.
|
||||||
|
prohibitSmartCasts.addAll(info.assignedInside)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exitClass() {
|
||||||
|
if (rootFunction == null) return
|
||||||
|
scopes.pop()
|
||||||
|
}
|
||||||
|
|
||||||
fun enterFunctionCall(lambdaArgs: Collection<FirAnonymousFunction>) {
|
fun enterFunctionCall(lambdaArgs: Collection<FirAnonymousFunction>) {
|
||||||
// If not inside a function at all, then there is no concept of a local and nothing to track.
|
// If not inside a function at all, then there is no concept of a local and nothing to track.
|
||||||
if (rootFunction == null) return
|
if (rootFunction == null) return
|
||||||
postponedLambdas.push(lambdaArgs.mapNotNull { getInfoForLocalFunction(it.symbol) }.associateWithTo(mutableMapOf()) { false })
|
postponedLambdas.push(lambdaArgs.mapNotNull { getInfoForDeclaration(it.symbol) }.associateWithTo(mutableMapOf()) { false })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exitFunctionCall(callCompleted: Boolean) {
|
fun exitFunctionCall(callCompleted: Boolean) {
|
||||||
@@ -235,7 +252,7 @@ internal class FirLocalVariableAssignmentAnalyzer {
|
|||||||
* so that shadowed names are handled correctly. This works because local variables at any scope have higher priority
|
* so that shadowed names are handled correctly. This works because local variables at any scope have higher priority
|
||||||
* than members on implicit receivers, even if the implicit receiver is introduced by a later scope.
|
* than members on implicit receivers, even if the implicit receiver is introduced by a later scope.
|
||||||
*/
|
*/
|
||||||
class FunctionFork(
|
class Fork(
|
||||||
val assignedLater: Set<FirProperty>,
|
val assignedLater: Set<FirProperty>,
|
||||||
val assignedInside: Set<FirProperty>,
|
val assignedInside: Set<FirProperty>,
|
||||||
)
|
)
|
||||||
@@ -255,7 +272,7 @@ internal class FirLocalVariableAssignmentAnalyzer {
|
|||||||
element.acceptChildren(this, data)
|
element.acceptChildren(this, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visitRepeatableElement(element: FirElement, data: MiniCfgData): Set<FirProperty> {
|
private fun visitElementWithLexicalScope(element: FirElement, data: MiniCfgData): Set<FirProperty> {
|
||||||
// Detach the flow so that variables declared inside the structure do not leak into the outside.
|
// Detach the flow so that variables declared inside the structure do not leak into the outside.
|
||||||
val flow = MiniFlow.start()
|
val flow = MiniFlow.start()
|
||||||
val freeVariables = data.variableDeclarations.flatMapTo(mutableSetOf()) { it.values }
|
val freeVariables = data.variableDeclarations.flatMapTo(mutableSetOf()) { it.values }
|
||||||
@@ -265,19 +282,25 @@ internal class FirLocalVariableAssignmentAnalyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: MiniCfgData) =
|
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: MiniCfgData) =
|
||||||
visitFunction(anonymousFunction, data)
|
visitLocalDeclaration(anonymousFunction, data)
|
||||||
|
|
||||||
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: MiniCfgData) =
|
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: MiniCfgData) =
|
||||||
visitFunction(simpleFunction, data)
|
visitLocalDeclaration(simpleFunction, data)
|
||||||
|
|
||||||
override fun visitFunction(function: FirFunction, data: MiniCfgData) {
|
override fun visitRegularClass(regularClass: FirRegularClass, data: MiniCfgData) =
|
||||||
|
visitLocalDeclaration(regularClass, data)
|
||||||
|
|
||||||
|
override fun visitAnonymousObject(anonymousObject: FirAnonymousObject, data: MiniCfgData) =
|
||||||
|
visitLocalDeclaration(anonymousObject, data)
|
||||||
|
|
||||||
|
private fun visitLocalDeclaration(declaration: FirDeclaration, data: MiniCfgData) {
|
||||||
val flow = data.flow
|
val flow = data.flow
|
||||||
val assignedInside = visitRepeatableElement(function, data)
|
val assignedInside = visitElementWithLexicalScope(declaration, data)
|
||||||
// Now that the inner variables have been discarded, the rest can be propagated to prevent smartcasts
|
// Now that the inner variables have been discarded, the rest can be propagated to prevent smartcasts
|
||||||
// in lambdas declared before this one.
|
// in declarations that came before this one.
|
||||||
flow.recordAssignments(assignedInside)
|
flow.recordAssignments(assignedInside)
|
||||||
data.flow = flow.fork()
|
data.flow = flow.fork()
|
||||||
data.functionForks[function.symbol] = FunctionFork(data.flow.assignedLater, assignedInside)
|
data.forks[declaration.symbol] = Fork(data.flow.assignedLater, assignedInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitWhenExpression(whenExpression: FirWhenExpression, data: MiniCfgData) {
|
override fun visitWhenExpression(whenExpression: FirWhenExpression, data: MiniCfgData) {
|
||||||
@@ -308,7 +331,7 @@ internal class FirLocalVariableAssignmentAnalyzer {
|
|||||||
|
|
||||||
override fun visitLoop(loop: FirLoop, data: MiniCfgData) {
|
override fun visitLoop(loop: FirLoop, data: MiniCfgData) {
|
||||||
val entry = data.flow
|
val entry = data.flow
|
||||||
val assignedInside = visitRepeatableElement(loop, data)
|
val assignedInside = visitElementWithLexicalScope(loop, data)
|
||||||
// All forks in the loop should have the same set of variables assigned later, equal to the set
|
// All forks in the loop should have the same set of variables assigned later, equal to the set
|
||||||
// at the start of the loop.
|
// at the start of the loop.
|
||||||
data.flow.recordAssignments(assignedInside)
|
data.flow.recordAssignments(assignedInside)
|
||||||
@@ -383,7 +406,7 @@ internal class FirLocalVariableAssignmentAnalyzer {
|
|||||||
class MiniCfgData {
|
class MiniCfgData {
|
||||||
var flow: MiniFlow = MiniFlow.start()
|
var flow: MiniFlow = MiniFlow.start()
|
||||||
val variableDeclarations: ArrayDeque<MutableMap<Name, FirProperty>> = ArrayDeque(listOf(mutableMapOf()))
|
val variableDeclarations: ArrayDeque<MutableMap<Name, FirProperty>> = ArrayDeque(listOf(mutableMapOf()))
|
||||||
val functionForks: MutableMap<FirFunctionSymbol<*>, FunctionFork> = mutableMapOf()
|
val forks: MutableMap<FirBasedSymbol<*>, Fork> = mutableMapOf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -371,10 +371,10 @@ class ControlFlowGraphBuilder {
|
|||||||
addEdge(source, enterNode, preferredKind = kind)
|
addEdge(source, enterNode, preferredKind = kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enterClass(klass: FirClass, buildGraph: Boolean): Pair<CFGNode<*>?, ClassEnterNode>? {
|
fun enterClass(klass: FirClass, buildGraph: Boolean): Pair<CFGNode<*>?, ClassEnterNode?> {
|
||||||
if (!buildGraph || klass !is FirControlFlowGraphOwner) {
|
if (!buildGraph || klass !is FirControlFlowGraphOwner) {
|
||||||
graphs.push(ControlFlowGraph(null, "<discarded class graph>", ControlFlowGraph.Kind.Class))
|
graphs.push(ControlFlowGraph(null, "<discarded class graph>", ControlFlowGraph.Kind.Class))
|
||||||
return null
|
return null to null
|
||||||
}
|
}
|
||||||
|
|
||||||
val localClassEnterNode = when {
|
val localClassEnterNode = when {
|
||||||
@@ -418,11 +418,11 @@ class ControlFlowGraphBuilder {
|
|||||||
return localClassEnterNode to enterNode
|
return localClassEnterNode to enterNode
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exitClass(): Pair<ClassExitNode?, ControlFlowGraph>? {
|
fun exitClass(): Pair<ClassExitNode?, ControlFlowGraph?> {
|
||||||
assert(currentGraph.kind == ControlFlowGraph.Kind.Class)
|
assert(currentGraph.kind == ControlFlowGraph.Kind.Class)
|
||||||
if (currentGraph.declaration == null) {
|
if (currentGraph.declaration == null) {
|
||||||
graphs.pop()
|
graphs.pop()
|
||||||
return null
|
return null to null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Members of a class can be visited in any order, so data flow between them is unordered,
|
// Members of a class can be visited in any order, so data flow between them is unordered,
|
||||||
@@ -435,7 +435,7 @@ class ControlFlowGraphBuilder {
|
|||||||
// this graph to be built twice (or more). Not sure what this means. Nothing good, probably.
|
// this graph to be built twice (or more). Not sure what this means. Nothing good, probably.
|
||||||
// In any case, attempting to add more edges to subgraphs will be fatal.
|
// In any case, attempting to add more edges to subgraphs will be fatal.
|
||||||
graphs.pop()
|
graphs.pop()
|
||||||
return null
|
return null to null
|
||||||
}
|
}
|
||||||
|
|
||||||
val calledInPlace = mutableListOf<ControlFlowGraph>()
|
val calledInPlace = mutableListOf<ControlFlowGraph>()
|
||||||
|
|||||||
+15
-15
@@ -8,11 +8,11 @@ fun objectInit() {
|
|||||||
x.length // ok
|
x.length // ok
|
||||||
y.length // ok
|
y.length // ok
|
||||||
val o = object {
|
val o = object {
|
||||||
init { x.length } // ?
|
init { <!SMARTCAST_IMPOSSIBLE!>x<!>.length } // ?
|
||||||
init { x = null }
|
init { x = null }
|
||||||
init { x.length } // bad
|
init { <!SMARTCAST_IMPOSSIBLE!>x<!>.length } // bad
|
||||||
init { y.length } // ok
|
init { y.length } // ok
|
||||||
fun foo() = x.length // bad
|
fun foo() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
}
|
}
|
||||||
y = null
|
y = null
|
||||||
x<!UNSAFE_CALL!>.<!>length // bad
|
x<!UNSAFE_CALL!>.<!>length // bad
|
||||||
@@ -24,7 +24,7 @@ fun objectMethod() {
|
|||||||
x = ""
|
x = ""
|
||||||
x.length // ok
|
x.length // ok
|
||||||
val o = object {
|
val o = object {
|
||||||
init { x.length } // sort of bad
|
init { <!SMARTCAST_IMPOSSIBLE!>x<!>.length } // sort of bad
|
||||||
fun foo() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
fun foo() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
fun bar() { x = null }
|
fun bar() { x = null }
|
||||||
fun baz() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
fun baz() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
@@ -45,24 +45,24 @@ fun classInit() {
|
|||||||
y.length // ok
|
y.length // ok
|
||||||
val ctor = run {
|
val ctor = run {
|
||||||
class C {
|
class C {
|
||||||
init { x.length } // ?
|
init { <!SMARTCAST_IMPOSSIBLE!>x<!>.length } // ?
|
||||||
init { x = null }
|
init { x = null }
|
||||||
init { x.length } // bad
|
init { <!SMARTCAST_IMPOSSIBLE!>x<!>.length } // bad
|
||||||
init { y.length } // bad
|
init { <!SMARTCAST_IMPOSSIBLE!>y<!>.length } // bad
|
||||||
fun foo() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
fun foo() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
}
|
}
|
||||||
x.length // bad
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
if (<!SENSELESS_COMPARISON!>x != null<!>) {
|
if (x != null) {
|
||||||
y = null
|
y = null
|
||||||
C() // read y & assign x here
|
C() // read y & assign x here
|
||||||
x.length // bad
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
}
|
}
|
||||||
::C
|
::C
|
||||||
}
|
}
|
||||||
x.length // bad
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
if (<!SENSELESS_COMPARISON!>x != null<!>) {
|
if (x != null) {
|
||||||
ctor() // read y & assign x here
|
ctor() // read y & assign x here
|
||||||
x.length // bad
|
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +75,8 @@ fun classMethod() {
|
|||||||
y.length // ok
|
y.length // ok
|
||||||
val ctor = run {
|
val ctor = run {
|
||||||
class C {
|
class C {
|
||||||
init { x.length } // sort of bad
|
init { <!SMARTCAST_IMPOSSIBLE!>x<!>.length } // sort of bad
|
||||||
init { y.length } // bad
|
init { <!SMARTCAST_IMPOSSIBLE!>y<!>.length } // bad
|
||||||
fun foo() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
fun foo() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
fun bar() { x = null }
|
fun bar() { x = null }
|
||||||
fun baz() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
fun baz() = <!SMARTCAST_IMPOSSIBLE!>x<!>.length // bad
|
||||||
|
|||||||
Reference in New Issue
Block a user