[FIR] Add "can be val" extended checker
This commit is contained in:
+1
-1
@@ -52,7 +52,7 @@ fun foo() {
|
|||||||
val piFloat: <!REDUNDANT_EXPLICIT_TYPE!>Float<!> = 3.14f
|
val piFloat: <!REDUNDANT_EXPLICIT_TYPE!>Float<!> = 3.14f
|
||||||
val piDouble: <!REDUNDANT_EXPLICIT_TYPE!>Double<!> = 3.14
|
val piDouble: <!REDUNDANT_EXPLICIT_TYPE!>Double<!> = 3.14
|
||||||
val charZ: <!REDUNDANT_EXPLICIT_TYPE!>Char<!> = 'z'
|
val charZ: <!REDUNDANT_EXPLICIT_TYPE!>Char<!> = 'z'
|
||||||
var alpha: <!REDUNDANT_EXPLICIT_TYPE!>Int<!> = 0
|
<!CAN_BE_VAL!>var<!> alpha: <!REDUNDANT_EXPLICIT_TYPE!>Int<!> = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test(boolean: Boolean) {
|
fun test(boolean: Boolean) {
|
||||||
|
|||||||
Vendored
+1
-1
@@ -4,7 +4,7 @@ fun f() {
|
|||||||
<!REDUNDANT_VISIBILITY_MODIFIER!>internal<!> var foo = 0
|
<!REDUNDANT_VISIBILITY_MODIFIER!>internal<!> var foo = 0
|
||||||
}
|
}
|
||||||
LocalClass().foo = 1
|
LocalClass().foo = 1
|
||||||
<!REDUNDANT_VISIBILITY_MODIFIER!>public<!> var baz = 0
|
<!REDUNDANT_VISIBILITY_MODIFIER!>public<!> <!CAN_BE_VAL!>var<!> baz = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
internal inline fun internal() {
|
internal inline fun internal() {
|
||||||
|
|||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
fun testDelegator() {
|
||||||
|
var x: Boolean by LocalFreezableVar(true)
|
||||||
|
var y by LocalFreezableVar("")
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalFreezableVar<T>(private var value: T) {
|
||||||
|
operator fun getValue(thisRef: Nothing?, property: KProperty<*>): T = value
|
||||||
|
|
||||||
|
operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: T) {
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class C
|
||||||
|
operator fun C.plus(a: Any): C = this
|
||||||
|
operator fun C.plusAssign(a: Any) {}
|
||||||
|
|
||||||
|
fun testOperatorAssignment() {
|
||||||
|
val c = C()
|
||||||
|
c += ""
|
||||||
|
<!CAN_BE_VAL!>var<!> c1 = C()
|
||||||
|
<!ASSIGN_OPERATOR_AMBIGUITY!>c1 += ""<!>
|
||||||
|
|
||||||
|
var a = 1
|
||||||
|
a += 12
|
||||||
|
a -= 10
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun destructuringDeclaration() {
|
||||||
|
<!CAN_BE_VAL!>var<!> (v1, v2) = getPair()
|
||||||
|
print(v1)
|
||||||
|
|
||||||
|
var (v3, v4) = getPair()
|
||||||
|
print(v3)
|
||||||
|
v4 = ""
|
||||||
|
|
||||||
|
var (v5, v6) = getPair()
|
||||||
|
v5 = 1
|
||||||
|
|
||||||
|
var (v7, v8) = getPair()
|
||||||
|
v7 = 2
|
||||||
|
v8 = "42"
|
||||||
|
|
||||||
|
val (a, b, c) = Triple(1, 1, 1)
|
||||||
|
|
||||||
|
<!CAN_BE_VAL!>var<!> (x, y, z) = Triple(1, 1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stackOverflowBug() {
|
||||||
|
<!CAN_BE_VAL!>var<!> a: Int
|
||||||
|
a = 1
|
||||||
|
for (i in 1..10)
|
||||||
|
print(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun smth(flag: Boolean) {
|
||||||
|
var a = 1
|
||||||
|
|
||||||
|
if (flag) {
|
||||||
|
while (a > 0) {
|
||||||
|
a--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withAnnotation(p: List<Any>) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
<!CAN_BE_VAL!>var<!> v = p as List<String>
|
||||||
|
print(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withReadonlyDeligate() {
|
||||||
|
val s: String by lazy { "Hello!" }
|
||||||
|
s.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPair(): Pair<Int, String> = Pair(1, "1")
|
||||||
|
|
||||||
|
fun listReceiver(p: List<String>) {}
|
||||||
|
|
||||||
|
fun withInitializer() {
|
||||||
|
var v1 = 1
|
||||||
|
var v2 = 2
|
||||||
|
<!CAN_BE_VAL!>var<!> v3 = 3
|
||||||
|
v1 = 1
|
||||||
|
v2++
|
||||||
|
print(v3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
var a = 0
|
||||||
|
while (a>0) {
|
||||||
|
a++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
<!CAN_BE_VAL!>var<!> a: Int
|
||||||
|
val bool = true
|
||||||
|
val b: String
|
||||||
|
|
||||||
|
if (bool) a = 4 else a = 42
|
||||||
|
|
||||||
|
bool = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cycles() {
|
||||||
|
var a = 10
|
||||||
|
while (a > 0) {
|
||||||
|
a--
|
||||||
|
}
|
||||||
|
|
||||||
|
val b: Int
|
||||||
|
while (a < 10) {
|
||||||
|
a++
|
||||||
|
b = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assignedTwice(p: Int) {
|
||||||
|
var v: Int
|
||||||
|
v = 0
|
||||||
|
if (p > 0) v = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String?>) {
|
||||||
|
<!CAN_BE_VAL!>var<!> a: String?
|
||||||
|
|
||||||
|
if (args.size == 1) {
|
||||||
|
a = args[0]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
a = args.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a != null && a.equals("cde")) return
|
||||||
|
}
|
||||||
|
|
||||||
|
fun run(f: () -> Unit) = f()
|
||||||
|
|
||||||
|
fun lambda() {
|
||||||
|
var a: Int
|
||||||
|
a = 10
|
||||||
|
|
||||||
|
run {
|
||||||
|
a = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambdaInitialization() {
|
||||||
|
<!CAN_BE_VAL!>var<!> a: Int
|
||||||
|
|
||||||
|
run {
|
||||||
|
a = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notAssignedWhenNotUsed(p: Int) {
|
||||||
|
<!CAN_BE_VAL!>var<!> v: Int
|
||||||
|
if (p > 0) {
|
||||||
|
v = 1
|
||||||
|
print(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var global = 1
|
||||||
|
|
||||||
|
class C {
|
||||||
|
var field = 2
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
print(field)
|
||||||
|
print(global)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withDelegate() {
|
||||||
|
var s: String by Delegates.notNull()
|
||||||
|
s = ""
|
||||||
|
}
|
||||||
+240
@@ -0,0 +1,240 @@
|
|||||||
|
FILE: VariableAssignmentChecker.kt
|
||||||
|
public final fun testDelegator(): R|kotlin/Unit| {
|
||||||
|
lvar x: R|kotlin/Boolean|by R|/LocalFreezableVar.LocalFreezableVar|<R|kotlin/Boolean|>(Boolean(true))
|
||||||
|
lvar y: R|kotlin/String|by R|/LocalFreezableVar.LocalFreezableVar|<R|kotlin/String|>(String())
|
||||||
|
}
|
||||||
|
public final class LocalFreezableVar<T> : R|kotlin/Any| {
|
||||||
|
public constructor<T>(value: R|T|): R|LocalFreezableVar<T>| {
|
||||||
|
super<R|kotlin/Any|>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private final var value: R|T| = R|<local>/value|
|
||||||
|
private get(): R|T|
|
||||||
|
private set(value: R|T|): R|kotlin/Unit|
|
||||||
|
|
||||||
|
public final operator fun getValue(thisRef: R|kotlin/Nothing?|, property: R|kotlin/reflect/KProperty<*>|): R|T| {
|
||||||
|
^getValue this@R|/LocalFreezableVar|.R|/LocalFreezableVar.value|
|
||||||
|
}
|
||||||
|
|
||||||
|
public final operator fun setValue(thisRef: R|kotlin/Nothing?|, property: R|kotlin/reflect/KProperty<*>|, value: R|T|): R|kotlin/Unit| {
|
||||||
|
this@R|/LocalFreezableVar|.R|/LocalFreezableVar.value| = R|<local>/value|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final class C : R|kotlin/Any| {
|
||||||
|
public constructor(): R|C| {
|
||||||
|
super<R|kotlin/Any|>()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final operator fun R|C|.plus(a: R|kotlin/Any|): R|C| {
|
||||||
|
^plus this@R|/plus|
|
||||||
|
}
|
||||||
|
public final operator fun R|C|.plusAssign(a: R|kotlin/Any|): R|kotlin/Unit| {
|
||||||
|
}
|
||||||
|
public final fun testOperatorAssignment(): R|kotlin/Unit| {
|
||||||
|
lval c: R|C| = R|/C.C|()
|
||||||
|
R|<local>/c|.R|/plusAssign|(String())
|
||||||
|
lvar c1: R|C| = R|/C.C|()
|
||||||
|
ERROR_EXPR(Operator overload ambiguity. Compatible candidates: [/plus, /plusAssign])
|
||||||
|
lvar a: R|kotlin/Int| = Int(1)
|
||||||
|
R|<local>/a| = R|<local>/a|.R|kotlin/Int.plus|(Int(12))
|
||||||
|
R|<local>/a| = R|<local>/a|.R|kotlin/Int.minus|(Int(10))
|
||||||
|
}
|
||||||
|
public final fun destructuringDeclaration(): R|kotlin/Unit| {
|
||||||
|
lval <destruct>: R|kotlin/Pair<kotlin/Int, kotlin/String>| = R|/getPair|()
|
||||||
|
lvar v1: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Pair.component1: R|kotlin/Int|>|()
|
||||||
|
lvar v2: R|kotlin/String| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Pair.component2: R|kotlin/String|>|()
|
||||||
|
R|kotlin/io/print|(R|<local>/v1|)
|
||||||
|
lval <destruct>: R|kotlin/Pair<kotlin/Int, kotlin/String>| = R|/getPair|()
|
||||||
|
lvar v3: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Pair.component1: R|kotlin/Int|>|()
|
||||||
|
lvar v4: R|kotlin/String| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Pair.component2: R|kotlin/String|>|()
|
||||||
|
R|kotlin/io/print|(R|<local>/v3|)
|
||||||
|
R|<local>/v4| = String()
|
||||||
|
lval <destruct>: R|kotlin/Pair<kotlin/Int, kotlin/String>| = R|/getPair|()
|
||||||
|
lvar v5: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Pair.component1: R|kotlin/Int|>|()
|
||||||
|
lvar v6: R|kotlin/String| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Pair.component2: R|kotlin/String|>|()
|
||||||
|
R|<local>/v5| = Int(1)
|
||||||
|
lval <destruct>: R|kotlin/Pair<kotlin/Int, kotlin/String>| = R|/getPair|()
|
||||||
|
lvar v7: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Pair.component1: R|kotlin/Int|>|()
|
||||||
|
lvar v8: R|kotlin/String| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Pair.component2: R|kotlin/String|>|()
|
||||||
|
R|<local>/v7| = Int(2)
|
||||||
|
R|<local>/v8| = String(42)
|
||||||
|
lval <destruct>: R|kotlin/Triple<kotlin/Int, kotlin/Int, kotlin/Int>| = R|kotlin/Triple.Triple|<R|kotlin/Int|, R|kotlin/Int|, R|kotlin/Int|>(Int(1), Int(1), Int(1))
|
||||||
|
lval a: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Triple.component1: R|kotlin/Int|>|()
|
||||||
|
lval b: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Triple.component2: R|kotlin/Int|>|()
|
||||||
|
lval c: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Triple.component3: R|kotlin/Int|>|()
|
||||||
|
lval <destruct>: R|kotlin/Triple<kotlin/Int, kotlin/Int, kotlin/Int>| = R|kotlin/Triple.Triple|<R|kotlin/Int|, R|kotlin/Int|, R|kotlin/Int|>(Int(1), Int(1), Int(1))
|
||||||
|
lvar x: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Triple.component1: R|kotlin/Int|>|()
|
||||||
|
lvar y: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Triple.component2: R|kotlin/Int|>|()
|
||||||
|
lvar z: R|kotlin/Int| = R|<local>/<destruct>|.R|FakeOverride<kotlin/Triple.component3: R|kotlin/Int|>|()
|
||||||
|
}
|
||||||
|
public final fun stackOverflowBug(): R|kotlin/Unit| {
|
||||||
|
lvar a: R|kotlin/Int|
|
||||||
|
R|<local>/a| = Int(1)
|
||||||
|
lval <iterator>: R|kotlin/collections/IntIterator| = Int(1).R|kotlin/Int.rangeTo|(Int(10)).R|kotlin/ranges/IntProgression.iterator|()
|
||||||
|
while(R|<local>/<iterator>|.R|kotlin/collections/Iterator.hasNext|()) {
|
||||||
|
lval i: R|kotlin/Int| = R|<local>/<iterator>|.R|kotlin/collections/IntIterator.next|()
|
||||||
|
R|kotlin/io/print|(R|<local>/i|)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final fun smth(flag: R|kotlin/Boolean|): R|kotlin/Unit| {
|
||||||
|
lvar a: R|kotlin/Int| = Int(1)
|
||||||
|
when () {
|
||||||
|
R|<local>/flag| -> {
|
||||||
|
while(CMP(>, R|<local>/a|.R|kotlin/Int.compareTo|(Int(0)))) {
|
||||||
|
lval <unary>: R|kotlin/Int| = R|<local>/a|
|
||||||
|
R|<local>/a| = R|<local>/<unary>|.R|kotlin/Int.dec|()
|
||||||
|
R|<local>/<unary>|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final fun withAnnotation(p: R|kotlin/collections/List<kotlin/Any>|): R|kotlin/Unit| {
|
||||||
|
@R|kotlin/Suppress|(vararg(String(UNCHECKED_CAST))) lvar v: R|kotlin/collections/List<kotlin/String>| = (R|<local>/p| as R|kotlin/collections/List<kotlin/String>|)
|
||||||
|
R|kotlin/io/print|(R|<local>/v|)
|
||||||
|
}
|
||||||
|
public final fun withReadonlyDeligate(): R|kotlin/Unit| {
|
||||||
|
lval s: R|kotlin/String|by R|kotlin/lazy|<R|kotlin/String|>(<L> = lazy@fun <anonymous>(): R|kotlin/String| {
|
||||||
|
^ String(Hello!)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
R|<local>/s|.R|kotlin/Any.hashCode|()
|
||||||
|
}
|
||||||
|
public final fun getPair(): R|kotlin/Pair<kotlin/Int, kotlin/String>| {
|
||||||
|
^getPair R|kotlin/Pair.Pair|<R|kotlin/Int|, R|kotlin/String|>(Int(1), String(1))
|
||||||
|
}
|
||||||
|
public final fun listReceiver(p: R|kotlin/collections/List<kotlin/String>|): R|kotlin/Unit| {
|
||||||
|
}
|
||||||
|
public final fun withInitializer(): R|kotlin/Unit| {
|
||||||
|
lvar v1: R|kotlin/Int| = Int(1)
|
||||||
|
lvar v2: R|kotlin/Int| = Int(2)
|
||||||
|
lvar v3: R|kotlin/Int| = Int(3)
|
||||||
|
R|<local>/v1| = Int(1)
|
||||||
|
lval <unary>: R|kotlin/Int| = R|<local>/v2|
|
||||||
|
R|<local>/v2| = R|<local>/<unary>|.R|kotlin/Int.inc|()
|
||||||
|
R|<local>/<unary>|
|
||||||
|
R|kotlin/io/print|(R|<local>/v3|)
|
||||||
|
}
|
||||||
|
public final fun test(): R|kotlin/Unit| {
|
||||||
|
lvar a: R|kotlin/Int| = Int(0)
|
||||||
|
while(CMP(>, R|<local>/a|.R|kotlin/Int.compareTo|(Int(0)))) {
|
||||||
|
lval <unary>: R|kotlin/Int| = R|<local>/a|
|
||||||
|
R|<local>/a| = R|<local>/<unary>|.R|kotlin/Int.inc|()
|
||||||
|
R|<local>/<unary>|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final fun foo(): R|kotlin/Unit| {
|
||||||
|
lvar a: R|kotlin/Int|
|
||||||
|
lval bool: R|kotlin/Boolean| = Boolean(true)
|
||||||
|
lval b: R|kotlin/String|
|
||||||
|
when () {
|
||||||
|
R|<local>/bool| -> {
|
||||||
|
R|<local>/a| = Int(4)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
R|<local>/a| = Int(42)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
R|<local>/bool| = Boolean(false)
|
||||||
|
}
|
||||||
|
public final fun cycles(): R|kotlin/Unit| {
|
||||||
|
lvar a: R|kotlin/Int| = Int(10)
|
||||||
|
while(CMP(>, R|<local>/a|.R|kotlin/Int.compareTo|(Int(0)))) {
|
||||||
|
lval <unary>: R|kotlin/Int| = R|<local>/a|
|
||||||
|
R|<local>/a| = R|<local>/<unary>|.R|kotlin/Int.dec|()
|
||||||
|
R|<local>/<unary>|
|
||||||
|
}
|
||||||
|
|
||||||
|
lval b: R|kotlin/Int|
|
||||||
|
while(CMP(<, R|<local>/a|.R|kotlin/Int.compareTo|(Int(10)))) {
|
||||||
|
lval <unary>: R|kotlin/Int| = R|<local>/a|
|
||||||
|
R|<local>/a| = R|<local>/<unary>|.R|kotlin/Int.inc|()
|
||||||
|
R|<local>/<unary>|
|
||||||
|
R|<local>/b| = R|<local>/a|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final fun assignedTwice(p: R|kotlin/Int|): R|kotlin/Unit| {
|
||||||
|
lvar v: R|kotlin/Int|
|
||||||
|
R|<local>/v| = Int(0)
|
||||||
|
when () {
|
||||||
|
CMP(>, R|<local>/p|.R|kotlin/Int.compareTo|(Int(0))) -> {
|
||||||
|
R|<local>/v| = Int(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final fun main(args: R|kotlin/Array<kotlin/String?>|): R|kotlin/Unit| {
|
||||||
|
lvar a: R|kotlin/String?|
|
||||||
|
when () {
|
||||||
|
==(R|<local>/args|.R|kotlin/Array.size|, Int(1)) -> {
|
||||||
|
R|<local>/a| = R|<local>/args|.R|FakeOverride<kotlin/Array.get: R|kotlin/String?|>|(Int(0))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
R|<local>/a| = R|<local>/args|.R|kotlin/Any.toString|()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when () {
|
||||||
|
!=(R|<local>/a|, Null(null)) && R|<local>/a|.R|kotlin/Any.equals|(String(cde)) -> {
|
||||||
|
^main Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final fun run(f: R|() -> kotlin/Unit|): R|kotlin/Unit| {
|
||||||
|
^run R|<local>/f|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Unit|>|()
|
||||||
|
}
|
||||||
|
public final fun lambda(): R|kotlin/Unit| {
|
||||||
|
lvar a: R|kotlin/Int|
|
||||||
|
R|<local>/a| = Int(10)
|
||||||
|
R|/run|(<L> = run@fun <anonymous>(): R|kotlin/Unit| {
|
||||||
|
R|<local>/a| = Int(20)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
public final fun lambdaInitialization(): R|kotlin/Unit| {
|
||||||
|
lvar a: R|kotlin/Int|
|
||||||
|
R|/run|(<L> = run@fun <anonymous>(): R|kotlin/Unit| {
|
||||||
|
R|<local>/a| = Int(20)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
public final fun notAssignedWhenNotUsed(p: R|kotlin/Int|): R|kotlin/Unit| {
|
||||||
|
lvar v: R|kotlin/Int|
|
||||||
|
when () {
|
||||||
|
CMP(>, R|<local>/p|.R|kotlin/Int.compareTo|(Int(0))) -> {
|
||||||
|
R|<local>/v| = Int(1)
|
||||||
|
R|kotlin/io/print|(R|<local>/v|)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final var global: R|kotlin/Int| = Int(1)
|
||||||
|
public get(): R|kotlin/Int|
|
||||||
|
public set(value: R|kotlin/Int|): R|kotlin/Unit|
|
||||||
|
public final class C : R|kotlin/Any| {
|
||||||
|
public constructor(): R|C| {
|
||||||
|
super<R|kotlin/Any|>()
|
||||||
|
}
|
||||||
|
|
||||||
|
public final var field: R|kotlin/Int| = Int(2)
|
||||||
|
public get(): R|kotlin/Int|
|
||||||
|
public set(value: R|kotlin/Int|): R|kotlin/Unit|
|
||||||
|
|
||||||
|
public final fun foo(): R|kotlin/Unit| {
|
||||||
|
R|kotlin/io/print|(this@R|/C|.R|/C.field|)
|
||||||
|
R|kotlin/io/print|(R|/global|)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public final fun withDelegate(): R|kotlin/Unit| {
|
||||||
|
lvar s: R|kotlin/String|by Q|kotlin/properties/Delegates|.R|kotlin/properties/Delegates.notNull|<R|kotlin/String|>()
|
||||||
|
R|<local>/s| = String()
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
fun foo() {
|
fun foo() {
|
||||||
var x = 0
|
var x = 0
|
||||||
var y = 0
|
<!CAN_BE_VAL!>var<!> y = 0
|
||||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> x + y + 5
|
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> x + y + 5
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
fun foo() {
|
fun foo() {
|
||||||
var x = 0
|
var x = 0
|
||||||
var y = 0
|
<!CAN_BE_VAL!>var<!> y = 0
|
||||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> y + x + 5
|
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> y + x + 5
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+5
@@ -48,6 +48,11 @@ public class ExtendedFirDiagnosticsTestGenerated extends AbstractExtendedFirDiag
|
|||||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.kt");
|
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("VariableAssignmentChecker.kt")
|
||||||
|
public void testVariableAssignmentChecker() throws Exception {
|
||||||
|
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/VariableAssignmentChecker.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment")
|
@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.FirSessionComponent
|
import org.jetbrains.kotlin.fir.FirSessionComponent
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
|
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
|
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
|
||||||
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
|
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
|
||||||
@@ -60,15 +61,19 @@ private class ComposedDeclarationCheckers : DeclarationCheckers() {
|
|||||||
|
|
||||||
override val constructorCheckers: List<FirConstructorChecker>
|
override val constructorCheckers: List<FirConstructorChecker>
|
||||||
get() = _constructorCheckers
|
get() = _constructorCheckers
|
||||||
|
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker>
|
||||||
|
get() = _controlFlowAnalyserCheckers
|
||||||
|
|
||||||
private val _declarationCheckers: MutableList<FirBasicDeclarationChecker> = mutableListOf()
|
private val _declarationCheckers: MutableList<FirBasicDeclarationChecker> = mutableListOf()
|
||||||
private val _memberDeclarationCheckers: MutableList<FirMemberDeclarationChecker> = mutableListOf()
|
private val _memberDeclarationCheckers: MutableList<FirMemberDeclarationChecker> = mutableListOf()
|
||||||
private val _constructorCheckers: MutableList<FirConstructorChecker> = mutableListOf()
|
private val _constructorCheckers: MutableList<FirConstructorChecker> = mutableListOf()
|
||||||
|
private val _controlFlowAnalyserCheckers: MutableList<FirControlFlowChecker> = mutableListOf()
|
||||||
|
|
||||||
fun register(checkers: DeclarationCheckers) {
|
fun register(checkers: DeclarationCheckers) {
|
||||||
_declarationCheckers += checkers.declarationCheckers
|
_declarationCheckers += checkers.declarationCheckers
|
||||||
_memberDeclarationCheckers += checkers.allMemberDeclarationCheckers
|
_memberDeclarationCheckers += checkers.allMemberDeclarationCheckers
|
||||||
_constructorCheckers += checkers.allConstructorCheckers
|
_constructorCheckers += checkers.allConstructorCheckers
|
||||||
|
_controlFlowAnalyserCheckers += checkers.controlFlowAnalyserCheckers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.analysis.cfa
|
||||||
|
|
||||||
|
import kotlinx.collections.immutable.PersistentMap
|
||||||
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
|
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
|
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
|
|
||||||
|
abstract class AbstractFirPropertyInitializationChecker : FirControlFlowChecker() {
|
||||||
|
abstract override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter)
|
||||||
|
|
||||||
|
class PropertyInitializationInfo(
|
||||||
|
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
|
||||||
|
) : ControlFlowInfo<PropertyInitializationInfo, FirPropertySymbol, EventOccurrencesRange>(map) {
|
||||||
|
companion object {
|
||||||
|
val EMPTY = PropertyInitializationInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
|
||||||
|
::PropertyInitializationInfo
|
||||||
|
|
||||||
|
fun merge(other: PropertyInitializationInfo): PropertyInitializationInfo {
|
||||||
|
var result = this
|
||||||
|
for (symbol in keys.union(other.keys)) {
|
||||||
|
val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO
|
||||||
|
val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO
|
||||||
|
result = result.put(symbol, kind1 or kind2)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalPropertyCollector private constructor() : ControlFlowGraphVisitorVoid() {
|
||||||
|
companion object {
|
||||||
|
fun collect(graph: ControlFlowGraph): MutableSet<FirPropertySymbol> {
|
||||||
|
val collector = LocalPropertyCollector()
|
||||||
|
graph.traverse(TraverseDirection.Forward, collector)
|
||||||
|
return collector.symbols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val symbols: MutableSet<FirPropertySymbol> = mutableSetOf()
|
||||||
|
|
||||||
|
override fun visitNode(node: CFGNode<*>) {}
|
||||||
|
|
||||||
|
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
||||||
|
symbols += node.fir.symbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DataCollector(private val localProperties: Set<FirPropertySymbol>) :
|
||||||
|
ControlFlowGraphVisitor<PropertyInitializationInfo, Collection<PropertyInitializationInfo>>() {
|
||||||
|
override fun visitNode(node: CFGNode<*>, data: Collection<PropertyInitializationInfo>): PropertyInitializationInfo {
|
||||||
|
if (data.isEmpty()) return PropertyInitializationInfo.EMPTY
|
||||||
|
return data.reduce(PropertyInitializationInfo::merge)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitVariableAssignmentNode(
|
||||||
|
node: VariableAssignmentNode,
|
||||||
|
data: Collection<PropertyInitializationInfo>
|
||||||
|
): PropertyInitializationInfo {
|
||||||
|
val dataForNode = visitNode(node, data)
|
||||||
|
val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode
|
||||||
|
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode
|
||||||
|
return if (symbol !in localProperties) {
|
||||||
|
dataForNode
|
||||||
|
} else {
|
||||||
|
processVariableWithAssignment(dataForNode, symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitVariableDeclarationNode(
|
||||||
|
node: VariableDeclarationNode,
|
||||||
|
data: Collection<PropertyInitializationInfo>
|
||||||
|
): PropertyInitializationInfo {
|
||||||
|
val dataForNode = visitNode(node, data)
|
||||||
|
return if (node.fir.initializer == null && node.fir.delegate == null) {
|
||||||
|
dataForNode
|
||||||
|
} else {
|
||||||
|
processVariableWithAssignment(dataForNode, node.fir.symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getData(graph: ControlFlowGraph) =
|
||||||
|
graph.collectDataForNode(TraverseDirection.Forward, PropertyInitializationInfo.EMPTY, DataCollector(localProperties))
|
||||||
|
|
||||||
|
private fun processVariableWithAssignment(
|
||||||
|
dataForNode: PropertyInitializationInfo,
|
||||||
|
symbol: FirPropertySymbol
|
||||||
|
): PropertyInitializationInfo {
|
||||||
|
val existingKind = dataForNode[symbol] ?: EventOccurrencesRange.ZERO
|
||||||
|
val kind = existingKind + EventOccurrencesRange.EXACTLY_ONCE
|
||||||
|
return dataForNode.put(symbol, kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-3
@@ -5,15 +5,17 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.cfa
|
package org.jetbrains.kotlin.fir.analysis.cfa
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkersComponent
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirClass
|
import org.jetbrains.kotlin.fir.declarations.FirClass
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||||
|
|
||||||
class FirControlFlowAnalyzer {
|
class FirControlFlowAnalyzer(session: FirSession) {
|
||||||
private val propertyInitializationAnalyzer = FirPropertyInitializationAnalyzer()
|
private val checkers = session.checkersComponent.declarationCheckers.controlFlowAnalyserCheckers
|
||||||
|
|
||||||
fun analyzeClassInitializer(klass: FirClass<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
fun analyzeClassInitializer(klass: FirClass<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
if (graph.owner != null) return
|
if (graph.owner != null) return
|
||||||
@@ -22,7 +24,7 @@ class FirControlFlowAnalyzer {
|
|||||||
|
|
||||||
fun analyzeFunction(function: FirFunction<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
fun analyzeFunction(function: FirFunction<*>, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
if (graph.owner != null) return
|
if (graph.owner != null) return
|
||||||
propertyInitializationAnalyzer.analyze(graph, reporter)
|
checkers.forEach { it.analyze(graph, reporter) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun analyzePropertyInitializer(property: FirProperty, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
fun analyzePropertyInitializer(property: FirProperty, graph: ControlFlowGraph, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
|
|||||||
+7
-87
@@ -5,23 +5,24 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.cfa
|
package org.jetbrains.kotlin.fir.analysis.cfa
|
||||||
|
|
||||||
import kotlinx.collections.immutable.PersistentMap
|
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
|
||||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||||
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphVisitorVoid
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.QualifiedAccessNode
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
|
|
||||||
class FirPropertyInitializationAnalyzer {
|
object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChecker() {
|
||||||
fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
|
override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
|
||||||
val localProperties = LocalPropertyCollector.collect(graph)
|
val localProperties = LocalPropertyCollector.collect(graph)
|
||||||
// we want to analyze only properties without initializers
|
// we want to analyze only properties without initializers
|
||||||
localProperties.retainAll { it.fir.initializer == null && it.fir.delegate == null }
|
localProperties.retainAll { it.fir.initializer == null && it.fir.delegate == null }
|
||||||
if (localProperties.isEmpty()) return
|
if (localProperties.isEmpty()) return
|
||||||
val data = graph.collectDataForNode(TraverseDirection.Forward, PropertyInitializationInfo.EMPTY, DataCollector(localProperties))
|
val data = DataCollector(localProperties).getData(graph)
|
||||||
val reporterVisitor = UninitializedPropertyReporter(data, localProperties, reporter)
|
val reporterVisitor = UninitializedPropertyReporter(data, localProperties, reporter)
|
||||||
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
||||||
}
|
}
|
||||||
@@ -45,85 +46,4 @@ class FirPropertyInitializationAnalyzer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PropertyInitializationInfo(
|
|
||||||
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
|
|
||||||
) : ControlFlowInfo<PropertyInitializationInfo, FirPropertySymbol, EventOccurrencesRange>(map) {
|
|
||||||
companion object {
|
|
||||||
val EMPTY = PropertyInitializationInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
|
|
||||||
::PropertyInitializationInfo
|
|
||||||
|
|
||||||
fun merge(other: PropertyInitializationInfo): PropertyInitializationInfo {
|
|
||||||
var result = this
|
|
||||||
for (symbol in keys.union(other.keys)) {
|
|
||||||
val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO
|
|
||||||
val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO
|
|
||||||
result = result.put(symbol, kind1 or kind2)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LocalPropertyCollector private constructor() : ControlFlowGraphVisitorVoid() {
|
|
||||||
companion object {
|
|
||||||
fun collect(graph: ControlFlowGraph): MutableSet<FirPropertySymbol> {
|
|
||||||
val collector = LocalPropertyCollector()
|
|
||||||
graph.traverse(TraverseDirection.Forward, collector)
|
|
||||||
return collector.symbols
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val symbols: MutableSet<FirPropertySymbol> = mutableSetOf()
|
|
||||||
|
|
||||||
override fun visitNode(node: CFGNode<*>) {}
|
|
||||||
|
|
||||||
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
|
||||||
symbols += node.fir.symbol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DataCollector(private val localProperties: Set<FirPropertySymbol>) : ControlFlowGraphVisitor<PropertyInitializationInfo, Collection<PropertyInitializationInfo>>() {
|
|
||||||
override fun visitNode(node: CFGNode<*>, data: Collection<PropertyInitializationInfo>): PropertyInitializationInfo {
|
|
||||||
if (data.isEmpty()) return PropertyInitializationInfo.EMPTY
|
|
||||||
return data.reduce(PropertyInitializationInfo::merge)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitVariableAssignmentNode(
|
|
||||||
node: VariableAssignmentNode,
|
|
||||||
data: Collection<PropertyInitializationInfo>
|
|
||||||
): PropertyInitializationInfo {
|
|
||||||
val dataForNode = visitNode(node, data)
|
|
||||||
val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode
|
|
||||||
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode
|
|
||||||
return if (symbol !in localProperties) {
|
|
||||||
dataForNode
|
|
||||||
} else{
|
|
||||||
processVariableWithAssignment(dataForNode, symbol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitVariableDeclarationNode(
|
|
||||||
node: VariableDeclarationNode,
|
|
||||||
data: Collection<PropertyInitializationInfo>
|
|
||||||
): PropertyInitializationInfo {
|
|
||||||
val dataForNode = visitNode(node, data)
|
|
||||||
return if (node.fir.initializer == null && node.fir.delegate == null) {
|
|
||||||
dataForNode
|
|
||||||
} else {
|
|
||||||
processVariableWithAssignment(dataForNode, node.fir.symbol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processVariableWithAssignment(
|
|
||||||
dataForNode: PropertyInitializationInfo,
|
|
||||||
symbol: FirPropertySymbol
|
|
||||||
): PropertyInitializationInfo {
|
|
||||||
val existingKind = dataForNode[symbol] ?: EventOccurrencesRange.ZERO
|
|
||||||
val kind = existingKind + EventOccurrencesRange.EXACTLY_ONCE
|
|
||||||
return dataForNode.put(symbol, kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.analysis.checkers.cfa
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||||
|
|
||||||
|
abstract class FirControlFlowChecker {
|
||||||
|
abstract fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter)
|
||||||
|
}
|
||||||
+3
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
||||||
|
|
||||||
abstract class DeclarationCheckers {
|
abstract class DeclarationCheckers {
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY: DeclarationCheckers = object : DeclarationCheckers() {}
|
val EMPTY: DeclarationCheckers = object : DeclarationCheckers() {}
|
||||||
@@ -13,6 +15,7 @@ abstract class DeclarationCheckers {
|
|||||||
open val declarationCheckers: List<FirBasicDeclarationChecker> = emptyList()
|
open val declarationCheckers: List<FirBasicDeclarationChecker> = emptyList()
|
||||||
open val memberDeclarationCheckers: List<FirMemberDeclarationChecker> = emptyList()
|
open val memberDeclarationCheckers: List<FirMemberDeclarationChecker> = emptyList()
|
||||||
open val constructorCheckers: List<FirConstructorChecker> = emptyList()
|
open val constructorCheckers: List<FirConstructorChecker> = emptyList()
|
||||||
|
open val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = emptyList()
|
||||||
|
|
||||||
internal val allMemberDeclarationCheckers: List<FirMemberDeclarationChecker> get() = memberDeclarationCheckers + declarationCheckers
|
internal val allMemberDeclarationCheckers: List<FirMemberDeclarationChecker> get() = memberDeclarationCheckers + declarationCheckers
|
||||||
internal val allConstructorCheckers: List<FirConstructorChecker> get() = constructorCheckers + allMemberDeclarationCheckers
|
internal val allConstructorCheckers: List<FirConstructorChecker> get() = constructorCheckers + allMemberDeclarationCheckers
|
||||||
|
|||||||
+7
@@ -5,6 +5,9 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.FirPropertyInitializationAnalyzer
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
||||||
|
|
||||||
object CommonDeclarationCheckers : DeclarationCheckers() {
|
object CommonDeclarationCheckers : DeclarationCheckers() {
|
||||||
override val declarationCheckers: List<FirBasicDeclarationChecker> = listOf(
|
override val declarationCheckers: List<FirBasicDeclarationChecker> = listOf(
|
||||||
FirAnnotationClassDeclarationChecker,
|
FirAnnotationClassDeclarationChecker,
|
||||||
@@ -19,4 +22,8 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
|||||||
override val constructorCheckers: List<FirConstructorChecker> = listOf(
|
override val constructorCheckers: List<FirConstructorChecker> = listOf(
|
||||||
FirConstructorAllowedChecker
|
FirConstructorAllowedChecker
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = listOf(
|
||||||
|
FirPropertyInitializationAnalyzer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
+5
-1
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.*
|
import org.jetbrains.kotlin.fir.analysis.checkers.extended.*
|
||||||
|
|
||||||
object ExtendedDeclarationCheckers : DeclarationCheckers() {
|
object ExtendedDeclarationCheckers : DeclarationCheckers() {
|
||||||
@@ -18,4 +19,7 @@ object ExtendedDeclarationCheckers : DeclarationCheckers() {
|
|||||||
RedundantExplicitTypeChecker
|
RedundantExplicitTypeChecker
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
override val controlFlowAnalyserCheckers: List<FirControlFlowChecker> = listOf(
|
||||||
|
VariableAssignmentChecker
|
||||||
|
)
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.analysis.checkers.extended
|
||||||
|
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||||
|
import org.jetbrains.kotlin.fir.FirFakeSourceElement
|
||||||
|
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.TraverseDirection
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.traverse
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
|
import org.jetbrains.kotlin.fir.psi
|
||||||
|
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
|
import org.jetbrains.kotlin.fir.toFirPsiSourceElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
|
|
||||||
|
|
||||||
|
object VariableAssignmentChecker : AbstractFirPropertyInitializationChecker() {
|
||||||
|
override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter) {
|
||||||
|
val unprocessedProperties = mutableSetOf<FirPropertySymbol>()
|
||||||
|
val propertiesCharacteristics = mutableMapOf<FirPropertySymbol, EventOccurrencesRange>()
|
||||||
|
|
||||||
|
val localProperties = LocalPropertyCollector.collect(graph)
|
||||||
|
if (localProperties.isEmpty()) return
|
||||||
|
|
||||||
|
val data = DataCollector(localProperties).getData(graph)
|
||||||
|
val reporterVisitor = UninitializedPropertyReporter(data, localProperties, unprocessedProperties, propertiesCharacteristics)
|
||||||
|
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
||||||
|
|
||||||
|
for (property in unprocessedProperties) {
|
||||||
|
if (property.fir.source is FirFakeSourceElement<*>) continue
|
||||||
|
if (property.callableId.callableName.asString() == "<destruct>") continue
|
||||||
|
propertiesCharacteristics[property] = EventOccurrencesRange.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastDestructuringSource: FirSourceElement? = null
|
||||||
|
var destructuringCanBeVal = false
|
||||||
|
var lastDestructuredVariables = 0
|
||||||
|
|
||||||
|
for ((symbol, value) in propertiesCharacteristics) {
|
||||||
|
val source = symbol.getValOrVarSource
|
||||||
|
if (symbol.callableId.callableName.asString() == "<destruct>") {
|
||||||
|
lastDestructuringSource = symbol.getValOrVarSource
|
||||||
|
val childrenCount = symbol.fir.psi?.children?.size ?: continue
|
||||||
|
lastDestructuredVariables = childrenCount - 1 // -1 cuz we don't need expression node after equals operator
|
||||||
|
destructuringCanBeVal = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDestructuringSource != null) {
|
||||||
|
// if this is the last variable in destructuring declaration and destructuringCanBeVal == true and it can be val
|
||||||
|
if (lastDestructuredVariables == 1 && destructuringCanBeVal && canBeVal(symbol, value)) {
|
||||||
|
reporter.report(lastDestructuringSource, FirErrors.CAN_BE_VAL)
|
||||||
|
lastDestructuringSource = null
|
||||||
|
} else if (!canBeVal(symbol, value)) {
|
||||||
|
destructuringCanBeVal = false
|
||||||
|
}
|
||||||
|
lastDestructuredVariables--
|
||||||
|
} else if (canBeVal(symbol, value) && symbol.fir.delegate == null ) {
|
||||||
|
reporter.report(source, FirErrors.CAN_BE_VAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun canBeVal(symbol: FirPropertySymbol, value: EventOccurrencesRange) =
|
||||||
|
(value == EventOccurrencesRange.EXACTLY_ONCE
|
||||||
|
|| value == EventOccurrencesRange.AT_MOST_ONCE
|
||||||
|
|| value == EventOccurrencesRange.ZERO
|
||||||
|
) && symbol.fir.isVar
|
||||||
|
|
||||||
|
private class UninitializedPropertyReporter(
|
||||||
|
val data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
||||||
|
val localProperties: Set<FirPropertySymbol>,
|
||||||
|
val unprocessedProperties: MutableSet<FirPropertySymbol>,
|
||||||
|
val propertiesCharacteristics: MutableMap<FirPropertySymbol, EventOccurrencesRange>
|
||||||
|
) : ControlFlowGraphVisitorVoid() {
|
||||||
|
override fun visitNode(node: CFGNode<*>) {}
|
||||||
|
|
||||||
|
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
|
||||||
|
val symbol = (node.fir.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirPropertySymbol
|
||||||
|
?: return
|
||||||
|
if (symbol !in localProperties) return
|
||||||
|
unprocessedProperties.remove(symbol)
|
||||||
|
|
||||||
|
val currentCharacteristic = propertiesCharacteristics.getOrDefault(symbol, EventOccurrencesRange.ZERO)
|
||||||
|
propertiesCharacteristics[symbol] = currentCharacteristic.or(data.getValue(node)[symbol] ?: EventOccurrencesRange.ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
||||||
|
val symbol = node.fir.symbol
|
||||||
|
if (node.fir.initializer == null && node.fir.delegate == null) {
|
||||||
|
unprocessedProperties.add(symbol)
|
||||||
|
} else {
|
||||||
|
propertiesCharacteristics[symbol] = EventOccurrencesRange.AT_MOST_ONCE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val FirPropertySymbol.getValOrVarSource
|
||||||
|
get() = (fir.psi as? KtProperty)?.valOrVarKeyword?.toFirPsiSourceElement()
|
||||||
|
?: fir.psi?.firstChild?.toFirPsiSourceElement()
|
||||||
|
?: fir.source
|
||||||
|
}
|
||||||
+1
-1
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
|
|||||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||||
|
|
||||||
class ControlFlowAnalysisDiagnosticComponent(collector: AbstractDiagnosticCollector) : AbstractDiagnosticCollectorComponent(collector) {
|
class ControlFlowAnalysisDiagnosticComponent(collector: AbstractDiagnosticCollector) : AbstractDiagnosticCollectorComponent(collector) {
|
||||||
private val controlFlowAnalyzer = FirControlFlowAnalyzer()
|
private val controlFlowAnalyzer = FirControlFlowAnalyzer(session)
|
||||||
|
|
||||||
// ------------------------------- Class initializer -------------------------------
|
// ------------------------------- Class initializer -------------------------------
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -87,7 +87,6 @@ object FirErrors {
|
|||||||
val REDUNDANT_MODALITY_MODIFIER by warning0<FirSourceElement, PsiElement>()
|
val REDUNDANT_MODALITY_MODIFIER by warning0<FirSourceElement, PsiElement>()
|
||||||
val REDUNDANT_RETURN_UNIT_TYPE by warning0<FirSourceElement, PsiTypeElement>()
|
val REDUNDANT_RETURN_UNIT_TYPE by warning0<FirSourceElement, PsiTypeElement>()
|
||||||
val REDUNDANT_EXPLICIT_TYPE by warning0<FirSourceElement, PsiElement>()
|
val REDUNDANT_EXPLICIT_TYPE by warning0<FirSourceElement, PsiElement>()
|
||||||
|
val CAN_BE_VAL by warning0<FirSourceElement, PsiElement>()
|
||||||
val CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT by warning0<FirSourceElement, PsiElement>()
|
val CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT by warning0<FirSourceElement, PsiElement>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+12
-6
@@ -12,16 +12,22 @@ enum class EventOccurrencesRange(private val left: Int, private val right: Int)
|
|||||||
ZERO(0, 0), // 0..0
|
ZERO(0, 0), // 0..0
|
||||||
AT_MOST_ONCE(0, 1), // 0..1
|
AT_MOST_ONCE(0, 1), // 0..1
|
||||||
EXACTLY_ONCE(1, 1), // 1..1
|
EXACTLY_ONCE(1, 1), // 1..1
|
||||||
AT_LEAST_ONCE(1, 2), // 1..*
|
AT_LEAST_ONCE(1, 3), // 1..*
|
||||||
UNKNOWN(0, 2); // 0..*
|
MORE_THAN_ONCE(2, 3), // 2..*
|
||||||
|
UNKNOWN(0, 3); // 0..*
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun fromRange(left: Int, right: Int): EventOccurrencesRange = when (min(left, 1) to min(right, 2)) {
|
private fun fromRange(left: Int, right: Int): EventOccurrencesRange = when (min(left, 2) to min(right, 3)) {
|
||||||
0 to 0 -> ZERO
|
0 to 0 -> ZERO
|
||||||
0 to 1 -> AT_MOST_ONCE
|
0 to 1 -> AT_MOST_ONCE
|
||||||
|
0 to 2 -> UNKNOWN
|
||||||
|
0 to 3 -> UNKNOWN
|
||||||
1 to 1 -> EXACTLY_ONCE
|
1 to 1 -> EXACTLY_ONCE
|
||||||
1 to 2 -> AT_LEAST_ONCE
|
1 to 2 -> AT_LEAST_ONCE
|
||||||
0 to 2 -> UNKNOWN
|
1 to 3 -> AT_LEAST_ONCE
|
||||||
|
2 to 2 -> MORE_THAN_ONCE
|
||||||
|
2 to 3 -> MORE_THAN_ONCE
|
||||||
|
3 to 3 -> MORE_THAN_ONCE
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,5 +40,5 @@ enum class EventOccurrencesRange(private val left: Int, private val right: Int)
|
|||||||
operator fun plus(other: EventOccurrencesRange): EventOccurrencesRange = Companion.plus(this, other)
|
operator fun plus(other: EventOccurrencesRange): EventOccurrencesRange = Companion.plus(this, other)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun EventOccurrencesRange.isDefinitelyVisited(): Boolean = this == EventOccurrencesRange.EXACTLY_ONCE || this == EventOccurrencesRange.AT_LEAST_ONCE
|
fun EventOccurrencesRange.isDefinitelyVisited(): Boolean = this == EventOccurrencesRange.EXACTLY_ONCE || this == EventOccurrencesRange.AT_LEAST_ONCE || this == EventOccurrencesRange.MORE_THAN_ONCE
|
||||||
fun EventOccurrencesRange.canBeRevisited(): Boolean = this == EventOccurrencesRange.UNKNOWN || this == EventOccurrencesRange.AT_LEAST_ONCE
|
fun EventOccurrencesRange.canBeRevisited(): Boolean = this == EventOccurrencesRange.UNKNOWN || this == EventOccurrencesRange.AT_LEAST_ONCE || this == EventOccurrencesRange.MORE_THAN_ONCE
|
||||||
|
|||||||
Reference in New Issue
Block a user