From 9e5f58ef773f0aee39a9705d9845361a2488649b Mon Sep 17 00:00:00 2001 From: Mingwei Samuel Date: Fri, 23 Aug 2024 11:08:35 -0700 Subject: [PATCH] feat(hydroflow_lang): allow `demux_enum` to have any number of outputs, fix #1329 (#1409) --- Cargo.lock | 21 ++-- hydroflow/src/util/demux_enum.rs | 20 ++- .../surface_demuxenum_notenum.stderr | 6 +- .../surface_demuxenum_port_duplicate_one.rs | 18 +++ ...urface_demuxenum_port_duplicate_one.stderr | 11 ++ .../surface_demuxenum_port_extra_zero.rs | 14 +++ .../surface_demuxenum_port_extra_zero.stderr | 37 ++++++ .../surface_demuxenum_port_missing_one.rs | 19 +++ .../surface_demuxenum_port_missing_one.stderr | 28 +++++ .../surface_demuxenum_port_wrong_one.rs | 17 +++ .../surface_demuxenum_port_wrong_one.stderr | 8 ++ .../surface_demuxenum_wrongenum.stderr | 6 +- hydroflow/tests/surface_demux_enum.rs | 27 +++++ hydroflow_lang/src/graph/ops/demux_enum.rs | 114 +++++++++++------- hydroflow_macro/src/lib.rs | 87 ++++++++----- 15 files changed, 335 insertions(+), 98 deletions(-) create mode 100644 hydroflow/tests/compile-fail/surface_demuxenum_port_duplicate_one.rs create mode 100644 hydroflow/tests/compile-fail/surface_demuxenum_port_duplicate_one.stderr create mode 100644 hydroflow/tests/compile-fail/surface_demuxenum_port_extra_zero.rs create mode 100644 hydroflow/tests/compile-fail/surface_demuxenum_port_extra_zero.stderr create mode 100644 hydroflow/tests/compile-fail/surface_demuxenum_port_missing_one.rs create mode 100644 hydroflow/tests/compile-fail/surface_demuxenum_port_missing_one.stderr create mode 100644 hydroflow/tests/compile-fail/surface_demuxenum_port_wrong_one.rs create mode 100644 hydroflow/tests/compile-fail/surface_demuxenum_port_wrong_one.stderr diff --git a/Cargo.lock b/Cargo.lock index 13fc77d02df..4523fc1b2f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3823,19 +3823,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if 1.0.0", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -3860,9 +3861,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3870,9 +3871,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -3883,9 +3884,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-bindgen-test" diff --git a/hydroflow/src/util/demux_enum.rs b/hydroflow/src/util/demux_enum.rs index a08577d821e..d9ef820dcd6 100644 --- a/hydroflow/src/util/demux_enum.rs +++ b/hydroflow/src/util/demux_enum.rs @@ -9,14 +9,26 @@ pub use hydroflow_macro::DemuxEnum; /// The derive will implement this such that `Outputs` can be any tuple where each item is a /// `Pusherator` that corresponds to each of the variants of the tuple, in alphabetic order. #[diagnostic::on_unimplemented( - note = "Ensure there is exactly one output for each enum variant.", - note = "Ensure that the type for each output is a tuple of the field for the variant: `()`, `(a,)`, or `(a, b, ...)`." + note = "ensure there is exactly one output for each enum variant.", + note = "ensure that the type for each output is a tuple of the field for the variant: `()`, `(a,)`, or `(a, b, ...)`." )] pub trait DemuxEnum: DemuxEnumBase { /// Pushes self into the corresponding output pusherator in `outputs`. fn demux_enum(self, outputs: &mut Outputs); } -/// Base implementation to constrain that `DemuxEnum` is implemented. -#[diagnostic::on_unimplemented(note = "Use `#[derive(hydroflow::DemuxEnum)]`")] +/// Special case of [`DemuxEnum`] for when there is only one variant. +#[diagnostic::on_unimplemented( + note = "requires that the enum have only one variant.", + note = "ensure there are no missing outputs; there must be exactly one output for each enum variant." +)] +pub trait SingleVariant: DemuxEnumBase { + /// Output tuple type. + type Output; + /// Convert self into it's single variant tuple Output. + fn single_variant(self) -> Self::Output; +} + +/// Base implementation to constrain that [`DemuxEnum`] is implemented. +#[diagnostic::on_unimplemented(note = "use `#[derive(hydroflow::DemuxEnum)]`")] pub trait DemuxEnumBase {} diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_notenum.stderr b/hydroflow/tests/compile-fail/surface_demuxenum_notenum.stderr index 2ff5c3c7129..be5512428e6 100644 --- a/hydroflow/tests/compile-fail/surface_demuxenum_notenum.stderr +++ b/hydroflow/tests/compile-fail/surface_demuxenum_notenum.stderr @@ -4,7 +4,7 @@ error[E0277]: the trait bound `Shape: DemuxEnumBase` is not satisfied 12 | ]) -> demux_enum::(); | ^^^^^ the trait `DemuxEnumBase` is not implemented for `Shape` | - = note: Use `#[derive(hydroflow::DemuxEnum)]` + = note: use `#[derive(hydroflow::DemuxEnum)]` note: required by a bound in `check_impl_demux_enum` --> tests/compile-fail/surface_demuxenum_notenum.rs:12:28 | @@ -61,5 +61,5 @@ error[E0277]: the trait bound `Shape: DemuxEnum<_>` is not satisfied 12 | ]) -> demux_enum::(); | ^^^^^^^^^^^^^^^^^^^^^ the trait `DemuxEnum<_>` is not implemented for `Shape` | - = note: Ensure there is exactly one output for each enum variant. - = note: Ensure that the type for each output is a tuple of the field for the variant: `()`, `(a,)`, or `(a, b, ...)`. + = note: ensure there is exactly one output for each enum variant. + = note: ensure that the type for each output is a tuple of the field for the variant: `()`, `(a,)`, or `(a, b, ...)`. diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_port_duplicate_one.rs b/hydroflow/tests/compile-fail/surface_demuxenum_port_duplicate_one.rs new file mode 100644 index 00000000000..0b193eaba6e --- /dev/null +++ b/hydroflow/tests/compile-fail/surface_demuxenum_port_duplicate_one.rs @@ -0,0 +1,18 @@ +use hydroflow::hydroflow_syntax; +use hydroflow::util::demux_enum::DemuxEnum; + +fn main() { + #[derive(DemuxEnum)] + enum Shape { + Square(f64), + } + + let mut df = hydroflow_syntax! { + my_demux = source_iter([ + Shape::Square(9.0), + ]) -> demux_enum::(); + my_demux[Square] -> for_each(std::mem::drop); + my_demux[Square] -> for_each(std::mem::drop); + }; + df.run_available(); +} diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_port_duplicate_one.stderr b/hydroflow/tests/compile-fail/surface_demuxenum_port_duplicate_one.stderr new file mode 100644 index 00000000000..b641266fa04 --- /dev/null +++ b/hydroflow/tests/compile-fail/surface_demuxenum_port_duplicate_one.stderr @@ -0,0 +1,11 @@ +error: Output connection conflicts with below ($DIR/tests/compile-fail/surface_demuxenum_port_duplicate_one.rs:15:18) (1/2) + --> tests/compile-fail/surface_demuxenum_port_duplicate_one.rs:14:18 + | +14 | my_demux[Square] -> for_each(std::mem::drop); + | ^^^^^^ + +error: Output connection conflicts with above ($DIR/tests/compile-fail/surface_demuxenum_port_duplicate_one.rs:14:18) (2/2) + --> tests/compile-fail/surface_demuxenum_port_duplicate_one.rs:15:18 + | +15 | my_demux[Square] -> for_each(std::mem::drop); + | ^^^^^^ diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_port_extra_zero.rs b/hydroflow/tests/compile-fail/surface_demuxenum_port_extra_zero.rs new file mode 100644 index 00000000000..8317420f9cb --- /dev/null +++ b/hydroflow/tests/compile-fail/surface_demuxenum_port_extra_zero.rs @@ -0,0 +1,14 @@ +use hydroflow::util::demux_enum::DemuxEnum; +use hydroflow::hydroflow_syntax; + +fn main() { + #[derive(DemuxEnum)] + enum Shape { + } + + let mut df = hydroflow_syntax! { + my_demux = source_iter([]) -> demux_enum::(); + my_demux[Square] -> for_each(std::mem::drop); + }; + df.run_available(); +} diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_port_extra_zero.stderr b/hydroflow/tests/compile-fail/surface_demuxenum_port_extra_zero.stderr new file mode 100644 index 00000000000..ae41a60d588 --- /dev/null +++ b/hydroflow/tests/compile-fail/surface_demuxenum_port_extra_zero.stderr @@ -0,0 +1,37 @@ +error[E0599]: no variant named `Square` found for enum `Shape` + --> tests/compile-fail/surface_demuxenum_port_extra_zero.rs:11:18 + | +6 | enum Shape { + | ---------- variant `Square` not found here +... +11 | my_demux[Square] -> for_each(std::mem::drop); + | ^^^^^^ variant not found in `Shape` + +error[E0277]: the trait bound `Shape: SingleVariant` is not satisfied + --> tests/compile-fail/surface_demuxenum_port_extra_zero.rs:10:52 + | +10 | my_demux = source_iter([]) -> demux_enum::(); + | ^^^^^ the trait `SingleVariant` is not implemented for `Shape` + | + = note: requires that the enum have only one variant. + = note: ensure there are no missing outputs; there must be exactly one output for each enum variant. + +error[E0277]: the trait bound `Shape: SingleVariant` is not satisfied + --> tests/compile-fail/surface_demuxenum_port_extra_zero.rs:10:39 + | +10 | my_demux = source_iter([]) -> demux_enum::(); + | ^^^^^^^^^^^^^^^^^^^^^ the trait `SingleVariant` is not implemented for `Shape` + | + = note: requires that the enum have only one variant. + = note: ensure there are no missing outputs; there must be exactly one output for each enum variant. + +error[E0277]: the trait bound `Shape: SingleVariant` is not satisfied + --> tests/compile-fail/surface_demuxenum_port_extra_zero.rs:10:39 + | +10 | my_demux = source_iter([]) -> demux_enum::(); + | _______________________________________^ +11 | | my_demux[Square] -> for_each(std::mem::drop); + | |____________________________________________________^ the trait `SingleVariant` is not implemented for `Shape` + | + = note: requires that the enum have only one variant. + = note: ensure there are no missing outputs; there must be exactly one output for each enum variant. diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_port_missing_one.rs b/hydroflow/tests/compile-fail/surface_demuxenum_port_missing_one.rs new file mode 100644 index 00000000000..7cf98339ada --- /dev/null +++ b/hydroflow/tests/compile-fail/surface_demuxenum_port_missing_one.rs @@ -0,0 +1,19 @@ +use hydroflow::util::demux_enum::DemuxEnum; +use hydroflow::hydroflow_syntax; + +fn main() { + #[derive(DemuxEnum)] + enum Shape { + Square(f64), + Rectangle { w: f64, h: f64 }, + } + + let mut df = hydroflow_syntax! { + my_demux = source_iter([ + Shape::Rectangle { w: 10.0, h: 8.0 }, + Shape::Square(9.0), + ]) -> demux_enum::(); + my_demux[Rectangle] -> for_each(std::mem::drop); + }; + df.run_available(); +} diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_port_missing_one.stderr b/hydroflow/tests/compile-fail/surface_demuxenum_port_missing_one.stderr new file mode 100644 index 00000000000..1290cb4aa25 --- /dev/null +++ b/hydroflow/tests/compile-fail/surface_demuxenum_port_missing_one.stderr @@ -0,0 +1,28 @@ +error[E0277]: the trait bound `Shape: SingleVariant` is not satisfied + --> tests/compile-fail/surface_demuxenum_port_missing_one.rs:15:28 + | +15 | ]) -> demux_enum::(); + | ^^^^^ the trait `SingleVariant` is not implemented for `Shape` + | + = note: requires that the enum have only one variant. + = note: ensure there are no missing outputs; there must be exactly one output for each enum variant. + +error[E0277]: the trait bound `Shape: SingleVariant` is not satisfied + --> tests/compile-fail/surface_demuxenum_port_missing_one.rs:15:15 + | +15 | ]) -> demux_enum::(); + | ^^^^^^^^^^^^^^^^^^^^^ the trait `SingleVariant` is not implemented for `Shape` + | + = note: requires that the enum have only one variant. + = note: ensure there are no missing outputs; there must be exactly one output for each enum variant. + +error[E0277]: the trait bound `Shape: SingleVariant` is not satisfied + --> tests/compile-fail/surface_demuxenum_port_missing_one.rs:15:15 + | +15 | ]) -> demux_enum::(); + | _______________^ +16 | | my_demux[Rectangle] -> for_each(std::mem::drop); + | |_______________________________________________________^ the trait `SingleVariant` is not implemented for `Shape` + | + = note: requires that the enum have only one variant. + = note: ensure there are no missing outputs; there must be exactly one output for each enum variant. diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_port_wrong_one.rs b/hydroflow/tests/compile-fail/surface_demuxenum_port_wrong_one.rs new file mode 100644 index 00000000000..293a4b66a74 --- /dev/null +++ b/hydroflow/tests/compile-fail/surface_demuxenum_port_wrong_one.rs @@ -0,0 +1,17 @@ +use hydroflow::util::demux_enum::DemuxEnum; +use hydroflow::hydroflow_syntax; + +fn main() { + #[derive(DemuxEnum)] + enum Shape { + Square(f64), + } + + let mut df = hydroflow_syntax! { + my_demux = source_iter([ + Shape::Square(9.0), + ]) -> demux_enum::(); + my_demux[Circle] -> for_each(std::mem::drop); + }; + df.run_available(); +} diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_port_wrong_one.stderr b/hydroflow/tests/compile-fail/surface_demuxenum_port_wrong_one.stderr new file mode 100644 index 00000000000..129d7ed698b --- /dev/null +++ b/hydroflow/tests/compile-fail/surface_demuxenum_port_wrong_one.stderr @@ -0,0 +1,8 @@ +error[E0599]: no variant named `Circle` found for enum `Shape` + --> tests/compile-fail/surface_demuxenum_port_wrong_one.rs:14:18 + | +6 | enum Shape { + | ---------- variant `Circle` not found here +... +14 | my_demux[Circle] -> for_each(std::mem::drop); + | ^^^^^^ variant not found in `Shape` diff --git a/hydroflow/tests/compile-fail/surface_demuxenum_wrongenum.stderr b/hydroflow/tests/compile-fail/surface_demuxenum_wrongenum.stderr index 3df27899d5c..6758b9f894e 100644 --- a/hydroflow/tests/compile-fail/surface_demuxenum_wrongenum.stderr +++ b/hydroflow/tests/compile-fail/surface_demuxenum_wrongenum.stderr @@ -4,7 +4,7 @@ error[E0277]: the trait bound `std::option::Option<()>: DemuxEnumBase` is not sa 17 | ]) -> demux_enum::>(); | ^^^^^^^^^^ the trait `DemuxEnumBase` is not implemented for `std::option::Option<()>` | - = note: Use `#[derive(hydroflow::DemuxEnum)]` + = note: use `#[derive(hydroflow::DemuxEnum)]` = help: the trait `DemuxEnumBase` is implemented for `Shape` note: required by a bound in `check_impl_demux_enum` --> tests/compile-fail/surface_demuxenum_wrongenum.rs:17:28 @@ -36,8 +36,8 @@ error[E0277]: the trait bound `std::option::Option<()>: DemuxEnum<_>` is not sat 17 | ]) -> demux_enum::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `DemuxEnum<_>` is not implemented for `std::option::Option<()>` | - = note: Ensure there is exactly one output for each enum variant. - = note: Ensure that the type for each output is a tuple of the field for the variant: `()`, `(a,)`, or `(a, b, ...)`. + = note: ensure there is exactly one output for each enum variant. + = note: ensure that the type for each output is a tuple of the field for the variant: `()`, `(a,)`, or `(a, b, ...)`. = help: the trait `DemuxEnum<(__PusheratorCircle, __PusheratorRectangle, __PusheratorSquare)>` is implemented for `Shape` error[E0271]: type mismatch resolving `> as Pusherator>::Item == Shape` diff --git a/hydroflow/tests/surface_demux_enum.rs b/hydroflow/tests/surface_demux_enum.rs index 34291e1dd23..ffbb1ac69c6 100644 --- a/hydroflow/tests/surface_demux_enum.rs +++ b/hydroflow/tests/surface_demux_enum.rs @@ -123,3 +123,30 @@ pub fn test_demux_enum_generic() { test::(9., 10., 8., 5.); test::(9, 10, 8, 5); } + +#[multiplatform_test] +fn test_zero_variants() { + #[derive(DemuxEnum)] + enum Never {} + let (_tx, rx) = hydroflow::util::unbounded_channel::(); + + let mut df = hydroflow_syntax! { + source_stream(rx) + -> demux_enum::(); + }; + df.run_available(); +} + +#[multiplatform_test] +fn test_one_variant() { + #[derive(DemuxEnum)] + enum Request { + OnlyMessage(T), + } + + let mut df = hydroflow_syntax! { + input = source_iter([Request::OnlyMessage("hi")]) -> demux_enum::>(); + input[OnlyMessage] -> assert_eq([("hi",)]); + }; + df.run_available(); +} diff --git a/hydroflow_lang/src/graph/ops/demux_enum.rs b/hydroflow_lang/src/graph/ops/demux_enum.rs index 7492a38ab2a..9aa4c76caef 100644 --- a/hydroflow_lang/src/graph/ops/demux_enum.rs +++ b/hydroflow_lang/src/graph/ops/demux_enum.rs @@ -45,8 +45,8 @@ pub const DEMUX_ENUM: OperatorConstraints = OperatorConstraints { categories: &[OperatorCategory::MultiOut], hard_range_inn: RANGE_1, soft_range_inn: RANGE_1, - hard_range_out: &(2..), - soft_range_out: &(2..), + hard_range_out: &(..), + soft_range_out: &(..), num_args: 0, persistence_args: RANGE_0, type_args: RANGE_1, @@ -63,6 +63,7 @@ pub const DEMUX_ENUM: OperatorConstraints = OperatorConstraints { root, op_span, ident, + inputs, outputs, is_pull, op_name, @@ -75,51 +76,46 @@ pub const DEMUX_ENUM: OperatorConstraints = OperatorConstraints { .. }, diagnostics| { - assert!(!is_pull); - let enum_type = &type_args[0]; // Port idents supplied via port connections in the surface syntax. let port_idents: Vec<_> = output_ports - .iter() - .filter_map(|output_port| { - let PortIndexValue::Path(port_expr) = output_port else { - diagnostics.push(Diagnostic::spanned( - output_port.span(), - Level::Error, - format!( - "Output port from `{}(..)` must be specified and must be a valid identifier.", - op_name, - ), - )); - return None; - }; - let port_ident = syn::parse2::(quote! { #port_expr }) - .map_err(|err| diagnostics.push(err.into())) - .ok()?; - - Some(port_ident) - }) - .collect(); + .iter() + .filter_map(|output_port| { + let PortIndexValue::Path(port_expr) = output_port else { + diagnostics.push(Diagnostic::spanned( + output_port.span(), + Level::Error, + format!( + "Output port from `{}(..)` must be specified and must be a valid identifier.", + op_name, + ), + )); + return None; + }; + let port_ident = syn::parse2::(quote! { #port_expr }) + .map_err(|err| diagnostics.push(err.into())) + .ok()?; - let enum_type_turbofish = ensure_turbofish(enum_type); - let port_variant_check_match_arms = port_idents.iter().map(|port_ident| { - let enum_type_turbofish = - change_spans(enum_type_turbofish.to_token_stream(), port_ident.span()); - quote_spanned! {port_ident.span()=> - #enum_type_turbofish::#port_ident { .. } => () - } - }); - - let mut sort_permute: Vec<_> = (0..port_idents.len()).collect(); - sort_permute.sort_by_key(|&i| &port_idents[i]); - - let sorted_outputs = sort_permute.iter().map(|&i| &outputs[i]); + Some(port_ident) + }) + .collect(); // The entire purpose of this closure and match statement is to generate readable error messages: // "missing match arm: `Variant(_)` not covered." // Or "no variant named `Variant` found for enum `Shape`" // Note this uses the `enum_type`'s span. + let enum_type_turbofish = ensure_turbofish(enum_type); + let port_variant_check_match_arms = port_idents + .iter() + .map(|port_ident| { + let enum_type_turbofish = + change_spans(enum_type_turbofish.to_token_stream(), port_ident.span()); + quote_spanned! {port_ident.span()=> + #enum_type_turbofish::#port_ident { .. } => () + } + }) + .collect::>(); let root_span = change_spans(root.clone(), enum_type.span()); let write_prologue = quote_spanned! {enum_type.span()=> let _ = |__val: #enum_type| { @@ -132,16 +128,42 @@ pub const DEMUX_ENUM: OperatorConstraints = OperatorConstraints { }; }; }; - let write_iterator = quote_spanned! {op_span=> - let #ident = { - let mut __outputs = ( #( #sorted_outputs, )* ); - #root::pusherator::for_each::ForEach::new(move |__item: #enum_type| { - #root::util::demux_enum::DemuxEnum::demux_enum( - __item, - &mut __outputs, - ); - }) + + let write_iterator = if 1 == outputs.len() { + // Use `enum_type`'s span. + let map_fn = quote_spanned! {enum_type.span()=> + <#enum_type as #root::util::demux_enum::SingleVariant>::single_variant }; + if is_pull { + let input = &inputs[0]; + quote_spanned! {op_span=> + let #ident = #input.map(#map_fn); + } + } else { + let output = &outputs[0]; + quote_spanned! {op_span=> + let #ident = #root::pusherator::map::Map::new(#map_fn, #output); + } + } + } else { + assert!(!is_pull); + + let mut sort_permute: Vec<_> = (0..port_idents.len()).collect(); + sort_permute.sort_by_key(|&i| &port_idents[i]); + + let sorted_outputs = sort_permute.iter().map(|&i| &outputs[i]); + + quote_spanned! {op_span=> + let #ident = { + let mut __outputs = ( #( #sorted_outputs, )* ); + #root::pusherator::for_each::ForEach::new(move |__item: #enum_type| { + #root::util::demux_enum::DemuxEnum::demux_enum( + __item, + &mut __outputs, + ); + }) + }; + } }; Ok(OperatorWriteOutput { diff --git a/hydroflow_macro/src/lib.rs b/hydroflow_macro/src/lib.rs index 0091d5cc1b6..1d66dc65c78 100644 --- a/hydroflow_macro/src/lib.rs +++ b/hydroflow_macro/src/lib.rs @@ -12,7 +12,7 @@ use hydroflow_lang::parse::HfCode; use proc_macro2::{Ident, Literal, Span}; use quote::{format_ident, quote}; use syn::{ - parse_macro_input, parse_quote, Attribute, GenericParam, ItemEnum, LitStr, Variant, + parse_macro_input, parse_quote, Attribute, Fields, GenericParam, ItemEnum, LitStr, Variant, WherePredicate, }; @@ -243,21 +243,21 @@ pub fn derive_answer_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStrea let root = root(); let ItemEnum { - ident, + ident: item_ident, generics, variants, .. } = parse_macro_input!(item as ItemEnum); // Sort variants alphabetically. - let mut variants_sorted = variants.into_iter().collect::>(); - variants_sorted.sort_by(|a, b| a.ident.cmp(&b.ident)); + let mut variants = variants.into_iter().collect::>(); + variants.sort_by(|a, b| a.ident.cmp(&b.ident)); - let variant_pusherator_generics = variants_sorted + let variant_pusherator_generics = variants .iter() .map(|variant| format_ident!("__Pusherator{}", variant.ident)) .collect::>(); - let variant_pusherator_localvars = variants_sorted + let variant_pusherator_localvars = variants .iter() .map(|variant| { format_ident!( @@ -267,22 +267,22 @@ pub fn derive_answer_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStrea ) }) .collect::>(); - let variant_output_types = variants_sorted + let variant_output_types = variants .iter() .map(|variant| match &variant.fields { - syn::Fields::Named(fields) => { + Fields::Named(fields) => { let field_types = fields.named.iter().map(|field| &field.ty); quote! { ( #( #field_types, )* ) } } - syn::Fields::Unnamed(fields) => { + Fields::Unnamed(fields) => { let field_types = fields.unnamed.iter().map(|field| &field.ty); quote! { ( #( #field_types, )* ) } } - syn::Fields::Unit => quote!(()), + Fields::Unit => quote!(()), }) .collect::>(); @@ -306,38 +306,38 @@ pub fn derive_answer_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStrea let (impl_generics_item, ty_generics, where_clause_item) = generics.split_for_impl(); let (impl_generics, _ty_generics, where_clause) = full_generics.split_for_impl(); - let variant_pats = variants_sorted + let variant_pats = variants .iter() .zip(variant_pusherator_localvars.iter()) .map(|(variant, pushvar)| { let Variant { ident, fields, .. } = variant; - let idents = fields - .iter() - .enumerate() - .map(|(i, field)| { - field - .ident - .clone() - .unwrap_or_else(|| format_ident!("_{}", i)) - }) - .collect::>(); - let (fields_pat, push_item) = match fields { - syn::Fields::Named(_) => { - (quote!( { #( #idents, )* } ), quote!( ( #( #idents, )* ) )) - } - syn::Fields::Unnamed(_) => { - (quote!( ( #( #idents ),* ) ), quote!( ( #( #idents, )* ) )) - } - syn::Fields::Unit => (quote!(), quote!(())), - }; + let (fields_pat, push_item) = field_pattern_item(fields); quote! { Self::#ident #fields_pat => #pushvar.give(#push_item) } }); + let single_impl = (1 == variants.len()).then(|| { + let Variant { ident, fields, .. } = variants.first().unwrap(); + let (fields_pat, push_item) = field_pattern_item(fields); + let out_type = variant_output_types.first().unwrap(); + quote! { + impl #impl_generics_item #root::util::demux_enum::SingleVariant + for #item_ident #ty_generics #where_clause_item + { + type Output = #out_type; + fn single_variant(self) -> Self::Output { + match self { + Self::#ident #fields_pat => #push_item, + } + } + } + } + }); + quote! { impl #impl_generics #root::util::demux_enum::DemuxEnum<( #( #variant_pusherator_generics, )* )> - for #ident #ty_generics #where_clause + for #item_ident #ty_generics #where_clause { fn demux_enum( self, @@ -350,7 +350,30 @@ pub fn derive_answer_fn(item: proc_macro::TokenStream) -> proc_macro::TokenStrea } } - impl #impl_generics_item #root::util::demux_enum::DemuxEnumBase for #ident #ty_generics #where_clause_item {} + impl #impl_generics_item #root::util::demux_enum::DemuxEnumBase + for #item_ident #ty_generics #where_clause_item {} + + #single_impl } .into() } + +/// (fields pattern, push item expr) +fn field_pattern_item(fields: &Fields) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let idents = fields + .iter() + .enumerate() + .map(|(i, field)| { + field + .ident + .clone() + .unwrap_or_else(|| format_ident!("_{}", i)) + }) + .collect::>(); + let (fields_pat, push_item) = match fields { + Fields::Named(_) => (quote!( { #( #idents, )* } ), quote!( ( #( #idents, )* ) )), + Fields::Unnamed(_) => (quote!( ( #( #idents ),* ) ), quote!( ( #( #idents, )* ) )), + Fields::Unit => (quote!(), quote!(())), + }; + (fields_pat, push_item) +}