Repository: redbadger/crux (language: rust)

Issue:
Adding Compose capability caused typegen to fail
I tried adding the Compose capability to my app. The `shared` crate compiles, but typegen fails in `shared_types`, because the `Never` enum has no cases. See the backtrace below for details.

We don't need to include `Compose` or `Never` in typegen, since they are not exposed to shells, but is there a way to mark a capability as "excluded"? Or maybe another workaround?

Here's [a minimal repo that produces the error](https://github.com/wasnotrice/crux_compose). You don't even have to use Compose, just include it in Capabilities. 

```
❯ RUST_BACKTRACE=1 RUSTFLAGS=-Awarnings cargo build -p shared_types
   Compiling shared_types v0.1.0 (/Users/eric/code/crux_compose/shared_types)
error: failed to run custom build command for `shared_types v0.1.0 (/Users/eric/code/crux_compose/shared_types)`
note: To improve backtraces for build dependencies, set the CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable to enable debug information generation.

Caused by:
  process didn't exit successfully: `/Users/eric/code/crux_compose/target/debug/build/shared_types-5b2877aeefdefaf8/build-script-build` (exit status: 101)
  --- stdout
  cargo:rerun-if-changed=../shared

  --- stderr
  thread 'main' panicked at /Users/eric/.cargo/registry/src/index.crates.io-6f17d22bba15001f/serde-reflection-0.3.6/src/de.rs:431:18:
  variant indexes must be a non-empty range 0..variants.len()
  stack backtrace:
     0: rust_begin_unwind
               at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/std/src/panicking.rs:647:5
     1: core::panicking::panic_fmt
               at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/panicking.rs:72:14
     2: core::panicking::panic_display
               at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/panicking.rs:196:5
     3: core::panicking::panic_str
               at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/panicking.rs:171:5
     4: core::option::expect_failed
               at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/option.rs:1988:5
     5: core::option::Option<T>::expect
     6: <serde_reflection::de::Deserializer as serde::de::Deserializer>::deserialize_enum::{{closure}}
     7: alloc::collections::btree::map::entry::Entry<K,V,A>::or_insert_with
     8: <serde_reflection::de::Deserializer as serde::de::Deserializer>::deserialize_enum
     9: crux_core::capability::_::<impl serde::de::Deserialize for crux_core::capability::Never>::deserialize
    10: serde_reflection::trace::Tracer::trace_type_once
    11: serde_reflection::trace::Tracer::trace_type
    12: serde_reflection::trace::Tracer::trace_simple_type
    13: crux_core::typegen::TypeGen::register_type
    14: <shared::app::Capabilities as crux_core::typegen::Export>::register_types
    15: crux_core::typegen::TypeGen::register_app
    16: build_script_build::main
    17: core::ops::function::FnOnce::call_once
  note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
```



Discussion / hints:
Apologies for taking this long to respond. 

It should be possible to work around this by marking the compose capability with `#[effect(skip)]`, e.g.

```
#[derive(Effect)]
pub struct Capabilities {
    pub render: Render<Event>,
    #[effect(skip)]
    pub compose: Compose<Event>,
}
```

It may or may not be possible for us to detect this during type gen and skip it, but it's unclear to me whether with the current typegen we can only do it specifically for `Compose` or for any capability with `Never` as the operation type.

Fix patch:
diff --git a/crux_macros/src/export.rs b/crux_macros/src/export.rs
index 851b33b4..56d3b212 100644
--- a/crux_macros/src/export.rs
+++ b/crux_macros/src/export.rs
@@ -13,8 +13,11 @@ struct ExportStructReceiver {
 }
 
 #[derive(FromField, Debug)]
+#[darling(attributes(effect))]
 pub struct ExportFieldReceiver {
     ty: Type,
+    #[darling(default)]
+    skip: bool,
 }
 
 impl ToTokens for ExportStructReceiver {
@@ -30,12 +33,15 @@ impl ToTokens for ExportStructReceiver {
             None => quote!(EffectFfi),
         };
 
-        let fields = self
+        let fields: Vec<&ExportFieldReceiver> = self
             .data
             .as_ref()
             .take_struct()
             .expect_or_abort("should be a struct")
-            .fields;
+            .fields
+            .into_iter()
+            .filter(|e| !e.skip)
+            .collect();
 
         let mut output_type_exports = Vec::new();
 
@@ -161,6 +167,93 @@ mod tests {
         assert_eq!(quote!(#actual_event).to_string(), quote!(Event).to_string());
     }
 
+    #[test]
+    fn export_macro_respects_an_skip_attr() {
+        let input = r#"
+            #[derive(Export)]
+            pub struct MyCapabilities {
+                pub http: crux_http::Http<MyEvent>,
+                pub key_value: KeyValue<MyEvent>,
+                pub platform: Platform<MyEvent>,
+                pub render: Render<MyEvent>,
+                #[effect(skip)]
+                pub time: Time<MyEvent>,
+            }
+        "#;
+        let input = parse_str(input).unwrap();
+        let input = ExportStructReceiver::from_derive_input(&input).unwrap();
+
+        let actual = quote!(#input);
+
+        insta::assert_snapshot!(pretty_print(&actual), @r###"
+        impl ::crux_core::typegen::Export for MyCapabilities {
+            fn register_types(
+                generator: &mut ::crux_core::typegen::TypeGen,
+            ) -> ::crux_core::typegen::Result {
+                generator
+                    .register_type::<
+                        <crux_http::Http<
+                            MyEvent,
+                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
+                    >()?;
+                generator
+                    .register_type::<
+                        <<crux_http::Http<
+                            MyEvent,
+                        > as ::crux_core::capability::Capability<
+                            MyEvent,
+                        >>::Operation as ::crux_core::capability::Operation>::Output,
+                    >()?;
+                generator
+                    .register_type::<
+                        <KeyValue<
+                            MyEvent,
+                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
+                    >()?;
+                generator
+                    .register_type::<
+                        <<KeyValue<
+                            MyEvent,
+                        > as ::crux_core::capability::Capability<
+                            MyEvent,
+                        >>::Operation as ::crux_core::capability::Operation>::Output,
+                    >()?;
+                generator
+                    .register_type::<
+                        <Platform<
+                            MyEvent,
+                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
+                    >()?;
+                generator
+                    .register_type::<
+                        <<Platform<
+                            MyEvent,
+                        > as ::crux_core::capability::Capability<
+                            MyEvent,
+                        >>::Operation as ::crux_core::capability::Operation>::Output,
+                    >()?;
+                generator
+                    .register_type::<
+                        <Render<
+                            MyEvent,
+                        > as ::crux_core::capability::Capability<MyEvent>>::Operation,
+                    >()?;
+                generator
+                    .register_type::<
+                        <<Render<
+                            MyEvent,
+                        > as ::crux_core::capability::Capability<
+                            MyEvent,
+                        >>::Operation as ::crux_core::capability::Operation>::Output,
+                    >()?;
+                generator.register_type::<EffectFfi>()?;
+                generator.register_type::<::crux_core::bridge::Request<EffectFfi>>()?;
+                Ok(())
+            }
+        }
+        "###);
+    }
+
     #[test]
     fn full() {
         let input = r#"


Test patch: