FIR IDE: quickfix for WrapWithSafeLetCall

There is some behavior change regarding the new WrapWithSafeLetCall quickfix

1. it now works correctly on binary expressions by wrapping it with `()`
2. it now looks for a nullable position upward and do the modification there,
   if possible. For example, consider the following code

   ```
   fun bar(s: String): String = s

   fun test(s: String?) {
     bar(bar(bar(<caret>s)))
   }
   ```

   After applying this fix, FE1.0 yields

   ```
   bar(bar(s?.let { bar(it) }))
   ```

   while the new implementation yields

   ```
   s?.let { bar(bar(bar(it))) }
   ```

   This behavior aligns with FE1.0 if `bar` accepts nullable values.
This commit is contained in:
Tianyu Geng
2021-04-28 23:20:53 +02:00
committed by Ilya Kirillov
parent 0eaab6d8a2
commit e1b542314a
62 changed files with 1190 additions and 26 deletions
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Foo {
val f: ((() -> Unit) -> String)?
}
fun test(foo: Foo) {
bar(foo.<caret>f {})
}
fun bar(s: String) {}
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Foo {
val f: ((() -> Unit) -> String)?
}
fun test(foo: Foo) {
foo.f?<caret>.let { bar(it {}) }
}
fun bar(s: String) {}
@@ -0,0 +1,14 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
interface Foo {
val f: ((() -> Unit) -> String)?
}
fun test(foo: Foo) {
bar(foo.<caret>f {})
}
fun bar(s: String) {}
@@ -0,0 +1,14 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
interface Foo {
val f: ((() -> Unit) -> String)?
}
fun test(foo: Foo) {
bar(foo.f?.let { it {} })
}
fun bar(s: String) {}
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res = 24.hashCode().foo(<caret>arg) + 1
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res = arg?.let { 24.hash<caret>Code().foo(it) + 1 }
@@ -1,8 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
// ERROR: Operator call corresponds to a dot-qualified call 'arg?.let { 24.hashCode().foo(it) }.plus(1)' which is not allowed on a nullable receiver 'arg?.let { 24.hashCode().foo(it) }'.
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res = 24.hashCode().foo(<caret>arg)
val res = 24.hashCode().foo(<caret>arg) + 1
@@ -1,8 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
// ERROR: Operator call corresponds to a dot-qualified call 'arg?.let { 24.hashCode().foo(it) }.plus(1)' which is not allowed on a nullable receiver 'arg?.let { 24.hashCode().foo(it) }'.
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res = arg?.let { 24.hashCode().foo(it) }
val res = arg?.let { 24.hashCode().foo(it) } + 1
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
// ERROR: Operator call corresponds to a dot-qualified call 'arg?.let { 24.hashCode().foo(it) }.plus(1)' which is not allowed on a nullable receiver 'arg?.let { 24.hashCode().foo(it) }'.
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res: Int = 24.hashCode().foo(<caret>arg) + 1
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
// ERROR: Operator call corresponds to a dot-qualified call 'arg?.let { 24.hashCode().foo(it) }.plus(1)' which is not allowed on a nullable receiver 'arg?.let { 24.hashCode().foo(it) }'.
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res: Int = arg?.let { 24.hashCode().foo(it) } + 1
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// ACTION: Add non-null asserted (!!) call
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
fun test(l: List<String>?, s: String) {
if (s <caret>in l) {}
}
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// ACTION: Add non-null asserted (!!) call
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
fun test(l: List<String>?, s: String) {
l?.let<caret> { if (s in it) {} }
}
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "false"
// ACTION: Add non-null asserted (!!) call
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// ERROR: Operator call corresponds to a dot-qualified call 'l.contains(s)' which is not allowed on a nullable receiver 'l'.
fun test(l: List<String>?, s: String) {
if (s <caret>in l) {}
}
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun foo(x: String?, y: String) {
y.let { bar(<caret>x, it) }
}
fun bar(s: String, t: String) = s.hashCode() + t.hashCode()
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun foo(x: String?, y: String) {
y.let { x?.l<caret>et { s -> bar(s, it) } }
}
fun bar(s: String, t: String) = s.hashCode() + t.hashCode()
@@ -0,0 +1,11 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
val it = ""
fun test(s: String?) {
val name = ""
bar(<caret>s)
}
fun bar(name: String) {}
@@ -0,0 +1,11 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
val it = ""
fun test(s: String?) {
val name = ""
s?.let { it1 -> bar(it1) }
}
fun bar(name: String) {}
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface A
operator fun A?.plus(a: A?): A? = this
fun test(a1: A, a2: A) {
notNull(<caret>a1 + a2)
}
fun notNull(t: A): A = t
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface A
operator fun A?.plus(a: A?): A? = this
fun test(a1: A, a2: A) {
(a1 + a2<caret>)?.let { notNull(it) }
}
fun notNull(t: A): A = t
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface A
operator fun A?.plus(a: A?): A? = this
fun test(a1: A, a2: A) {
notNull(<caret>a1 + a2)
}
fun notNull(t: A): A = t
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface A
operator fun A?.plus(a: A?): A? = this
fun test(a1: A, a2: A) {
a1 + a2?.let { notNull(it) }
}
fun notNull(t: A): A = t
@@ -0,0 +1,16 @@
// "Wrap with '?.let { ... }' call" "false"
// ACTION: Add non-null asserted (!!) call
// ACTION: Convert to run
// ACTION: Convert to with
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type B?
// WITH_RUNTIME
class A {
fun foo() {}
}
class B(val a: A)
fun test(b: B?) {
b<caret>.a.foo() // b.a is UNSAFE_CALL
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.<caret>foo()
}
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.foo?.let { it() }
}
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.<caret>foo2()
}
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
fo<caret>o2?.let { s.it() }
}
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.<caret>foo2()
}
}
@@ -0,0 +1,18 @@
// "Wrap with '?.let { ... }' call" "true"
// ERROR: Expression 'it' of type 'Unit' cannot be invoked as a function. The function 'invoke()' is not found
// ERROR: Function invocation 'foo2()' expected
// ERROR: Reference has a nullable type '(Str.() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.foo2?.let { it() }
}
}
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "false"
// ACTION: Add 's =' to argument
// ACTION: Add non-null asserted (!!) call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// DISABLE-ERRORS
// WITH_RUNTIME
fun foo(s: String?) {}
fun bar(s: String?) {
foo(s<caret>.substring(1))
}
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
nullable(nullable(notNull(notNull(<caret>s))))
}
fun notNull(name: String): String = name
fun nullable(name: String?): String = ""
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
nullable(nullable(s?.let { notNull<caret>(notNull(it)) }))
}
fun notNull(name: String): String = name
fun nullable(name: String?): String = ""
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
nullable(nullable(notNull(notNull(<caret>s))))
}
fun notNull(name: String): String = name
fun nullable(name: String?): String = ""
@@ -0,0 +1,11 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?) {
nullable(nullable(notNull(s?.let { notNull(it) })))
}
fun notNull(name: String): String = name
fun nullable(name: String?): String = ""
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
val s2 = notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
val s2 = s?.let { notNull<caret>(notNull(it)) }
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
val s2 = notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?) {
val s2 = notNull(s?.let { notNull(it) })
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
s?.let { notNull<caret>(notNull(it)) }
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?) {
notNull(s?.let { notNull(it) })
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String? {
if (true) {
notNull(notNull(<caret>s))
}
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String? {
if (true) {
s?.let { notNull<caret>(notNull(it)) }
}
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String? {
if (true) {
notNull(notNull(<caret>s))
}
}
fun notNull(name: String): String = name
@@ -0,0 +1,13 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: A 'return' expression required in a function with a block body ('{...}')
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?): String? {
if (true) {
notNull(s?.let { notNull(it) })
}
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String {
while (true) notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String {
while (true) s?.let { notNull<caret>(notNull(it)) }
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String {
while (true) notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?): String {
while (true) notNull(s?.let { notNull(it) })
}
fun notNull(name: String): String = name
@@ -0,0 +1,19 @@
// "Wrap with '?.let { ... }' call" "true"
// ACTION: Add 'a =' to argument
// ACTION: Add non-null asserted (!!) call
// ACTION: Flip '+'
// ACTION: Introduce local variable
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// WITH_RUNTIME
interface A {
operator fun plus(a: A): A = this
}
fun test(a1: A?, a2: A) {
notNull(a1 <caret>+ a2)
}
fun notNull(a: A): A = a
@@ -0,0 +1,19 @@
// "Wrap with '?.let { ... }' call" "true"
// ACTION: Add 'a =' to argument
// ACTION: Add non-null asserted (!!) call
// ACTION: Flip '+'
// ACTION: Introduce local variable
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// WITH_RUNTIME
interface A {
operator fun plus(a: A): A = this
}
fun test(a1: A?, a2: A) {
a1?.let { n<caret>otNull(it + a2) }
}
fun notNull(a: A): A = a
@@ -0,0 +1,21 @@
// "Wrap with '?.let { ... }' call" "false"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ACTION: Add 'a =' to argument
// ACTION: Add non-null asserted (!!) call
// ACTION: Flip '+'
// ACTION: Introduce local variable
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// ERROR: Operator call corresponds to a dot-qualified call 'a1.plus(a2)' which is not allowed on a nullable receiver 'a1'.
// WITH_RUNTIME
interface A {
operator fun plus(a: A): A = this
}
fun test(a1: A?, a2: A) {
notNull(a1 <caret>+ a2)
}
fun notNull(a: A): A = a