Smart casts for local variables not captured in a closure and not changed in a loop, see #KT-3175.

isLocalVariable added. Assignment / initialization analysis.
Control whether a variable is changed in a loop at the beginning and at the end of the loop.
Control whether a variable is captured in a closure. #KT-3175  Fixed. #KT-2266  Fixed.
Tests for variable null safety and for variables is/as operations.
Loop / closure / variable property tests are included.
Old tests changed in accordance with KT-3175. In particular, all three of testSmartcastImpossible were fixed.
This commit is contained in:
Mikhail Glukhikh
2015-03-24 17:03:01 +03:00
parent e4b1046a54
commit d5aed62410
126 changed files with 1655 additions and 71 deletions
@@ -0,0 +1,10 @@
fun foo() {
var v: String? = null
v<!UNSAFE_CALL!>.<!>length()
v = "abc"
<!DEBUG_INFO_SMARTCAST!>v<!>.length()
v = null
v<!UNSAFE_CALL!>.<!>length()
v = "abc"
<!DEBUG_INFO_SMARTCAST!>v<!>.length()
}
@@ -0,0 +1,3 @@
package
internal fun foo(): kotlin.Unit
@@ -0,0 +1,21 @@
data class SomeObject(val n: SomeObject?) {
fun doSomething(): Boolean = true
fun next(): SomeObject? = n
}
fun list(start: SomeObject) {
var e: SomeObject?
e = start
do {
// In theory smart cast is possible here
// But in practice we have a loop with changing e
// ?: should we "or" entrance type info with condition type info?
if (!e<!UNSAFE_CALL!>.<!>doSomething())
break
// Smart cast here is still not possible
e = e<!UNSAFE_CALL!>.<!>next()
} while (e != null)
// e can be null because of next()
e<!UNSAFE_CALL!>.<!>doSomething()
}
@@ -0,0 +1,15 @@
package
internal fun list(/*0*/ start: SomeObject): kotlin.Unit
kotlin.data() internal final class SomeObject {
public constructor SomeObject(/*0*/ n: SomeObject?)
internal final val n: SomeObject?
internal final /*synthesized*/ fun component1(): SomeObject?
public final /*synthesized*/ fun copy(/*0*/ n: SomeObject? = ...): SomeObject
internal final fun doSomething(): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
internal final fun next(): SomeObject?
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,12 @@
fun x(): Boolean { return true }
public fun foo(pp: String?): Int {
var p = pp
do {
p!!.length()
if (p == "abc") break
p = null
} while (!x())
// Smart cast is NOT possible here
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ pp: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,16 @@
data class SomeObject(val n: SomeObject?) {
fun doSomething() {}
fun next(): SomeObject? = n
}
fun list(start: SomeObject): SomeObject {
var e: SomeObject? = start
for (i in 0..42) {
// Unsafe calls because of nullable e at the beginning
e<!UNSAFE_CALL!>.<!>doSomething()
e = e<!UNSAFE_CALL!>.<!>next()
}
// Smart cast is not possible here due to next()
return <!TYPE_MISMATCH!>e<!>
}
@@ -0,0 +1,15 @@
package
internal fun list(/*0*/ start: SomeObject): SomeObject
kotlin.data() internal final class SomeObject {
public constructor SomeObject(/*0*/ n: SomeObject?)
internal final val n: SomeObject?
internal final /*synthesized*/ fun component1(): SomeObject?
public final /*synthesized*/ fun copy(/*0*/ n: SomeObject? = ...): SomeObject
internal final fun doSomething(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
internal final fun next(): SomeObject?
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,17 @@
data class SomeObject(val n: SomeObject?) {
fun doSomething() {}
fun next(): SomeObject? = n
}
fun list(start: SomeObject): SomeObject {
var e: SomeObject? = start
for (i in 0..42) {
if (e == null)
break
// Smart casts are possible because of the break before
<!DEBUG_INFO_SMARTCAST!>e<!>.doSomething()
e = <!DEBUG_INFO_SMARTCAST!>e<!>.next()
}
return <!TYPE_MISMATCH!>e<!>
}
@@ -0,0 +1,15 @@
package
internal fun list(/*0*/ start: SomeObject): SomeObject
kotlin.data() internal final class SomeObject {
public constructor SomeObject(/*0*/ n: SomeObject?)
internal final val n: SomeObject?
internal final /*synthesized*/ fun component1(): SomeObject?
public final /*synthesized*/ fun copy(/*0*/ n: SomeObject? = ...): SomeObject
internal final fun doSomething(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
internal final fun next(): SomeObject?
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,17 @@
data class SomeObject(val n: SomeObject?) {
fun doSomething() {}
fun next(): SomeObject? = n
}
fun list(start: SomeObject): SomeObject {
var e: SomeObject? = start
for (i in 0..42) {
if (e == null)
continue
// Smart casts are possible because of the continue before
<!DEBUG_INFO_SMARTCAST!>e<!>.doSomething()
e = <!DEBUG_INFO_SMARTCAST!>e<!>.next()
}
return <!TYPE_MISMATCH!>e<!>
}
@@ -0,0 +1,15 @@
package
internal fun list(/*0*/ start: SomeObject): SomeObject
kotlin.data() internal final class SomeObject {
public constructor SomeObject(/*0*/ n: SomeObject?)
internal final val n: SomeObject?
internal final /*synthesized*/ fun component1(): SomeObject?
public final /*synthesized*/ fun copy(/*0*/ n: SomeObject? = ...): SomeObject
internal final fun doSomething(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
internal final fun next(): SomeObject?
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,9 @@
public fun fooNotNull(s: String) {
System.out.println("Length of $s is ${s.length()}")
}
public fun foo() {
var s: String? = "not null"
if (s != null)
fooNotNull(<!DEBUG_INFO_SMARTCAST!>s<!>)
}
@@ -0,0 +1,4 @@
package
public fun foo(): kotlin.Unit
public fun fooNotNull(/*0*/ s: kotlin.String): kotlin.Unit
@@ -0,0 +1,13 @@
// See KT-5737
fun get(): String? {
return "abc"
}
fun foo(): Int {
var ss:String? = get()
return if (ss != null && <!DEBUG_INFO_SMARTCAST!>ss<!>.length() > 0)
1
else
0
}
@@ -0,0 +1,4 @@
package
internal fun foo(): kotlin.Int
internal fun get(): kotlin.String?
@@ -0,0 +1,12 @@
public fun fooNotNull(s: String) {
System.out.println("Length of $s is ${s.length()}")
}
public fun foo() {
var s: String? = "not null"
if (s == null) {
// Coming soon
} else {
fooNotNull(<!DEBUG_INFO_SMARTCAST!>s<!>)
}
}
@@ -0,0 +1,4 @@
package
public fun foo(): kotlin.Unit
public fun fooNotNull(/*0*/ s: kotlin.String): kotlin.Unit
@@ -0,0 +1,11 @@
public fun fooNotNull(s: String) {
System.out.println("Length of $s is ${s.length()}")
}
public fun foo() {
var s: String? = "not null"
if (s == null) {
return
}
fooNotNull(<!DEBUG_INFO_SMARTCAST!>s<!>)
}
@@ -0,0 +1,4 @@
package
public fun foo(): kotlin.Unit
public fun fooNotNull(/*0*/ s: kotlin.String): kotlin.Unit
@@ -0,0 +1,21 @@
// See KT-969
fun f() {
var s: String?
s = "a"
var s1 = "" // String ?
if (<!SENSELESS_COMPARISON!>s != null<!>) { // Redundant
s1.length()
// We can do smartcast here and below
s1 = <!DEBUG_INFO_SMARTCAST!>s<!>.toString() // return String?
s1.length()
s1 = <!DEBUG_INFO_SMARTCAST!>s<!>
s1.length()
// It's just an assignment without smartcast
val s2 = s
// But smartcast can be done here
<!DEBUG_INFO_SMARTCAST!>s2<!>.length()
// And also here
val s3 = <!DEBUG_INFO_SMARTCAST!>s<!>.toString()
s3.length()
}
}
@@ -0,0 +1,3 @@
package
internal fun f(): kotlin.Unit
@@ -0,0 +1,20 @@
data class SomeObject(val n: SomeObject?) {
fun doSomething(): Boolean = true
fun next(): SomeObject? = n
}
fun list(start: SomeObject) {
var e: SomeObject?
e = start
// This comparison is senseless
while (e != null) {
// Smart cast because of the loop condition
if (!<!DEBUG_INFO_SMARTCAST!>e<!>.doSomething())
break
// We still have smart cast here despite of a break
e = <!DEBUG_INFO_SMARTCAST!>e<!>.next()
}
// e can be null because of next()
e<!UNSAFE_CALL!>.<!>doSomething()
}
@@ -0,0 +1,15 @@
package
internal fun list(/*0*/ start: SomeObject): kotlin.Unit
kotlin.data() internal final class SomeObject {
public constructor SomeObject(/*0*/ n: SomeObject?)
internal final val n: SomeObject?
internal final /*synthesized*/ fun component1(): SomeObject?
public final /*synthesized*/ fun copy(/*0*/ n: SomeObject? = ...): SomeObject
internal final fun doSomething(): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
internal final fun next(): SomeObject?
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,11 @@
// See KT-774
fun box() : Int {
var a : Int? = 1
var d = 1
if (a == null) {
return 2
} else {
return <!DEBUG_INFO_SMARTCAST!>a<!> + d
}
}
@@ -0,0 +1,3 @@
package
internal fun box(): kotlin.Int
@@ -0,0 +1,7 @@
fun foo() {
var v: String? = "xyz"
// It is possible in principle to provide smart cast here
v<!UNSAFE_CALL!>.<!>length()
v = null
v<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,3 @@
package
internal fun foo(): kotlin.Unit
@@ -0,0 +1,14 @@
data class SomeObject(val n: SomeObject?) {
fun doSomething() {}
fun next(): SomeObject? = n
}
fun list(start: SomeObject) {
var e: SomeObject? = start
while (e != null) {
// While condition makes both smart casts possible
<!DEBUG_INFO_SMARTCAST!>e<!>.doSomething()
e = <!DEBUG_INFO_SMARTCAST!>e<!>.next()
}
}
@@ -0,0 +1,15 @@
package
internal fun list(/*0*/ start: SomeObject): kotlin.Unit
kotlin.data() internal final class SomeObject {
public constructor SomeObject(/*0*/ n: SomeObject?)
internal final val n: SomeObject?
internal final /*synthesized*/ fun component1(): SomeObject?
public final /*synthesized*/ fun copy(/*0*/ n: SomeObject? = ...): SomeObject
internal final fun doSomething(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
internal final fun next(): SomeObject?
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,15 @@
fun x(): Boolean { return true }
public fun foo(pp: String?, rr: String?): Int {
var p = pp
var r = rr
do {
do {
p!!.length()
} while (r == null)
} while (!x())
// Auto cast possible
<!DEBUG_INFO_SMARTCAST!>r<!>.length()
// Auto cast possible
return <!DEBUG_INFO_SMARTCAST!>p<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ pp: kotlin.String?, /*1*/ rr: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,19 @@
fun x(): Boolean { return true }
public fun foo(qq: String?): Int {
var q = qq
while(true) {
q!!.length()
var r = q
do {
var p = r
do {
// p = r, r = q and q is not null
<!DEBUG_INFO_SMARTCAST!>p<!>.length()
} while (!x())
} while (<!SENSELESS_COMPARISON!>r == null<!>) // r = q and q is not null
if (!x()) break
}
// Smart cast is possible
return <!DEBUG_INFO_SMARTCAST!>q<!>.length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ qq: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,27 @@
class Bar {
fun next(): Bar? {
if (2 == 4)
return this
else
return null
}
}
fun foo(): Bar {
var x: Bar? = Bar()
var y: Bar? = Bar()
while (x != null) {
// Here call is unsafe because of initialization and also inner loop
y<!UNSAFE_CALL!>.<!>next()
while (y != null) {
if (x == y)
// x is not null because of outer while
return <!DEBUG_INFO_SMARTCAST!>x<!>
// y is not null because of inner while
y = <!DEBUG_INFO_SMARTCAST!>y<!>.next()
}
// x is not null because of outer while
x = <!DEBUG_INFO_SMARTCAST!>x<!>.next()
}
return Bar()
}
@@ -0,0 +1,11 @@
package
internal fun foo(): Bar
internal final class Bar {
public constructor Bar()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
internal final fun next(): Bar?
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,12 @@
fun String?.foo(): String {
return this ?: ""
}
class MyClass {
private var s: String? = null
fun bar(): String {
s = "42"
return s.foo()
}
}
@@ -0,0 +1,12 @@
package
internal fun kotlin.String?.foo(): kotlin.String
internal final class MyClass {
public constructor MyClass()
private final var s: kotlin.String?
internal final fun bar(): kotlin.String
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,12 @@
fun String?.foo(): String {
return this ?: ""
}
class MyClass {
fun bar(): String {
var s: String? = null
if (4 < 2)
s = "42"
return s.foo()
}
}
@@ -0,0 +1,11 @@
package
internal fun kotlin.String?.foo(): kotlin.String
internal final class MyClass {
public constructor MyClass()
internal final fun bar(): kotlin.String
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,19 @@
fun create(): Map<String, String> = null!!
fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = null!!
fun <K, V> Map.Entry<K, V>.component1() = getKey()
fun <K, V> Map.Entry<K, V>.component2() = getValue()
class MyClass {
private var m: Map<String, String>? = null
fun foo(): Int {
var res = 0
m = create()
// See KT-7428
for ((k, v) in <!SMARTCAST_IMPOSSIBLE!>m<!>)
res += (k.length() + v.length())
return res
}
}
@@ -0,0 +1,15 @@
package
internal fun create(): kotlin.Map<kotlin.String, kotlin.String>
internal fun </*0*/ K, /*1*/ V> kotlin.Map.Entry<K, V>.component1(): K
internal fun </*0*/ K, /*1*/ V> kotlin.Map.Entry<K, V>.component2(): V
internal fun </*0*/ K, /*1*/ V> kotlin.Map<K, V>.iterator(): kotlin.Iterator<kotlin.Map.Entry<K, V>>
internal final class MyClass {
public constructor MyClass()
private final var m: kotlin.Map<kotlin.String, kotlin.String>?
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final fun foo(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,17 @@
public fun foo() {
var s: String? = ""
fun closure(): Int {
if (s == "") {
s = null
return -1
} else if (s == null) {
return -2
} else {
return s<!UNSAFE_CALL!>.<!>length() // Here smartcast is possible, at least in principle
}
}
if (s != null) {
System.out.println(closure())
System.out.println(s<!UNSAFE_CALL!>.<!>length()) // Here smartcast is not possible due to a closure predecessor
}
}
@@ -0,0 +1,3 @@
package
public fun foo(): kotlin.Unit
@@ -0,0 +1,15 @@
// See also KT-7186
fun IntArray.forEachIndexed( op: (i: Int, value: Int) -> Unit) {
for (i in 0..this.size()-1)
op(i, this[i])
}
fun max(a: IntArray): Int? {
var maxI: Int? = null
a.forEachIndexed { i, value ->
if (maxI == null || value >= a[<!TYPE_MISMATCH!>maxI<!>])
maxI = i
}
return maxI
}
@@ -0,0 +1,4 @@
package
internal fun max(/*0*/ a: kotlin.IntArray): kotlin.Int?
internal fun kotlin.IntArray.forEachIndexed(/*0*/ op: (kotlin.Int, kotlin.Int) -> kotlin.Unit): kotlin.Unit
@@ -0,0 +1,15 @@
public fun foo() {
var s: String? = ""
fun closure(): Int {
if (s == null) {
return -1
} else {
return 0
}
}
if (s != null) {
System.out.println(closure())
// Smart cast is possible but closure makes it harder to understand
System.out.println(s<!UNSAFE_CALL!>.<!>length())
}
}
@@ -0,0 +1,3 @@
package
public fun foo(): kotlin.Unit
@@ -0,0 +1,9 @@
public fun foo() {
var i: Int? = 1
if (i != null) {
while (i != 10) {
<!UNUSED_CHANGED_VALUE!>i<!UNSAFE_CALL!>++<!><!> // Here smart cast should not be performed due to a successor
i = null
}
}
}
@@ -0,0 +1,3 @@
package
public fun foo(): kotlin.Unit
@@ -0,0 +1,9 @@
fun get(): String? {
return ""
}
fun foo(): Int {
var c: String? = get()
c!!.length()
return <!DEBUG_INFO_SMARTCAST!>c<!>.length() // Previous line should make !! unnecessary here.
}
@@ -0,0 +1,4 @@
package
internal fun foo(): kotlin.Int
internal fun get(): kotlin.String?
@@ -0,0 +1,5 @@
fun foo(): Int {
var i: Int? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>42<!>
i = null
return i <!UNSAFE_INFIX_CALL!>+<!> 1
}
@@ -0,0 +1,3 @@
package
internal fun foo(): kotlin.Int
@@ -0,0 +1,8 @@
public fun foo() {
var i: Int? = 1
if (i != null) {
while (i != 10) {
<!DEBUG_INFO_SMARTCAST!>i<!>++
}
}
}
@@ -0,0 +1,3 @@
package
public fun foo(): kotlin.Unit
@@ -0,0 +1,5 @@
fun foo(): Int {
var s: String? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>"abc"<!>
s = null
return s<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,3 @@
package
internal fun foo(): kotlin.Int
@@ -0,0 +1,13 @@
fun x(): Boolean { return true }
public fun foo(pp: String?): Int {
var p = pp
while(true) {
p!!.length()
if (x()) break
p = null
}
// Smart cast is NOT possible here
// (we could provide it but p = null makes it much harder)
return p<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
public fun foo(/*0*/ pp: kotlin.String?): kotlin.Int
internal fun x(): kotlin.Boolean
@@ -0,0 +1,17 @@
data class SomeObject(val n: SomeObject?) {
fun doSomething(): Boolean = true
fun next(): SomeObject? = n
}
fun list(start: SomeObject) {
var e: SomeObject? = start
while (e != null) {
// Smart cast due to the loop condition
if (!<!DEBUG_INFO_SMARTCAST!>e<!>.doSomething())
break
// We still have smart cast here despite of a break
e = <!DEBUG_INFO_SMARTCAST!>e<!>.next()
}
// e can be null because of next()
e<!UNSAFE_CALL!>.<!>doSomething()
}
@@ -0,0 +1,15 @@
package
internal fun list(/*0*/ start: SomeObject): kotlin.Unit
kotlin.data() internal final class SomeObject {
public constructor SomeObject(/*0*/ n: SomeObject?)
internal final val n: SomeObject?
internal final /*synthesized*/ fun component1(): SomeObject?
public final /*synthesized*/ fun copy(/*0*/ n: SomeObject? = ...): SomeObject
internal final fun doSomething(): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
internal final fun next(): SomeObject?
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}