Skip to content

Commit d9414c7

Browse files
authored
Enforce composable target diagnostics for menu applier boundaries (JetBrains#2777)
[CMP-7106](https://youtrack.jetbrains.com/issue/CMP-7106) Use separate `ComposableTargetMarker` for desktop menu API Invalid API mixing such as putting UI composables into menu DSL (or vice versa) can fail at runtime due to applier mismatch. This change improves compile-time detection and reduces runtime-only failures. - Added desktop-specific marker annotations: `MenuComposable` - Marked menu APIs with `MenuComposable` where menu applier is expected - Kept applier-creating entrypoints open where appropriate with `@ComposableOpenTarget(-1)`. - Refactored menu bar composition internals: replaced list adapter usage with direct `JMenuBar` applier (`SwingApplier`-based path) ## Testing ```kt Window(onCloseRequest = ::exitApplication) { MenuBar { Text("Label 1") } } Tray(icon) { Text("Label 2") } ``` Now produces ``` Calling a UI Composable composable function where a Menu Composable composable was expected ``` ## Release Notes ### Features - Desktop - New compile-time warnings for invalid ui/menu composable mixing that previously failed only at runtime.
1 parent 0555020 commit d9414c7

10 files changed

Lines changed: 106 additions & 68 deletions

File tree

compose/ui/ui/api/desktop/ui.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4440,6 +4440,9 @@ public final class androidx/compose/ui/window/MenuBarScope {
44404440
public final fun Menu (Ljava/lang/String;Ljava/lang/Character;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
44414441
}
44424442

4443+
public abstract interface annotation class androidx/compose/ui/window/MenuComposable : java/lang/annotation/Annotation {
4444+
}
4445+
44434446
public final class androidx/compose/ui/window/MenuScope {
44444447
public static final field $stable I
44454448
public final fun CheckboxItem (Ljava/lang/String;ZLandroidx/compose/ui/graphics/painter/Painter;ZLjava/lang/Character;Landroidx/compose/ui/input/key/KeyShortcut;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/AwtWindow.desktop.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package androidx.compose.ui.awt
1818

1919
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.ComposableOpenTarget
2021
import androidx.compose.runtime.DisposableEffect
2122
import androidx.compose.runtime.remember
2223
import androidx.compose.ui.node.Ref
@@ -56,6 +57,7 @@ import java.awt.Window
5657
*/
5758
@OptIn(DelicateCoroutinesApi::class)
5859
@Composable
60+
@ComposableOpenTarget(-1)
5961
fun <T : Window> AwtWindow(
6062
visible: Boolean = true,
6163
create: () -> T,
@@ -111,4 +113,4 @@ fun <T : Window> AwtWindow(
111113
showJob.cancel()
112114
}
113115
}
114-
}
116+
}

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingDialog.desktop.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package androidx.compose.ui.awt
1818

1919
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.ComposableOpenTarget
2021
import androidx.compose.runtime.currentCompositionLocalContext
2122
import androidx.compose.runtime.getValue
2223
import androidx.compose.runtime.remember
@@ -102,6 +103,7 @@ import javax.swing.JDialog
102103
*/
103104
@OptIn(ExperimentalComposeUiApi::class)
104105
@Composable
106+
@ComposableOpenTarget(-1)
105107
fun SwingDialog(
106108
visible: Boolean = true,
107109
onPreviewKeyEvent: ((KeyEvent) -> Boolean) = { false },
@@ -180,6 +182,7 @@ fun SwingDialog(
180182
*/
181183
@ExperimentalComposeUiApi
182184
@Composable
185+
@ComposableOpenTarget(-1)
183186
fun SwingDialog(
184187
onCloseRequest: () -> Unit,
185188
state: DialogState = rememberDialogState(),
@@ -330,4 +333,4 @@ internal fun DialogModalityType.toAwtModalityType(): ModalityType = when (this)
330333
DialogModalityType.DocumentModal -> ModalityType.DOCUMENT_MODAL
331334
DialogModalityType.ApplicationModal -> ModalityType.APPLICATION_MODAL
332335
else -> error("Unknown dialog modality type: $this")
333-
}
336+
}

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingWindow.desktop.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package androidx.compose.ui.awt
1818

1919
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.ComposableOpenTarget
2021
import androidx.compose.runtime.currentCompositionLocalContext
2122
import androidx.compose.runtime.getValue
2223
import androidx.compose.runtime.remember
@@ -93,6 +94,7 @@ import javax.swing.JFrame
9394
*/
9495
@OptIn(ExperimentalComposeUiApi::class)
9596
@Composable
97+
@ComposableOpenTarget(-1)
9698
fun SwingWindow(
9799
visible: Boolean = true,
98100
onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
@@ -172,6 +174,7 @@ fun SwingWindow(
172174
*/
173175
@ExperimentalComposeUiApi
174176
@Composable
177+
@ComposableOpenTarget(-1)
175178
fun SwingWindow(
176179
onCloseRequest: () -> Unit,
177180
state: WindowState = rememberWindowState(),
@@ -320,4 +323,3 @@ fun SwingWindow(
320323
content = content
321324
)
322325
}
323-

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/AwtWindow.desktop.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package androidx.compose.ui.window
1818

1919
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.ComposableOpenTarget
2021
import java.awt.Window
2122

2223
/**
@@ -52,11 +53,12 @@ import java.awt.Window
5253
)
5354
)
5455
@Composable
56+
@ComposableOpenTarget(-1)
5557
fun <T : Window> AwtWindow(
5658
visible: Boolean = true,
5759
create: () -> T,
5860
dispose: (T) -> Unit,
5961
update: (T) -> Unit = {}
6062
) {
6163
androidx.compose.ui.awt.AwtWindow(visible, create, dispose, update)
62-
}
64+
}

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package androidx.compose.ui.window
1818

1919
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.ComposableOpenTarget
2021
import androidx.compose.runtime.Stable
2122
import androidx.compose.ui.ExperimentalComposeUiApi
2223
import androidx.compose.ui.awt.ComposeDialog
@@ -33,6 +34,7 @@ import java.awt.Window
3334
"onPreviewKeyEvent, onKeyEvent, content)")
3435
)
3536
@Composable
37+
@ComposableOpenTarget(-1)
3638
fun Dialog(
3739
onCloseRequest: () -> Unit,
3840
state: DialogState = rememberDialogState(),
@@ -70,6 +72,7 @@ fun Dialog(
7072
message = "Replaced by DialogWindow with alwaysOnTop parameter",
7173
)
7274
@Composable
75+
@ComposableOpenTarget(-1)
7376
fun DialogWindow(
7477
onCloseRequest: () -> Unit,
7578
state: DialogState = rememberDialogState(),
@@ -164,6 +167,7 @@ fun DialogWindow(
164167
* @param content Composable content of the dialog.
165168
*/
166169
@Composable
170+
@ComposableOpenTarget(-1)
167171
fun DialogWindow(
168172
onCloseRequest: () -> Unit,
169173
state: DialogState = rememberDialogState(),
@@ -261,6 +265,7 @@ fun DialogWindow(
261265
*/
262266
@ExperimentalComposeUiApi
263267
@Composable
268+
@ComposableOpenTarget(-1)
264269
fun DialogWindow(
265270
onCloseRequest: () -> Unit,
266271
state: DialogState = rememberDialogState(),
@@ -306,6 +311,7 @@ fun DialogWindow(
306311
)
307312
)
308313
@Composable
314+
@ComposableOpenTarget(-1)
309315
fun Dialog(
310316
visible: Boolean = true,
311317
onPreviewKeyEvent: ((KeyEvent) -> Boolean) = { false },
@@ -372,6 +378,7 @@ fun Dialog(
372378
)
373379
)
374380
@Composable
381+
@ComposableOpenTarget(-1)
375382
fun DialogWindow(
376383
visible: Boolean = true,
377384
onPreviewKeyEvent: ((KeyEvent) -> Boolean) = { false },
@@ -437,4 +444,4 @@ class DialogModalityType private constructor(val name: String) {
437444
*/
438445
val ApplicationModal = DialogModalityType("Application")
439446
}
440-
}
447+
}

0 commit comments

Comments
 (0)