Jak pokryć niepotrzebne sprawdzenie wartości zerowej wygenerowane przez Kotlin?

9

Rozważ następujący minimalny przykład Kotlina:

fun <U> someWrapper(supplier: () -> U): () -> (U) {
    return { supplier() }
}

fun foo(taskExecutor: TaskExecutor): Int {
    val future = CompletableFuture.supplyAsync(someWrapper {
        42
    }, taskExecutor::execute)
    return future.join()
}

@Test
public void shouldFoo() {
    assertThat(foo(), is(42));
}

Mam zasady zasięgu oddziałów w Jacoco, które zawodzą dla powyższego kodu, mówiąc, że 1 z 2 oddziałów nie jest objęty linią someWrapperpołączenia. Niestety nie mogę wykluczyć wszystkich klas, z których someWrapperjest wywoływane.

Patrząc na zdekompilowany kod Java:

public final int foo(TaskExecutor taskExecutor) {
    Object var10000 = WrappersKt.someWrapper((Function0)null.INSTANCE);
    if (var10000 != null) {
        Object var2 = var10000;
        var10000 = new Foo$sam$java_util_function_Supplier$0((Function0)var2);
    }

    Supplier var3 = (Supplier)var10000;
    Function1 var4 = (Function1)(new Function1(this.taskExecutor) {
        // $FF: synthetic method
        // $FF: bridge method
        public Object invoke(Object var1) {
        this.invoke((Runnable)var1);
        return Unit.INSTANCE;
        }

        public final void invoke(Runnable p1) {
        ((TaskExecutor)this.receiver).execute(p1);
        }

        public final KDeclarationContainer getOwner() {
        return Reflection.getOrCreateKotlinClass(TaskExecutor.class);
        }

        public final String getName() {
        return "execute";
        }

        public final String getSignature() {
        return "execute(Ljava/lang/Runnable;)V";
        }
    });
    CompletableFuture future = CompletableFuture.supplyAsync(var3, (Executor)(new Foo$sam$java_util_concurrent_Executor$0(var4)));
    var10000 = future.join();
    Intrinsics.checkExpressionValueIsNotNull(var10000, "future.join()");
    return ((Number)var10000).intValue();
}

Myślę, że problemem jest if (var10000 != null)gałąź, która jest nawet oznaczona przez IDE jako niepotrzebna (zawsze prawdziwa).

Czy w jakiś sposób można dostosować kod, tak aby możliwe było objęcie wszystkich gałęzi, np. upewniając się, że kompilator nie generuje dodatkowego sprawdzenia zerowego? Mogę zmienić kod obu foo(..)i someWrapper(..)tak długo, jak będę w stanie dostarczyć zdobioną lambda.

Używam Kotlin 1.3.50 i Jacoco 0.8.4.

EDYTOWAĆ.

Jednym oczywistym obejściem jest wyodrębnienie supplyAsync(someWrapper { ... })do niektórych klas utils i wykluczenie tylko tej klasy, tj .:

fun <U> supplyAsync(supplier: () -> U, executor: TaskExecutor): CompletableFuture<U> {
    return CompletableFuture.supplyAsync(someWrapper { supplier() }, executor::execute)
}

Byłoby to dla mnie wystarczająco dobre, choć wciąż jestem ciekawy, dlaczego gałąź została dodana przez Kotlin, gdzie nie musi być żadnej gałęzi.

BKE
źródło
Rozumiem, Type inference failedgdy próbuję skompilować twój przykładowy kod. Byłoby świetnie, gdybyś mógł podać przykładowy kod, który działa po wyjęciu z pudełka! Na przykład taskExecutori controllersą nieznane.
Enselic,
@Enselic dodał małą edycję, aby usunąć błędy rozpraszające uwagę. Nie zamierzam dalej go rozszerzać na pełnoprawny kod, ponieważ powinno to wystarczyć, aby ten pomysł został zrealizowany.
BKE
1
Patrząc na to, jak JaCoCo stopniowo dostosowuje się do obsługi Kotlina (patrz github.com/jacoco/jacoco/releases i wyszukaj „dodane przez kompilator Kotlin”), myślę, że jest to kolejna luka, która zostanie naprawiona wcześniej czy później. Jeśli poważnie myślisz o zwiększeniu zasięgu, sugeruję zgłoszenie problemu.
PiotrK

Odpowiedzi:

1

Jeśli zwracana wartość someWrapperma być używana tylko jako instancja Supplier, wówczas można usunąć niepotrzebne sprawdzanie wartości zerowej, jawnie używając Supplierjako typu zwracanego.

fun <U> someWrapper(supplier: () -> U): Supplier<U> {
    return Supplier { supplier() }
}
Leo Aso
źródło