diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.kt new file mode 100644 index 00000000000..b08344fe660 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.kt @@ -0,0 +1,35 @@ +import kotlin.contracts.* + +@OptIn(ExperimentalContracts::class) +fun foo(x: String?): Any? { + contract { + returns(null) implies (x != null) + } + + if (true) { + throw java.lang.IllegalArgumentException() + } + + return x +} + +@OptIn(ExperimentalContracts::class) +fun bar(x: String?): Any? { + contract { + returns() implies (x != null) + } + + if (x == null) { + return x + } + return x +} + +@OptIn(ExperimentalContracts::class) +fun baz(x: String?): Any? { + contract { + returns(null) implies (x != null) + } + + return x +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.txt new file mode 100644 index 00000000000..c647b287806 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.txt @@ -0,0 +1,40 @@ +FILE: notNull.kt + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun foo(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NULL) -> x != null + > + { + [StubStatement] + when () { + Boolean(true) -> { + throw Q|java/lang|.R|java/lang/IllegalArgumentException.IllegalArgumentException|() + } + } + + ^foo R|/x| + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun bar(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(WILDCARD) -> x != null + > + { + [StubStatement] + when () { + ==(R|/x|, Null(null)) -> { + ^bar R|/x| + } + } + + ^bar R|/x| + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun baz(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NULL) -> x != null + > + { + [StubStatement] + ^baz R|/x| + } diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.kt new file mode 100644 index 00000000000..e81cb2ee4eb --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.kt @@ -0,0 +1,18 @@ +import kotlin.contracts.* + +@OptIn(ExperimentalContracts::class) +fun Any?.isNotNull(): Boolean { + contract { + returns(true) implies (this@isNotNull != null) + } + return this != null +} + +@OptIn(ExperimentalContracts::class) +val Any?.isNotNull: Boolean + get() { + contract { + returns(true) implies (this@isNotNull != null) + } + return this@isNotNull != null + } \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.txt new file mode 100644 index 00000000000..315c65699a5 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.txt @@ -0,0 +1,20 @@ +FILE: propertyGetter.kt + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun R|kotlin/Any?|.isNotNull(): R|kotlin/Boolean| + [R|Contract description] + < + Returns(TRUE) -> this != null + > + { + [StubStatement] + ^isNotNull !=(this@R|/isNotNull|, Null(null)) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final val R|kotlin/Any?|.isNotNull: R|kotlin/Boolean| + public get(): R|kotlin/Boolean| + [R|Contract description] + < + Returns(TRUE) -> this != null + > + { + [StubStatement] + ^ !=(this@R|/isNotNull|, Null(null)) + } diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.kt new file mode 100644 index 00000000000..403dd4ca9d7 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.kt @@ -0,0 +1,121 @@ +import kotlin.contracts.* + +@OptIn(ExperimentalContracts::class) +fun test1(x: String?): Any? { + contract { + returnsNotNull() implies (!(x == null)) + } + + return x +} + +@OptIn(ExperimentalContracts::class) +fun test2(x: String?): Any? { + contract { + returnsNotNull() implies (x is String && x != null) + } + + return x +} + +@OptIn(ExperimentalContracts::class) +fun test3(x: String?): Any? { + contract { + returnsNotNull() implies (x is String? || x is Any?) + } + + return x +} + +@OptIn(ExperimentalContracts::class) +fun test4(x: String?, y: String?): Any? { + contract { + returns(true) implies (x != null && y != null) + } + + return x != null && y != null +} + +@OptIn(ExperimentalContracts::class) +fun test5(x: Any?): Any? { + contract { + returns(true) implies (x != null || x is Any) + } + + return x != null +} + +@OptIn(ExperimentalContracts::class) +fun test6(x: Any?): Any? { + contract { + returns(true) implies (x is String? && x != null) + } + + return x is String +} + +@OptIn(ExperimentalContracts::class) +fun test7(x: Any?): Any? { + contract { + returns(true) implies (x is String? && x != null || x is Int) + } + + return x is String +} + +@OptIn(ExperimentalContracts::class) +fun test8(x: Any?): Any? { + contract { + returns(true) implies (x is String || x is Int) + } + + return x is String || x is Int +} + +@OptIn(ExperimentalContracts::class) +fun test9(x: Any?): Any? { + contract { + returns(true) implies (x is String || x is Int) + } + + if(x is String){ + return true + } + return x is Int +} + +@OptIn(ExperimentalContracts::class) +fun test10(x: Any?): Any? { + contract { + returns(true) implies (x is Comparable<*> || x is CharSequence) + } + + return x is String +} + +@OptIn(ExperimentalContracts::class) +fun test11(x: Any?): Any? { + contract { + returns(true) implies (x is Comparable<*> && x is CharSequence) + } + + return x is String +} + +@OptIn(ExperimentalContracts::class) +fun test12(x: Any?): Any? { + contract { + returns(true) implies (x is Comparable<*> && (x is CharSequence || x is Number)) + } + + return x is String || x is Int +} + +@OptIn(ExperimentalContracts::class) +fun test13(x: Any?): Any? { + contract { + returns(true) implies (!(x !is Comparable<*> || (x !is CharSequence && !(x is Number)))) + } + + return x is String || x is Int +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.txt new file mode 100644 index 00000000000..eac7a472ce7 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.txt @@ -0,0 +1,118 @@ +FILE: conditionLogic.kt + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test1(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NOT_NULL) -> !x == null + > + { + [StubStatement] + ^test1 R|/x| + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test2(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + > + { + [StubStatement] + ^test2 R|/x| + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test3(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NOT_NULL) -> x is kotlin/String? || x is kotlin/Any? + > + { + [StubStatement] + ^test3 R|/x| + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test4(x: R|kotlin/String?|, y: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x != null && y != null + > + { + [StubStatement] + ^test4 !=(R|/x|, Null(null)) && !=(R|/y|, Null(null)) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test5(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x != null || x is kotlin/Any + > + { + [StubStatement] + ^test5 !=(R|/x|, Null(null)) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test6(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + > + { + [StubStatement] + ^test6 (R|/x| is R|kotlin/String|) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test7(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + > + { + [StubStatement] + ^test7 (R|/x| is R|kotlin/String|) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test8(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x is kotlin/String || x is kotlin/Int + > + { + [StubStatement] + ^test8 (R|/x| is R|kotlin/String|) || (R|/x| is R|kotlin/Int|) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test9(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x is kotlin/String || x is kotlin/Int + > + { + [StubStatement] + when () { + (R|/x| is R|kotlin/String|) -> { + ^test9 Boolean(true) + } + } + + ^test9 (R|/x| is R|kotlin/Int|) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test10(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x is kotlin/Comparable<*> || x is kotlin/CharSequence + > + { + [StubStatement] + ^test10 (R|/x| is R|kotlin/String|) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test11(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + > + { + [StubStatement] + ^test11 (R|/x| is R|kotlin/String|) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test12(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + > + { + [StubStatement] + ^test12 (R|/x| is R|kotlin/String|) || (R|/x| is R|kotlin/Int|) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test13(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + > + { + [StubStatement] + ^test13 (R|/x| is R|kotlin/String|) || (R|/x| is R|kotlin/Int|) + } diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.kt new file mode 100644 index 00000000000..794a43e3a21 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.kt @@ -0,0 +1,57 @@ +import kotlin.contracts.* + +@OptIn(ExperimentalContracts::class) +fun wrongFalse(x: String?): Boolean { + contract { + returns(false) implies (x != null) + } + + return true +} + +@OptIn(ExperimentalContracts::class) +fun wrongTrue(x: String?): Boolean { + contract { + returns(true) implies (x != null) + } + + return false +} + +@OptIn(ExperimentalContracts::class) +fun nullableConstant(x: String?): Any? { + contract { + returns(null) implies (x != null) + } + + return 1 +} + +fun string() : String = "" + +@OptIn(ExperimentalContracts::class) +fun nullableReturn(x: String?): Any? { + contract { + returns(null) implies (x != null) + } + + return string() +} + +@OptIn(ExperimentalContracts::class) +fun returnsNull(x: String?): Any? { + contract { + returnsNotNull() implies (x != null) + } + + return null +} + +@OptIn(ExperimentalContracts::class) +fun wrongReturnType(x: String?): Any? { + contract { + returns(true) implies (x != null) + } + + return "true" +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.txt new file mode 100644 index 00000000000..668bfa3f9d1 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.txt @@ -0,0 +1,58 @@ +FILE: inapplicable.kt + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun wrongFalse(x: R|kotlin/String?|): R|kotlin/Boolean| + [R|Contract description] + < + Returns(FALSE) -> x != null + > + { + [StubStatement] + ^wrongFalse Boolean(true) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun wrongTrue(x: R|kotlin/String?|): R|kotlin/Boolean| + [R|Contract description] + < + Returns(TRUE) -> x != null + > + { + [StubStatement] + ^wrongTrue Boolean(false) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun nullableConstant(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NULL) -> x != null + > + { + [StubStatement] + ^nullableConstant Int(1) + } + public final fun string(): R|kotlin/String| { + ^string String() + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun nullableReturn(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NULL) -> x != null + > + { + [StubStatement] + ^nullableReturn R|/string|() + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun returnsNull(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NOT_NULL) -> x != null + > + { + [StubStatement] + ^returnsNull Null(null) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun wrongReturnType(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x != null + > + { + [StubStatement] + ^wrongReturnType String(true) + } diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.kt new file mode 100644 index 00000000000..2b9f4792413 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.kt @@ -0,0 +1,56 @@ +import kotlin.contracts.* + +@OptIn(ExperimentalContracts::class) +fun test1(x: String?): Boolean { + contract { + returns(false) implies (x != null) + } + + return x == null +} + +@OptIn(ExperimentalContracts::class) +fun test2(x: String?): Boolean { + contract { + returns(true) implies (x != null) + } + + return x != null +} + +@OptIn(ExperimentalContracts::class) +fun test3(x: String?): Any? { + contract { + returns(true) implies (x != null) + } + + if(x != null){ + return true + } + + return false +} + +@OptIn(ExperimentalContracts::class) +fun test4(x: String?): Any? { + contract { + returns(true) implies (x != null) + } + + val y = x + + if(y != null){ + return true + } + + return false +} + +@OptIn(ExperimentalContracts::class) +fun test5(x: String?): Any? { + contract { + returns(true) implies (x != null) + } + + return test2(x) +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.txt new file mode 100644 index 00000000000..df8552b3ffd --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.txt @@ -0,0 +1,59 @@ +FILE: notNull.kt + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test1(x: R|kotlin/String?|): R|kotlin/Boolean| + [R|Contract description] + < + Returns(FALSE) -> x != null + > + { + [StubStatement] + ^test1 ==(R|/x|, Null(null)) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test2(x: R|kotlin/String?|): R|kotlin/Boolean| + [R|Contract description] + < + Returns(TRUE) -> x != null + > + { + [StubStatement] + ^test2 !=(R|/x|, Null(null)) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test3(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x != null + > + { + [StubStatement] + when () { + !=(R|/x|, Null(null)) -> { + ^test3 Boolean(true) + } + } + + ^test3 Boolean(false) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test4(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x != null + > + { + [StubStatement] + lval y: R|kotlin/String?| = R|/x| + when () { + !=(R|/y|, Null(null)) -> { + ^test4 Boolean(true) + } + } + + ^test4 Boolean(false) + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test5(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x != null + > + { + [StubStatement] + ^test5 R|/test2|(R|/x|) + } diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt index f41966ee0c7..4ed8d1f9864 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt @@ -6,16 +6,16 @@ interface A { var Any?.isNotNull: Boolean get() { - contract { + contract { returns(true) implies (this@isNotNull != null) - } + } return this != null } set(value) { - contract { + contract { returns() implies (this@isNotNull != null) require(this != null) - } + } } fun test_1(a: A?) { diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.kt new file mode 100644 index 00000000000..6e14e47c283 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.kt @@ -0,0 +1,19 @@ +import kotlin.contracts.* + +@OptIn(ExperimentalContracts::class) +fun test1(x: String?): Int? { + contract { + returnsNotNull() implies (x != null) + } + + return x?.length +} + +@OptIn(ExperimentalContracts::class) +fun test2(x: String?): Int? { + contract { + returnsNotNull() implies (x is Boolean) + } + + return x?.length +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.txt new file mode 100644 index 00000000000..95b83f45c51 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.txt @@ -0,0 +1,19 @@ +FILE: safeCall.kt + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test1(x: R|kotlin/String?|): R|kotlin/Int?| + [R|Contract description] + < + Returns(NOT_NULL) -> x != null + > + { + [StubStatement] + ^test1 R|/x|?.{ $subj$.R|kotlin/String.length| } + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test2(x: R|kotlin/String?|): R|kotlin/Int?| + [R|Contract description] + < + Returns(NOT_NULL) -> x is kotlin/Boolean + > + { + [StubStatement] + ^test2 R|/x|?.{ $subj$.R|kotlin/String.length| } + } diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.kt new file mode 100644 index 00000000000..98be9306a5a --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.kt @@ -0,0 +1,39 @@ +import kotlin.contracts.* + +@OptIn(ExperimentalContracts::class) +fun test1(x: String?): Any? { + contract { + returnsNotNull() implies (x != null) + } + + return x +} + +@OptIn(ExperimentalContracts::class) +fun test2(x: String?): Any? { + contract { + returns(true) implies (x != null) + } + + return if(x != null) true else false +} + +@OptIn(ExperimentalContracts::class) +fun test3(x: Any?): Any? { + contract { + returnsNotNull() implies (x != null) + } + return if(true) x else null +} + +@OptIn(ExperimentalContracts::class) +fun test4(x: Any?): Any? { + contract { + returnsNotNull() implies (x != null) + } + return if(x != null) { + if (true) x else false + } else { + null + } +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.txt new file mode 100644 index 00000000000..db6b38a1be5 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.txt @@ -0,0 +1,69 @@ +FILE: trickyCases.kt + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test1(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NOT_NULL) -> x != null + > + { + [StubStatement] + ^test1 R|/x| + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test2(x: R|kotlin/String?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(TRUE) -> x != null + > + { + [StubStatement] + ^test2 when () { + !=(R|/x|, Null(null)) -> { + Boolean(true) + } + else -> { + Boolean(false) + } + } + + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test3(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NOT_NULL) -> x != null + > + { + [StubStatement] + ^test3 when () { + Boolean(true) -> { + R|/x| + } + else -> { + Null(null) + } + } + + } + @R|kotlin/OptIn|(vararg((Q|kotlin/contracts/ExperimentalContracts|))) public final fun test4(x: R|kotlin/Any?|): R|kotlin/Any?| + [R|Contract description] + < + Returns(NOT_NULL) -> x != null + > + { + [StubStatement] + ^test4 when () { + !=(R|/x|, Null(null)) -> { + when () { + Boolean(true) -> { + R|/x| + } + else -> { + Boolean(false) + } + } + + } + else -> { + Null(null) + } + } + + } diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithStdlibTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithStdlibTestGenerated.java index 906b8dd3030..85dec654fb3 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithStdlibTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithStdlibTestGenerated.java @@ -590,6 +590,29 @@ public class FirDiagnosticsWithStdlibTestGenerated extends AbstractFirDiagnostic runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/callsInPlace/toLocalVariables.kt"); } } + + @TestMetadata("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReturnsImplies extends AbstractFirDiagnosticsWithStdlibTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInReturnsImplies() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @TestMetadata("notNull.kt") + public void testNotNull() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/notNull.kt"); + } + + @TestMetadata("propertyGetter.kt") + public void testPropertyGetter() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/bad/returnsImplies/propertyGetter.kt"); + } + } } @TestMetadata("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good") @@ -674,16 +697,31 @@ public class FirDiagnosticsWithStdlibTestGenerated extends AbstractFirDiagnostic runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/booleanOperators.kt"); } + @TestMetadata("conditionLogic.kt") + public void testConditionLogic() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/conditionLogic.kt"); + } + @TestMetadata("eqNotEq.kt") public void testEqNotEq() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/eqNotEq.kt"); } + @TestMetadata("inapplicable.kt") + public void testInapplicable() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/inapplicable.kt"); + } + @TestMetadata("namedArguments.kt") public void testNamedArguments() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/namedArguments.kt"); } + @TestMetadata("notNull.kt") + public void testNotNull() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/notNull.kt"); + } + @TestMetadata("propertyAccessors.kt") public void testPropertyAccessors() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt"); @@ -694,6 +732,16 @@ public class FirDiagnosticsWithStdlibTestGenerated extends AbstractFirDiagnostic runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/receivers.kt"); } + @TestMetadata("safeCall.kt") + public void testSafeCall() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/safeCall.kt"); + } + + @TestMetadata("trickyCases.kt") + public void testTrickyCases() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/trickyCases.kt"); + } + @TestMetadata("typePredicate.kt") public void testTypePredicate() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/typePredicate.kt"); diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt new file mode 100644 index 00000000000..04905a1ded1 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt @@ -0,0 +1,212 @@ +/* + * 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 org.jetbrains.kotlin.fir.BuiltinTypes +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker +import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription +import org.jetbrains.kotlin.fir.contracts.description.* +import org.jetbrains.kotlin.fir.declarations.FirContractDescriptionOwner +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.resolve.dfa.* +import org.jetbrains.kotlin.fir.resolve.dfa.cfg.BlockExitNode +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.JumpNode +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.typeContext +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.types.model.KotlinTypeMarker +import org.jetbrains.kotlin.types.model.TypeCheckerProviderContext +import org.jetbrains.kotlin.utils.addIfNotNull + +object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { + + override fun analyze(graph: ControlFlowGraph, reporter: DiagnosticReporter) { + val function = graph.declaration as? FirFunction<*> ?: return + val graphRef = function.controlFlowGraphReference as FirControlFlowGraphReferenceImpl + val dataFlowInfo = graphRef.dataFlowInfo + if (function !is FirContractDescriptionOwner || dataFlowInfo == null) return + + val effects = (function.contractDescription as? FirResolvedContractDescription)?.effects + ?.filter { it is ConeConditionalEffectDeclaration && it.effect is ConeReturnsEffectDeclaration } + + if (effects.isNullOrEmpty()) return + + val logicSystem = object : PersistentLogicSystem(function.session.typeContext) { + override fun processUpdatedReceiverVariable(flow: PersistentFlow, variable: RealVariable) = + throw IllegalStateException("Receiver variable update is not possible for this logic system") + + override fun updateAllReceivers(flow: PersistentFlow) = + throw IllegalStateException("Update of all receivers is not possible for this logic system") + } + + effects.forEach { effect -> + val wrongCondition = graph.exitNode.previousCfgNodes.any { + isWrongConditionOnNode(it, effect as ConeConditionalEffectDeclaration, function, logicSystem, dataFlowInfo) + } + + if (wrongCondition) { + function.contractDescription.source?.let { + reporter.report(FirErrors.WRONG_IMPLIES_CONDITION.on(it)) + } + } + } + } + + private fun isWrongConditionOnNode( + node: CFGNode<*>, + effectDeclaration: ConeConditionalEffectDeclaration, + function: FirFunction<*>, + logicSystem: LogicSystem, + dataFlowInfo: DataFlowInfo + ): Boolean { + val effect = effectDeclaration.effect as ConeReturnsEffectDeclaration + val builtinTypes = function.session.builtinTypes + val typeContext = function.session.typeContext + val flow = dataFlowInfo.flowOnNodes.getValue(node) as PersistentFlow + + val isReturn = node is JumpNode && node.fir is FirReturnExpression + val resultExpression = if (isReturn) (node.fir as FirReturnExpression).result else node.fir + + val expressionType = (resultExpression as? FirExpression)?.typeRef?.coneType + if (expressionType == builtinTypes.nothingType.type) return false + + if (isReturn && resultExpression is FirWhenExpression) { + return node.collectBranchExits().any { + isWrongConditionOnNode(it, effectDeclaration, function, logicSystem, dataFlowInfo) + } + } + + var typeStatements: TypeStatements = flow.approvedTypeStatements + + if (effect.value != ConeConstantReference.WILDCARD) { + val operation = effect.value.toOperation() + if (expressionType != null && expressionType.isInapplicableWith(operation, function.session)) return false + + if (resultExpression is FirConstExpression<*>) { + if (!resultExpression.isApplicableWith(operation)) return false + } else { + val resultVar = dataFlowInfo.variableStorage.getOrCreateVariable(flow, resultExpression) + typeStatements = logicSystem.approveOperationStatement(flow, OperationStatement(resultVar, operation), builtinTypes) + } + } + + val conditionStatements = + effectDeclaration.condition.buildTypeStatements(function, logicSystem, dataFlowInfo.variableStorage, flow) ?: return false + + for ((realVar, requiredTypeStatement) in conditionStatements) { + val fixedRealVar = typeStatements.keys.find { it.identifier == realVar.identifier } ?: realVar + val resultTypeStatement = typeStatements[fixedRealVar] + + val resultType = mutableListOf().apply { + addIfNotNull(function.getParameterType(fixedRealVar.identifier.symbol)) + if (resultTypeStatement != null) addAll(resultTypeStatement.exactType) + }.let { typeContext.intersectTypesOrNull(it) } + + val requiredType = typeContext.intersectTypesOrNull(requiredTypeStatement.exactType.toList()) + if (requiredType != null && !requiredType.isSupertypeOf(typeContext, resultType)) return true + } + return false + } + + private fun LogicSystem.approveOperationStatement( + flow: PersistentFlow, + statement: OperationStatement, + builtinTypes: BuiltinTypes + ): MutableTypeStatements { + val newTypeStatements: MutableTypeStatements = mutableMapOf() + + approveStatementsTo(newTypeStatements, flow, statement, flow.logicStatements.flatMap { it.value }) + newTypeStatements.mergeTypeStatements(flow.approvedTypeStatements) + + val variable = statement.variable + if (variable.isReal()) { + if (statement.operation == Operation.NotEqNull) { + newTypeStatements.addStatement(variable, simpleTypeStatement(variable, true, builtinTypes.anyType.type)) + } else if (statement.operation == Operation.EqNull) { + newTypeStatements.addStatement(variable, simpleTypeStatement(variable, false, builtinTypes.anyType.type)) + } + } + + return newTypeStatements + } + + private fun ConeBooleanExpression.buildTypeStatements( + function: FirFunction<*>, + logicSystem: LogicSystem<*>, + variableStorage: VariableStorage, + flow: Flow + ): MutableTypeStatements? = when (this) { + is ConeBinaryLogicExpression -> { + val left = left.buildTypeStatements(function, logicSystem, variableStorage, flow) + val right = right.buildTypeStatements(function, logicSystem, variableStorage, flow) + if (left != null && right != null) { + if (kind == LogicOperationKind.AND) { + left.apply { mergeTypeStatements(right) } + } else logicSystem.orForTypeStatements(left, right) + } else (left ?: right) + } + is ConeIsInstancePredicate -> { + val fir = function.getParameterSymbol(arg.parameterIndex).fir + val realVar = variableStorage.getOrCreateRealVariable(flow, fir.symbol, fir) + realVar?.to(simpleTypeStatement(realVar, !isNegated, type))?.let { mutableMapOf(it) } + } + is ConeIsNullPredicate -> { + val fir = function.getParameterSymbol(arg.parameterIndex).fir + val realVar = variableStorage.getOrCreateRealVariable(flow, fir.symbol, fir) + realVar?.to(simpleTypeStatement(realVar, isNegated, function.session.builtinTypes.anyType.type))?.let { mutableMapOf(it) } + } + is ConeLogicalNot -> arg.buildTypeStatements(function, logicSystem, variableStorage, flow) + ?.mapValuesTo(mutableMapOf()) { (_, value) -> value.invert() } + + else -> null + } + + private fun ConeKotlinType.isInapplicableWith(operation: Operation, session: FirSession): Boolean { + return (operation == Operation.EqFalse || operation == Operation.EqTrue) + && !AbstractTypeChecker.isSubtypeOf(session.typeContext, session.builtinTypes.booleanType.type, this) + || operation == Operation.EqNull && !isNullable + } + + private fun FirConstExpression<*>.isApplicableWith(operation: Operation): Boolean = when { + kind == FirConstKind.Null -> operation == Operation.EqNull + kind == FirConstKind.Boolean && operation == Operation.EqTrue -> (value as Boolean) + kind == FirConstKind.Boolean && operation == Operation.EqFalse -> !(value as Boolean) + else -> true + } + + fun KotlinTypeMarker.isSupertypeOf(context: TypeCheckerProviderContext, type: KotlinTypeMarker?) = + type != null && AbstractTypeChecker.isSubtypeOf(context, type, this) + + private fun simpleTypeStatement(realVar: RealVariable, exactType: Boolean, type: ConeKotlinType): MutableTypeStatement { + return MutableTypeStatement( + realVar, + if (exactType) linkedSetOf(type) else linkedSetOf(), + if (!exactType) linkedSetOf(type) else linkedSetOf() + ) + } + + private fun CFGNode<*>.collectBranchExits(nodes: MutableList> = mutableListOf()): List> { + if (this is BlockExitNode) { + nodes += previousCfgNodes + } else previousCfgNodes.forEach { it.collectBranchExits(nodes) } + return nodes + } + + private fun FirFunction<*>.getParameterType(symbol: AbstractFirBasedSymbol<*>): ConeKotlinType? { + return (if (this.symbol == symbol) receiverTypeRef else valueParameters.find { it.symbol == symbol }?.returnTypeRef)?.coneType + } + + private fun FirFunction<*>.getParameterSymbol(index: Int): AbstractFirBasedSymbol<*> { + return if (index == -1) this.symbol else this.valueParameters[index].symbol + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DefaultDeclarationCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DefaultDeclarationCheckers.kt index 835dac35f0f..826ce42c9cb 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DefaultDeclarationCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DefaultDeclarationCheckers.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration import org.jetbrains.kotlin.fir.analysis.cfa.FirCallsEffectAnalyzer import org.jetbrains.kotlin.fir.analysis.cfa.FirPropertyInitializationAnalyzer +import org.jetbrains.kotlin.fir.analysis.cfa.FirReturnsImpliesAnalyzer import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker object CommonDeclarationCheckers : DeclarationCheckers() { @@ -42,6 +43,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() { override val controlFlowAnalyserCheckers: List = listOf( FirPropertyInitializationAnalyzer, - FirCallsEffectAnalyzer + FirCallsEffectAnalyzer, + FirReturnsImpliesAnalyzer ) } \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 935718cc5f6..64a09f6bbe2 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME -import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FIR import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NULLABLE_STRING import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.PROPERTY_NAME import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOL @@ -255,7 +254,7 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { TO_STRING ) map.put(LEAKED_IN_PLACE_LAMBDA, "Leaked in-place lambda: {2}", SYMBOL) - + map.put(FirErrors.WRONG_IMPLIES_CONDITION, "Wrong implies condition") // Extended checkers group // map.put(REDUNDANT_VISIBILITY_MODIFIER, ...) // & diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index add5b0dc090..cf53cdb0562 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -131,6 +131,7 @@ object FirErrors { val UNINITIALIZED_VARIABLE by error1() val WRONG_INVOCATION_KIND by warning3, EventOccurrencesRange, EventOccurrencesRange>() val LEAKED_IN_PLACE_LAMBDA by error1>() + val WRONG_IMPLIES_CONDITION by error0() // Extended checkers group val REDUNDANT_VISIBILITY_MODIFIER by warning0() diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/model.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/model.kt index dba9b452adc..33167fad3e8 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/model.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/model.kt @@ -177,7 +177,7 @@ class MutableTypeStatement( override val isEmpty: Boolean get() = exactType.isEmpty() && exactType.isEmpty() - override fun invert(): TypeStatement { + override fun invert(): MutableTypeStatement { return MutableTypeStatement( variable, LinkedHashSet(exactNotType), diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt index 81c725e8825..34a41cfc1e7 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/util.kt @@ -79,7 +79,7 @@ internal fun FirFunctionCall.isBooleanNot(): Boolean { return symbol.callableId == FirDataFlowAnalyzer.KOTLIN_BOOLEAN_NOT } -internal fun ConeConstantReference.toOperation(): Operation = when (this) { +fun ConeConstantReference.toOperation(): Operation = when (this) { ConeConstantReference.NULL -> Operation.EqNull ConeConstantReference.NOT_NULL -> Operation.NotEqNull ConeBooleanConstantReference.TRUE -> Operation.EqTrue diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/accessToOuterThis.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/accessToOuterThis.fir.kt index e40ee556980..8407b8e1afd 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/accessToOuterThis.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/accessToOuterThis.fir.kt @@ -27,9 +27,9 @@ class Foo { } fun A?.goodWithReceiver() { - contract { + contract { returns() implies (this@goodWithReceiver != null) - } + } } fun A?.badWithReceiver() { diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/illegalConstructionInContractBlock.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/illegalConstructionInContractBlock.fir.kt index 43ca8bca1ab..9d3d73dc753 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/illegalConstructionInContractBlock.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/illegalConstructionInContractBlock.fir.kt @@ -48,8 +48,8 @@ fun doWhileInContract(x: Any?) { } fun localValInContract(x: Any?) { - contract { + contract { val y: Int = 42 returns() implies (x is String) - } + } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.3.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.3.fir.kt index 7f2d66eba2e..10bda601213 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.3.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.3.fir.kt @@ -5,29 +5,29 @@ import kotlin.contracts.* inline fun referToReifiedGeneric(x: Any?) { - contract { + contract { returns() implies (x is T) - } + } } class Generic { fun referToCaptured(x: Any?) { - contract { + contract { returns() implies (x is T) - } + } } } fun referToSubstituted(x: Any?) { - contract { + contract { returns() implies (x is Generic) - } + } } fun referToSubstitutedWithStar(x: Any?) { - contract { + contract { returns() implies (x is Generic<*>) - } + } } typealias GenericString = Generic @@ -35,19 +35,19 @@ typealias FunctionalType = () -> Unit typealias SimpleType = Int fun referToAliasedGeneric(x: Any?) { - contract { + contract { returns() implies (x is GenericString) - } + } } fun referToAliasedFunctionType(x: Any?) { - contract { + contract { returns() implies (x is FunctionalType) - } + } } fun referToAliasedSimpleType(x: Any?) { - contract { + contract { returns() implies (x is SimpleType) - } + } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.4.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.4.fir.kt index 7c3a4a218c8..34ff3139fa4 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.4.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.4.fir.kt @@ -5,29 +5,29 @@ import kotlin.contracts.* inline fun referToReifiedGeneric(x: Any?) { - contract { + contract { returns() implies (x is T) - } + } } class Generic { fun referToCaptured(x: Any?) { - contract { + contract { returns() implies (x is T) - } + } } } fun referToSubstituted(x: Any?) { - contract { + contract { returns() implies (x is Generic) - } + } } fun referToSubstitutedWithStar(x: Any?) { - contract { + contract { returns() implies (x is Generic<*>) - } + } } typealias GenericString = Generic @@ -35,19 +35,19 @@ typealias FunctionalType = () -> Unit typealias SimpleType = Int fun referToAliasedGeneric(x: Any?) { - contract { + contract { returns() implies (x is GenericString) - } + } } fun referToAliasedFunctionType(x: Any?) { - contract { + contract { returns() implies (x is FunctionalType) - } + } } fun referToAliasedSimpleType(x: Any?) { - contract { + contract { returns() implies (x is SimpleType) - } + } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/operatorsTests/equalsWithNullableBoolean.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/operatorsTests/equalsWithNullableBoolean.fir.kt index bec71fc96ca..5424b7c7a96 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/operatorsTests/equalsWithNullableBoolean.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/operatorsTests/equalsWithNullableBoolean.fir.kt @@ -5,9 +5,9 @@ import kotlin.contracts.* fun safeIsString(x: Any?): Boolean? { - contract { + contract { returns(true) implies (x is String) - } + } return x?.let { it is String } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/when/withSubjectNullableBoolean.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/when/withSubjectNullableBoolean.fir.kt index a6d2b6e1db2..b531c71d436 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/when/withSubjectNullableBoolean.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/when/withSubjectNullableBoolean.fir.kt @@ -5,9 +5,9 @@ import kotlin.contracts.* fun safeIsString(x: Any?): Boolean? { - contract { + contract { returns(true) implies (x is String) - } + } return x?.let { it is String } } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/common/neg/1.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/common/neg/1.fir.kt index 21ef60422e0..d781d5be1ac 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/common/neg/1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/common/neg/1.fir.kt @@ -21,12 +21,12 @@ inline fun case_1(value_1: Int?, block: () -> Unit): Boolean { // TESTCASE NUMBER: 2 inline fun T?.case_2(value_1: Int?, value_2: Any?, block: () -> Unit): Boolean? { - contract { + contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) returns(true) implies (value_1 == null && this@case_2 == null && value_2 !is Boolean?) returns(false) implies (value_2 is Boolean?) returns(null) implies ((value_1 != null || this@case_2 != null) && value_2 !is Boolean?) - } + } block() diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/1.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/1.fir.kt index f938331e4aa..de3278d3f10 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/1.fir.kt @@ -53,8 +53,8 @@ fun case_8(value_1: Any?) { if (!funWithReturnsTrueAndInvertCondition(value_1 !is String)) println(value_1.length) if (funWithReturnsFalse(value_1 is String)) println(value_1.length) if (funWithReturnsFalseAndInvertCondition(value_1 !is String)) println(value_1.length) - if (funWithReturnsNotNull(value_1 is String) == null) println(value_1.length) - if (!(funWithReturnsNotNull(value_1 is String) != null)) println(value_1.length) + if (funWithReturnsNotNull(value_1 is String) == null) println(value_1.length) + if (!(funWithReturnsNotNull(value_1 is String) != null)) println(value_1.length) if (!(funWithReturnsNull(value_1 is String) == null)) println(value_1.length) if (funWithReturnsNull(value_1 is String) != null) println(value_1.length) } @@ -65,8 +65,8 @@ fun case_9(value_1: String?) { if (!funWithReturnsTrueAndInvertCondition(value_1 == null)) println(value_1.length) if (funWithReturnsFalse(value_1 != null)) println(value_1.length) if (funWithReturnsFalseAndInvertCondition(value_1 == null)) println(value_1.length) - if (funWithReturnsNotNull(value_1 != null) == null) println(value_1.length) - if (funWithReturnsNotNullAndInvertCondition(value_1 == null) == null) println(value_1.length) + if (funWithReturnsNotNull(value_1 != null) == null) println(value_1.length) + if (funWithReturnsNotNullAndInvertCondition(value_1 == null) == null) println(value_1.length) if (funWithReturnsNull(value_1 != null) != null) println(value_1.length) if (funWithReturnsNullAndInvertCondition(value_1 == null) != null) println(value_1.length) } @@ -75,8 +75,8 @@ fun case_9(value_1: String?) { fun case_10(value_1: Any?) { if (!funWithReturnsTrueAndTypeCheck(value_1)) println(value_1.length) if (!!funWithReturnsFalseAndTypeCheck(value_1)) println(value_1.length) - if (!(funWithReturnsNotNullAndTypeCheck(value_1) != null)) println(value_1.length) - if (!!(funWithReturnsNotNullAndTypeCheck(value_1) == null)) println(value_1.length) + if (!(funWithReturnsNotNullAndTypeCheck(value_1) != null)) println(value_1.length) + if (!!(funWithReturnsNotNullAndTypeCheck(value_1) == null)) println(value_1.length) if (!!(funWithReturnsNullAndTypeCheck(value_1) != null)) println(value_1.length) if (!(funWithReturnsNullAndTypeCheck(value_1) == null)) println(value_1.length) } @@ -87,8 +87,8 @@ fun case_11(value_1: Number?) { if (!funWithReturnsTrueAndNullCheck(value_1)) println(value_1) if (funWithReturnsFalseAndNotNullCheck(value_1)) println(value_1.toByte()) if (funWithReturnsFalseAndNullCheck(value_1)) println(value_1) - if ((funWithReturnsNotNullAndNotNullCheck(value_1) == null)) println(value_1.toByte()) - if (!!!(funWithReturnsNotNullAndNotNullCheck(value_1) != null)) println(value_1.toByte()) + if ((funWithReturnsNotNullAndNotNullCheck(value_1) == null)) println(value_1.toByte()) + if (!!!(funWithReturnsNotNullAndNotNullCheck(value_1) != null)) println(value_1.toByte()) if (!!(funWithReturnsNotNullAndNullCheck(value_1) == null)) println(value_1) if (!(funWithReturnsNullAndNotNullCheck(value_1) == null)) println(value_1.toByte()) if (!!(funWithReturnsNullAndNotNullCheck(value_1) != null)) println(value_1.toByte()) diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/8.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/8.fir.kt index 839cfe16fc0..765f349152c 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/8.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/neg/8.fir.kt @@ -9,45 +9,45 @@ import kotlin.contracts.* // TESTCASE NUMBER: 3 fun T?.case_3(value_1: Int?, value_2: Boolean): Boolean { - contract { + contract { returns(true) implies (value_1 != null) returns(false) implies (value_1 == null && !value_2) returns(null) implies (value_1 == null && value_2) - } + } return value_1 == null } // TESTCASE NUMBER: 4 fun case_4(value_1: Number, block: (() -> Unit)?): Boolean? { - contract { + contract { returns(true) implies (block != null) returns(false) implies (value_1 is Int) returns(null) implies (block == null) - } + } return value_1 == null } // TESTCASE NUMBER: 5 fun String?.case_5(value_1: Number?): Boolean? { - contract { + contract { returns(true) implies (value_1 == null) returns(false) implies (this@case_5 == null) returnsNotNull() implies (value_1 is Int) - } + } return value_1 == null } // TESTCASE NUMBER: 6 fun T?.case_6(value_1: Number, value_2: String?): Boolean? { - contract { + contract { returns(true) implies (this@case_6 == null) returns(false) implies (value_1 is Int) returns(null) implies (this@case_6 is String) returnsNotNull() implies (value_2 == null) - } + } return value_1 == null } @@ -62,7 +62,7 @@ fun case_1(value_1: Any?) { println(value_1?.toByte()) if (funWithReturnsTrue(value_1 !is Number)) { println(value_1.toByte()) - if (funWithReturnsNotNull(value_1 is Int) == null) println(value_1.inv()) + if (funWithReturnsNotNull(value_1 is Int) == null) println(value_1.inv()) } } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/1.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/1.fir.kt index b3858186833..02a62cfa2b7 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/1.fir.kt @@ -52,8 +52,8 @@ fun case_8(value_1: Any?) { if (funWithReturnsTrueAndInvertCondition(value_1 !is String)) println(value_1.length) if (!funWithReturnsFalse(value_1 is String)) println(value_1.length) if (!funWithReturnsFalseAndInvertCondition(value_1 !is String)) println(value_1.length) - if (funWithReturnsNotNull(value_1 is String) != null) println(value_1.length) - if (!(funWithReturnsNotNull(value_1 is String) == null)) println(value_1.length) + if (funWithReturnsNotNull(value_1 is String) != null) println(value_1.length) + if (!(funWithReturnsNotNull(value_1 is String) == null)) println(value_1.length) } // TESTCASE NUMBER: 9 @@ -62,17 +62,17 @@ fun case_9(value_1: String?) { if (funWithReturnsTrueAndInvertCondition(value_1 == null)) println(value_1.length) if (!funWithReturnsFalse(value_1 != null)) println(value_1.length) if (!funWithReturnsFalseAndInvertCondition(value_1 == null)) println(value_1.length) - if (funWithReturnsNotNull(value_1 != null) != null) println(value_1.length) - if (!(funWithReturnsNotNull(value_1 != null) == null)) println(value_1.length) - if (!(funWithReturnsNotNullAndInvertCondition(value_1 == null) == null)) println(value_1.length) + if (funWithReturnsNotNull(value_1 != null) != null) println(value_1.length) + if (!(funWithReturnsNotNull(value_1 != null) == null)) println(value_1.length) + if (!(funWithReturnsNotNullAndInvertCondition(value_1 == null) == null)) println(value_1.length) } // TESTCASE NUMBER: 10 fun case_10(value_1: Any?) { if (funWithReturnsTrueAndTypeCheck(value_1)) println(value_1.length) if (!funWithReturnsFalseAndTypeCheck(value_1)) println(value_1.length) - if (funWithReturnsNotNullAndTypeCheck(value_1) != null) println(value_1.length) - if (!(funWithReturnsNotNullAndTypeCheck(value_1) == null)) println(value_1.length) + if (funWithReturnsNotNullAndTypeCheck(value_1) != null) println(value_1.length) + if (!(funWithReturnsNotNullAndTypeCheck(value_1) == null)) println(value_1.length) } // TESTCASE NUMBER: 11 @@ -82,7 +82,7 @@ fun case_11(value_1: Number?, value_2: Int?) { if (!funWithReturnsFalseAndNotNullCheck(value_2)) value_2.inc() if (!funWithReturnsFalseAndNotNullCheck(value_1)) println(value_1.toByte()) if (!funWithReturnsFalseAndNullCheck(value_1)) println(value_1) - if (!(funWithReturnsNotNullAndNotNullCheck(value_1) == null)) println(value_1.toByte()) - if (funWithReturnsNotNullAndNotNullCheck(value_1) != null) println(value_1.toByte()) + if (!(funWithReturnsNotNullAndNotNullCheck(value_1) == null)) println(value_1.toByte()) + if (funWithReturnsNotNullAndNotNullCheck(value_1) != null) println(value_1.toByte()) if (funWithReturnsNotNullAndNullCheck(value_1) != null) println(value_1) } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/10.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/10.fir.kt index d9a01d2566f..2cc478d1cb5 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/10.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/10.fir.kt @@ -15,7 +15,7 @@ fun case_1(value_1: Int?, value_2: Int? = 10): Boolean { // TESTCASE NUMBER: 2 fun case_2(value_1: Int? = 10, value_2: Int? = 10, value_3: Int? = 10): Boolean { - contract { returns(true) implies (value_2 != null) } + contract { returns(true) implies (value_2 != null) } return value_1 != null } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/14.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/14.fir.kt index 1bb50abc9ec..253e058210e 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/14.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/14.fir.kt @@ -17,27 +17,27 @@ fun case_1(value_1: Int?): Boolean? { // TESTCASE NUMBER: 2 fun case_2(value_1: Int?): Boolean { - contract { + contract { returns(false) implies (value_1 != null) - } + } return value_1 != null } // TESTCASE NUMBER: 3 fun case_3(value_1: Int?): Boolean? { - contract { + contract { returnsNotNull() implies (value_1 != null) - } + } return value_1 != null } // TESTCASE NUMBER: 4 fun case_4(value_1: Any?): Boolean { - contract { + contract { returnsNotNull() implies (value_1 is Number) - } + } return value_1 is Number } @@ -70,6 +70,6 @@ fun case_3(value_1: Int?) { // TESTCASE NUMBER: 4 fun case_4(value_1: Any?) { if (contracts.case_4(value_1) != null) { - value_1.toByte() + value_1.toByte() } } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/8.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/8.fir.kt index f9b7b8d157c..0c0626c5249 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/8.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/analysis/smartcasts/pos/8.fir.kt @@ -20,34 +20,34 @@ fun T?.case_3(value_1: Int?, value_2: Boolean): Boolean { // TESTCASE NUMBER: 4 fun case_4(value_1: Number, block: (() -> Unit)?): Boolean? { - contract { + contract { returns(true) implies (value_1 is Int) returns(false) implies (block == null) returns(null) implies (block != null) - } + } return value_1 == null } // TESTCASE NUMBER: 5 fun String?.case_5(value_1: Number?): Boolean? { - contract { + contract { returns(true) implies (value_1 != null) returns(false) implies (value_1 is Int) returnsNotNull() implies (this@case_5 != null) - } + } return value_1 == null } // TESTCASE NUMBER: 6 fun T?.case_6(value_1: Number, value_2: String?): Boolean? { - contract { + contract { returns(true) implies (this@case_6 != null) returns(false) implies (this@case_6 is String) returns(null) implies (value_1 is Int) returnsNotNull() implies (value_2 != null) - } + } return value_1 == null } @@ -62,7 +62,7 @@ fun case_1(value_1: Any?) { println(value_1?.toByte()) if (funWithReturnsTrue(value_1 is Number)) { println(value_1.toByte()) - if (funWithReturnsNotNull(value_1 is Int) != null) println(value_1.inv()) + if (funWithReturnsNotNull(value_1 is Int) != null) println(value_1.inv()) } } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/1.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/1.fir.kt new file mode 100644 index 00000000000..014e8eb34ee --- /dev/null +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/1.fir.kt @@ -0,0 +1,32 @@ +// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts + + +import kotlin.contracts.* + +// TESTCASE NUMBER: 1 +inline fun case_1(block: () -> Unit) { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return block() +} + +// TESTCASE NUMBER: 2 +inline fun case_2(value_1: Int?, block: () -> Unit): Boolean { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + returns(true) implies (value_1 != null) + } + block() + return value_1 != null +} + +// TESTCASE NUMBER: 3 +inline fun T?.case_3(value_1: Int?, value_2: Boolean, value_3: Int?, block: () -> Unit): Boolean? { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + returns(true) implies (value_1 != null) + returns(false) implies (!value_2) + returnsNotNull() implies (this@case_3 != null && value_3 != null) + } + block() + return value_1 != null +} diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/1.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/1.kt index 8b0ea4d0081..a909d2748a6 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/1.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/pos/1.kt @@ -1,4 +1,3 @@ -// FIR_IDENTICAL // !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts /*