瀏覽代碼

Assert that launch never returns for better compiler errors (#2517)

* assert that launch never returns for better compiler errors

* fix static generation launch function

* fix web renderer
Evan Almloff 1 年之前
父節點
當前提交
f4a62fad6b

+ 6 - 4
packages/desktop/src/launch.rs

@@ -11,7 +11,7 @@ use tao::event::{Event, StartCause, WindowEvent};
 ///
 /// This will block the main thread, and *must* be spawned on the main thread. This function does not assume any runtime
 /// and is equivalent to calling launch_with_props with the tokio feature disabled.
-pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Config) {
+pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Config) -> ! {
     let (event_loop, mut app) = App::new(desktop_config, virtual_dom);
 
     event_loop.run(move |window_event, _, control_flow| {
@@ -66,7 +66,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf
 }
 
 /// Launches the WebView and runs the event loop, with configuration and root props.
-pub fn launch_virtual_dom(virtual_dom: VirtualDom, desktop_config: Config) {
+pub fn launch_virtual_dom(virtual_dom: VirtualDom, desktop_config: Config) -> ! {
     #[cfg(feature = "tokio")]
     tokio::runtime::Builder::new_multi_thread()
         .enable_all()
@@ -77,7 +77,9 @@ pub fn launch_virtual_dom(virtual_dom: VirtualDom, desktop_config: Config) {
         }));
 
     #[cfg(not(feature = "tokio"))]
-    launch_virtual_dom_blocking(virtual_dom, desktop_config)
+    launch_virtual_dom_blocking(virtual_dom, desktop_config);
+
+    unreachable!("The desktop launch function will never exit")
 }
 
 /// Launches the WebView and runs the event loop, with configuration and root props.
@@ -85,7 +87,7 @@ pub fn launch(
     root: fn() -> Element,
     contexts: Vec<Box<dyn Fn() -> Box<dyn Any>>>,
     platform_config: Config,
-) {
+) -> ! {
     let mut virtual_dom = VirtualDom::new(root);
 
     for context in contexts {

+ 48 - 26
packages/dioxus/src/launch.rs

@@ -55,7 +55,7 @@ impl LaunchBuilder {
     )]
     pub fn new() -> LaunchBuilder<current_platform::Config, ValidContext> {
         LaunchBuilder {
-            launch_fn: current_platform::launch,
+            launch_fn: |root, contexts, cfg| current_platform::launch(root, contexts, cfg),
             contexts: Vec::new(),
             platform_config: None,
         }
@@ -77,7 +77,7 @@ impl LaunchBuilder {
     #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
     pub fn desktop() -> LaunchBuilder<dioxus_desktop::Config, UnsendContext> {
         LaunchBuilder {
-            launch_fn: dioxus_desktop::launch::launch,
+            launch_fn: |root, contexts, cfg| dioxus_desktop::launch::launch(root, contexts, cfg),
             contexts: Vec::new(),
             platform_config: None,
         }
@@ -88,7 +88,7 @@ impl LaunchBuilder {
     #[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
     pub fn fullstack() -> LaunchBuilder<dioxus_fullstack::Config, SendContext> {
         LaunchBuilder {
-            launch_fn: dioxus_fullstack::launch::launch,
+            launch_fn: |root, contexts, cfg| dioxus_fullstack::launch::launch(root, contexts, cfg),
             contexts: Vec::new(),
             platform_config: None,
         }
@@ -99,7 +99,7 @@ impl LaunchBuilder {
     #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
     pub fn mobile() -> LaunchBuilder<dioxus_mobile::Config, UnsendContext> {
         LaunchBuilder {
-            launch_fn: dioxus_mobile::launch::launch,
+            launch_fn: |root, contexts, cfg| dioxus_mobile::launch::launch(root, contexts, cfg),
             contexts: Vec::new(),
             platform_config: None,
         }
@@ -188,12 +188,23 @@ impl<Cfg: Default + 'static, ContextFn: ?Sized> LaunchBuilder<Cfg, ContextFn> {
         self
     }
 
+    // Static generation is the only platform that may exit. We can't use the `!` type here
+    #[cfg(any(feature = "static-generation", feature = "web"))]
     /// Launch your application.
     pub fn launch(self, app: fn() -> Element) {
         let cfg = self.platform_config.unwrap_or_default();
 
         (self.launch_fn)(app, self.contexts, cfg)
     }
+
+    #[cfg(not(any(feature = "static-generation", feature = "web")))]
+    /// Launch your application.
+    pub fn launch(self, app: fn() -> Element) -> ! {
+        let cfg = self.platform_config.unwrap_or_default();
+
+        (self.launch_fn)(app, self.contexts, cfg);
+        unreachable!("Launching an application will never exit")
+    }
 }
 
 /// Re-export the platform we expect the user wants
@@ -308,36 +319,47 @@ mod current_platform {
         root: fn() -> dioxus_core::Element,
         contexts: Vec<Box<super::ValidContext>>,
         platform_config: (),
-    ) {
+    ) -> ! {
         #[cfg(feature = "third-party-renderer")]
         panic!("No first party renderer feature enabled. It looks like you are trying to use a third party renderer. You will need to use the launch function from the third party renderer crate.");
 
-        panic!("No platform feature enabled. Please enable one of the following features: liveview, desktop, mobile, web, tui, fullstack to use the launch API.");
+        panic!("No platform feature enabled. Please enable one of the following features: liveview, desktop, mobile, web, tui, fullstack to use the launch API.")
     }
 }
 
-/// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
-// If you aren't using a third party renderer and this is not a docs.rs build, generate a warning about no renderer being enabled
-#[cfg_attr(
-    all(not(any(
-        docsrs,
-        feature = "third-party-renderer",
-        feature = "liveview",
-        feature = "desktop",
-        feature = "mobile",
-        feature = "web",
-        feature = "fullstack",
-        feature = "static-generation"
-    ))),
-    deprecated(
-        note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```"
-    )
-)]
-pub fn launch(app: fn() -> Element) {
-    #[allow(deprecated)]
-    LaunchBuilder::new().launch(app)
+// ! is unstable, so we can't name the type with an alias. Instead we need to generate different variants of items with macros
+macro_rules! impl_launch {
+    ($($return_type:tt),*) => {
+        /// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
+        // If you aren't using a third party renderer and this is not a docs.rs build, generate a warning about no renderer being enabled
+        #[cfg_attr(
+            all(not(any(
+                docsrs,
+                feature = "third-party-renderer",
+                feature = "liveview",
+                feature = "desktop",
+                feature = "mobile",
+                feature = "web",
+                feature = "fullstack",
+                feature = "static-generation"
+            ))),
+            deprecated(
+                note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```"
+            )
+        )]
+        pub fn launch(app: fn() -> Element) -> $($return_type)* {
+            #[allow(deprecated)]
+            LaunchBuilder::new().launch(app)
+        }
+    };
 }
 
+// Static generation is the only platform that may exit. We can't use the `!` type here
+#[cfg(any(feature = "static-generation", feature = "web"))]
+impl_launch!(());
+#[cfg(not(any(feature = "static-generation", feature = "web")))]
+impl_launch!(!);
+
 #[cfg(feature = "web")]
 #[cfg_attr(docsrs, doc(cfg(feature = "web")))]
 /// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options.

+ 56 - 27
packages/fullstack/src/launch.rs

@@ -6,47 +6,76 @@ use dioxus_lib::prelude::{Element, VirtualDom};
 
 pub use crate::Config;
 
-/// Launch a fullstack app with the given root component, contexts, and config.
-#[allow(unused)]
-pub fn launch(
+fn virtual_dom_factory(
     root: fn() -> Element,
     contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
-    platform_config: Config,
-) {
-    let virtual_dom_factory = move || {
+) -> impl Fn() -> VirtualDom + 'static {
+    move || {
         let mut vdom = VirtualDom::new(root);
         for context in &contexts {
             vdom.insert_any_root_context(context());
         }
         vdom
-    };
+    }
+}
 
+#[cfg(feature = "server")]
+/// Launch a fullstack app with the given root component, contexts, and config.
+#[allow(unused)]
+pub fn launch(
+    root: fn() -> Element,
+    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
+    platform_config: Config,
+) -> ! {
+    let factory = virtual_dom_factory(root, contexts);
     #[cfg(all(feature = "server", not(target_arch = "wasm32")))]
     tokio::runtime::Runtime::new()
         .unwrap()
         .block_on(async move {
-            platform_config.launch_server(virtual_dom_factory).await;
+            platform_config.launch_server(factory).await;
         });
 
-    #[cfg(not(feature = "server"))]
-    {
-        #[cfg(feature = "web")]
-        {
-            // TODO: this should pull the props from the document
-            let cfg = platform_config.web_cfg.hydrate(true);
-            dioxus_web::launch::launch_virtual_dom(virtual_dom_factory(), cfg);
-        }
+    unreachable!("Launching a fullstack app should never return")
+}
 
-        #[cfg(feature = "desktop")]
-        {
-            let cfg = platform_config.desktop_cfg;
-            dioxus_desktop::launch::launch_virtual_dom(virtual_dom_factory(), cfg)
-        }
+#[cfg(all(not(feature = "server"), feature = "web"))]
+/// Launch a fullstack app with the given root component, contexts, and config.
+#[allow(unused)]
+pub fn launch(
+    root: fn() -> Element,
+    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
+    platform_config: Config,
+) {
+    let factory = virtual_dom_factory(root, contexts);
+    let cfg = platform_config.web_cfg.hydrate(true);
+    dioxus_web::launch::launch_virtual_dom(factory(), cfg)
+}
 
-        #[cfg(feature = "mobile")]
-        {
-            let cfg = platform_config.mobile_cfg;
-            dioxus_mobile::launch::launch_virtual_dom(virtual_dom_factory(), cfg)
-        }
-    }
+#[cfg(all(not(any(feature = "server", feature = "web")), feature = "desktop"))]
+/// Launch a fullstack app with the given root component, contexts, and config.
+#[allow(unused)]
+pub fn launch(
+    root: fn() -> Element,
+    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
+    platform_config: Config,
+) -> ! {
+    let factory = virtual_dom_factory(root, contexts);
+    let cfg = platform_config.desktop_cfg;
+    dioxus_desktop::launch::launch_virtual_dom(factory(), cfg)
+}
+
+#[cfg(all(
+    not(any(feature = "server", feature = "web", feature = "desktop")),
+    feature = "mobile"
+))]
+/// Launch a fullstack app with the given root component, contexts, and config.
+#[allow(unused)]
+pub fn launch(
+    root: fn() -> Element,
+    contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
+    platform_config: Config,
+) {
+    let factory = virtual_dom_factory(root, contexts);
+    let cfg = platform_config.mobile_cfg;
+    dioxus_mobile::launch::launch_virtual_dom(factory(), cfg)
 }

+ 3 - 1
packages/liveview/src/launch.rs

@@ -8,7 +8,7 @@ pub fn launch(
     root: fn() -> Element,
     contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
     platform_config: Config,
-) {
+) -> ! {
     #[cfg(feature = "multi-threaded")]
     let mut builder = tokio::runtime::Builder::new_multi_thread();
     #[cfg(not(feature = "multi-threaded"))]
@@ -28,4 +28,6 @@ pub fn launch(
             .launch()
             .await;
     });
+
+    panic!("Launching a liveview app should never return")
 }

+ 2 - 2
packages/web/src/launch.rs

@@ -16,7 +16,7 @@ pub fn launch(
     for context in contexts {
         vdom.insert_any_root_context(context());
     }
-    launch_virtual_dom(vdom, platform_config);
+    launch_virtual_dom(vdom, platform_config)
 }
 
 /// Launch the web application with a prebuild virtual dom
@@ -30,5 +30,5 @@ pub fn launch_virtual_dom(vdom: VirtualDom, platform_config: Config) {
 
 /// Launch the web application with the given root component and config
 pub fn launch_cfg(root: fn() -> Element, platform_config: Config) {
-    launch_virtual_dom(VirtualDom::new(root), platform_config);
+    launch_virtual_dom(VirtualDom::new(root), platform_config)
 }