Skip to content

Commit

Permalink
Use unsafe cast in inline Compose code (#2349)
Browse files Browse the repository at this point in the history
Regular casts emit calls into Kotlin's isInterface function and ClassCastException-throwing function. We know these are the types, though, and unsafe cast allows eliminating all that inline code.

Example diff:

     if ($composer_1.get_inserting_25mlsw_k$()) {
    -  var tmp = ensureNotNull($composer_1.get_applier_bupu8u_k$());
    -  var applier = isInterface(tmp, RedwoodApplier) ? tmp : THROW_CCE();
    +  var applier = $composer_1.get_applier_bupu8u_k$();
       var tmp_0 = $composer_1;
       tmp_0.createNode_ahrd54_k$(function () {
    -    // Inline function 'com.example.redwood.testapp.compose.Button.<anonymous>' call
    -    var tmp = applier.get_widgetSystem_mxid3w_k$();
    -    var tmp$ret$0 = (isInterface(tmp, WidgetFactoryOwner) ? tmp : THROW_CCE()).get_TestSchema_txdklg_k$().Button_x3fioi_k$();
    -    return new WidgetNode(applier, tmp$ret$0);
    +    var tmp$ret$6 = applier.get_widgetSystem_mxid3w_k$().get_TestSchema_txdklg_k$().Button_x3fioi_k$();
    +    return new WidgetNode(applier, tmp$ret$6);
       });
     } else {
       $composer_1.useNode_io5s9l_k$();
  • Loading branch information
JakeWharton authored Oct 1, 2024
1 parent 95ad195 commit 631cdad
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 12 deletions.
5 changes: 5 additions & 0 deletions redwood-compose/api/android/redwood-compose.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
public final class app/cash/redwood/compose/ActualsKt {
public static final fun redwoodApplier (Landroidx/compose/runtime/Composer;)Lapp/cash/redwood/compose/RedwoodApplier;
public static final fun widgetSystem (Lapp/cash/redwood/compose/RedwoodApplier;)Lapp/cash/redwood/widget/WidgetFactoryOwner;
}

public final class app/cash/redwood/compose/AndroidUiDispatcher : kotlinx/coroutines/CoroutineDispatcher {
public static final field $stable I
public static final field Companion Lapp/cash/redwood/compose/AndroidUiDispatcher$Companion;
Expand Down
5 changes: 5 additions & 0 deletions redwood-compose/api/jvm/redwood-compose.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
public final class app/cash/redwood/compose/ActualsKt {
public static final fun redwoodApplier (Landroidx/compose/runtime/Composer;)Lapp/cash/redwood/compose/RedwoodApplier;
public static final fun widgetSystem (Lapp/cash/redwood/compose/RedwoodApplier;)Lapp/cash/redwood/widget/WidgetFactoryOwner;
}

public final class app/cash/redwood/compose/BackHandlerKt {
public static final fun BackHandler (ZLkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V
public static final fun getCurrent (Lapp/cash/redwood/ui/OnBackPressedDispatcher$Companion;Landroidx/compose/runtime/Composer;I)Lapp/cash/redwood/ui/OnBackPressedDispatcher;
Expand Down
2 changes: 2 additions & 0 deletions redwood-compose/api/redwood-compose.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ final fun app.cash.redwood.compose/app_cash_redwood_compose_ChildrenNode$stablep
final fun app.cash.redwood.compose/app_cash_redwood_compose_NodeApplier$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_NodeApplier$stableprop_getter|app_cash_redwood_compose_NodeApplier$stableprop_getter(){}[0]
final fun app.cash.redwood.compose/app_cash_redwood_compose_RedwoodComposeContent$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_RedwoodComposeContent$stableprop_getter|app_cash_redwood_compose_RedwoodComposeContent$stableprop_getter(){}[0]
final fun app.cash.redwood.compose/app_cash_redwood_compose_WidgetNode$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_WidgetNode$stableprop_getter|app_cash_redwood_compose_WidgetNode$stableprop_getter(){}[0]
final inline fun <#A: app.cash.redwood.widget/WidgetFactoryOwner<#B>, #B: kotlin/Any> (app.cash.redwood.compose/RedwoodApplier<#B>).app.cash.redwood.compose/widgetSystem(): #A // app.cash.redwood.compose/widgetSystem|[email protected]<0:1>(){0§<app.cash.redwood.widget.WidgetFactoryOwner<0:1>>;1§<kotlin.Any>}[0]
final inline fun <#A: app.cash.redwood.widget/WidgetFactoryOwner<#C>, #B: app.cash.redwood.widget/Widget<#C>, #C: kotlin/Any> app.cash.redwood.compose/RedwoodComposeNode(crossinline kotlin/Function1<#A, #B>, kotlin/Function1<androidx.compose.runtime/Updater<app.cash.redwood.compose/WidgetNode<#B, #C>>, kotlin/Unit>, kotlin/Function3<app.cash.redwood.compose/RedwoodComposeContent<#B>, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int) // app.cash.redwood.compose/RedwoodComposeNode|RedwoodComposeNode(kotlin.Function1<0:0,0:1>;kotlin.Function1<androidx.compose.runtime.Updater<app.cash.redwood.compose.WidgetNode<0:1,0:2>>,kotlin.Unit>;kotlin.Function3<app.cash.redwood.compose.RedwoodComposeContent<0:1>,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int){0§<app.cash.redwood.widget.WidgetFactoryOwner<0:2>>;1§<app.cash.redwood.widget.Widget<0:2>>;2§<kotlin.Any>}[0]
final inline fun <#A: kotlin/Any> (androidx.compose.runtime/Composer).app.cash.redwood.compose/redwoodApplier(): app.cash.redwood.compose/RedwoodApplier<#A> // app.cash.redwood.compose/redwoodApplier|[email protected](){0§<kotlin.Any>}[0]

// Targets: [ios]
final object app.cash.redwood.compose/DisplayLinkClock : androidx.compose.runtime/MonotonicFrameClock { // app.cash.redwood.compose/DisplayLinkClock|null[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package app.cash.redwood.compose
import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeNode
import androidx.compose.runtime.Composer
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisallowComposableCalls
Expand Down Expand Up @@ -180,6 +181,14 @@ public interface RedwoodApplier<W : Any> {
public fun recordChanged(widget: Widget<W>)
}

@PublishedApi
@RedwoodCodegenApi
internal expect inline fun <V : Any> Composer.redwoodApplier(): RedwoodApplier<V>

@PublishedApi
@RedwoodCodegenApi
internal expect inline fun <O : WidgetFactoryOwner<V>, V : Any> RedwoodApplier<V>.widgetSystem(): O

/**
* A version of [ComposeNode] which exposes the applier to the [factory] function. Through this
* we expose the owner type [O] to our factory function so the correct widget can be created.
Expand All @@ -199,19 +208,9 @@ public inline fun <O : WidgetFactoryOwner<V>, W : Widget<V>, V : Any> RedwoodCom
currentComposer.startNode()

if (currentComposer.inserting) {
// Perform an explicit !! on the return value to avoid the Kotlin compiler inserting a huge
// string into the output as an error message for an otherwise implicit null check.
@Suppress(
// Safe so long as you use generated composition function.
"UNCHECKED_CAST",
"UNNECESSARY_NOT_NULL_ASSERTION",
)
val applier = currentComposer.applier!! as RedwoodApplier<V>

val applier = currentComposer.redwoodApplier<V>()
currentComposer.createNode {
// Safe so long as you use generated composition function.
@Suppress("UNCHECKED_CAST")
WidgetNode(applier, factory(applier.widgetSystem as O))
WidgetNode(applier, factory(applier.widgetSystem()))
}
} else {
currentComposer.useNode()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.redwood.compose

import androidx.compose.runtime.Composer
import app.cash.redwood.RedwoodCodegenApi
import app.cash.redwood.widget.WidgetFactoryOwner

@PublishedApi
@RedwoodCodegenApi
@Suppress("NOTHING_TO_INLINE")
internal actual inline fun <V : Any> Composer.redwoodApplier(): RedwoodApplier<V> {
return applier.unsafeCast<RedwoodApplier<V>>()
}

@PublishedApi
@RedwoodCodegenApi
@Suppress("NOTHING_TO_INLINE")
internal actual inline fun <O : WidgetFactoryOwner<V>, V : Any> RedwoodApplier<V>.widgetSystem(): O {
return widgetSystem.unsafeCast<O>()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.redwood.compose

import androidx.compose.runtime.Composer
import app.cash.redwood.RedwoodCodegenApi
import app.cash.redwood.widget.WidgetFactoryOwner

@PublishedApi
@RedwoodCodegenApi
@Suppress("NOTHING_TO_INLINE")
internal actual inline fun <V : Any> Composer.redwoodApplier(): RedwoodApplier<V> {
// Perform an explicit !! on the return value to avoid the Kotlin compiler inserting a huge
// string into the output as an error message for an otherwise implicit null check.
@Suppress(
// Safe so long as you use generated composition function.
"UNCHECKED_CAST",
"UNNECESSARY_NOT_NULL_ASSERTION",
)
return applier!! as RedwoodApplier<V>
}

@PublishedApi
@RedwoodCodegenApi
@Suppress("NOTHING_TO_INLINE")
internal actual inline fun <O : WidgetFactoryOwner<V>, V : Any> RedwoodApplier<V>.widgetSystem(): O {
@Suppress("UNCHECKED_CAST")
return widgetSystem as O
}

0 comments on commit 631cdad

Please sign in to comment.