fzyzcjy / flutter_rust_bridge

Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple.
https://fzyzcjy.github.io/flutter_rust_bridge/
MIT License
4.3k stars 301 forks source link

Support third-party (mirroring) enums with struct variants #2327

Open p0lloc opened 1 month ago

p0lloc commented 1 month ago

Describe the bug

The code generator does not generate concrete code for a struct-like enum defined in a third-party crate. It is left as an opaque type and cannot be constructed on the Dart side.

Steps to reproduce

  1. Create and add a third-party crate to rust_input
  2. Write a struct-like enum in the third-party crate:
pub enum ThirdPartyEnum {
    A(String),
    B(f32)
}
  1. Reference the enum in the first-party crate like:
    pub fn third_party(value: ThirdPartyEnum) {
    // do something with value
    }
  2. Run flutter_rust_bridge_codegen generate
  3. ThirdPartyEnum is left as abstract and cannot be used in the Dart code

Logs

#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
pub enum ThirdPartyEnum { A(String), B(f32), }
 stderr=    Checking model v0.1.0 (/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/model)
target(s) in 0.07s

Dumping cargo_expand.rs into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/source/cargo_expand.rs"
Dumping hir_tree/1_parse_pack.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_tree/1_parse_pack.json"
Dumping hir_tree/2_pub_use_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_tree/2_pub_use_transformer.json"
Dumping hir_naive_flat/1_parse_pack.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_naive_flat/1_parse_pack.json"
Dumping hir_naive_flat/2_move_third_party_override_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_naive_flat/2_move_third_party_override_transformer.json"
Dumping hir_naive_flat/3_filter_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_naive_flat/3_filter_transformer.json"
parse_syn_item_struct_or_enum item_ident=Ident { sym: FirstPartyEnum, span: bytes(434..448) }
parse_syn_item_struct_or_enum item_ident=Ident { sym: ThirdPartyEnum, span: bytes(36682..36696) }
Dumping hir_flat/1_parse_pack.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_flat/1_parse_pack.json"
Dumping hir_flat/2_filter_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_flat/2_filter_transformer.json"
Dumping hir_flat/3_remove_not_defined_trait_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_flat/3_remove_not_defined_trait_transformer.json"
Dumping hir_flat/4_copy_trait_def_to_impl_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_flat/4_copy_trait_def_to_impl_transformer.json"
Dumping hir_flat/5_function_frb_override_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_flat/5_function_frb_override_transformer.json"
Dumping hir_flat/6_merge_duplicate_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_flat/6_merge_duplicate_transformer.json"
Dumping hir_flat/7_resolve_type_alias_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_flat/7_resolve_type_alias_transformer.json"
Dumping hir_flat/8_sort_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/hir_flat/8_sort_transformer.json"
parse_function function name: "first_party"
parse_function_lifetime name=first_party inputs_lifetimes=[[]] output_lifetimes=[] ans=ParseFunctionLifetimeOutput { needs_extend_lifetime_per_arg: [false] }
TypeParserWithContext.parse_type ty=String ans=Delegate(String)
TypeParserWithContext.parse_type ty=f32 ans=Primitive(F32)
TypeParserWithContext.parse_type ty=FirstPartyEnum ans=EnumRef(MirTypeEnumRef { ident: MirEnumIdent(NamespacedName { namespace: Namespace { joined_path: "crate::api::minimal" }, name: "FirstPartyEnum" }), is_exception: false })
parse_function function name: "init_app"
parse_function_lifetime name=init_app inputs_lifetimes=[] output_lifetimes=[] ans=ParseFunctionLifetimeOutput { needs_extend_lifetime_per_arg: [] }
parse_function function name: "third_party"
parse_function_lifetime name=third_party inputs_lifetimes=[[]] output_lifetimes=[] ans=ParseFunctionLifetimeOutput { needs_extend_lifetime_per_arg: [false] }
TypeParserWithContext.parse_type ty=String ans=Delegate(String)
TypeParserWithContext.parse_type ty=f32 ans=Primitive(F32)
Treat ThirdPartyEnum as opaque by compute_default_opaque
TypeParserWithContext.parse_type ty=ThirdPartyEnum ans=RustAutoOpaqueImplicit(MirTypeRustAutoOpaqueImplicit { ownership_mode: Owned, inner: MirTypeRustOpaque { namespace: Namespace { joined_path: "model" }, inner: MirRustOpaqueInner(MirLifetimeAwareType { raw: "flutter_rust_bridge::for_generated::RustAutoOpaqueInner<ThirdPartyEnum>" }), codec: Moi, dart_api_type: None, brief_name: true }, raw: MirRustAutoOpaqueRaw { string: MirLifetimeAwareType { raw: "ThirdPartyEnum" }, segments: [NameComponent { ident: "ThirdPartyEnum", args: [] }] }, reason: Some(StructOrEnumRequireOpaque), ignore: false })
Dumping early_generator/1_tentative_mir/1_parse_pack.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/early_generator/1_tentative_mir/1_parse_pack.json"
Dumping early_generator/1_tentative_mir/2_filter_trait_impl_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/early_generator/1_tentative_mir/2_filter_trait_impl_transformer.json"
Dumping early_generator/2_trait_impl_enum.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/early_generator/2_trait_impl_enum.json"
Dumping early_generator/3_proxy_enum.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/early_generator/3_proxy_enum.json"
Dumping early_generator/4_ui_related.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/early_generator/4_ui_related.json"
Dumping early_generator/5_sorter.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/hir/early_generator/5_sorter.json"
parse_function function name: "first_party"
parse_function_lifetime name=first_party inputs_lifetimes=[[]] output_lifetimes=[] ans=ParseFunctionLifetimeOutput { needs_extend_lifetime_per_arg: [false] }
TypeParserWithContext.parse_type ty=String ans=Delegate(String)
TypeParserWithContext.parse_type ty=f32 ans=Primitive(F32)
TypeParserWithContext.parse_type ty=FirstPartyEnum ans=EnumRef(MirTypeEnumRef { ident: MirEnumIdent(NamespacedName { namespace: Namespace { joined_path: "crate::api::minimal" }, name: "FirstPartyEnum" }), is_exception: false })
parse_function function name: "init_app"
parse_function_lifetime name=init_app inputs_lifetimes=[] output_lifetimes=[] ans=ParseFunctionLifetimeOutput { needs_extend_lifetime_per_arg: [] }
parse_function function name: "third_party"
parse_function_lifetime name=third_party inputs_lifetimes=[[]] output_lifetimes=[] ans=ParseFunctionLifetimeOutput { needs_extend_lifetime_per_arg: [false] }
TypeParserWithContext.parse_type ty=String ans=Delegate(String)
TypeParserWithContext.parse_type ty=f32 ans=Primitive(F32)
Treat ThirdPartyEnum as opaque by compute_default_opaque
To handle some types, `enable_lifetime: true` may need to be set. Please visit https://fzyzcjy.github.io/flutter_rust_bridge/guides/lifetimes for more details
TypeParserWithContext.parse_type ty=ThirdPartyEnum ans=RustAutoOpaqueImplicit(MirTypeRustAutoOpaqueImplicit { ownership_mode: Owned, inner: MirTypeRustOpaque { namespace: Namespace { joined_path: "model" }, inner: MirRustOpaqueInner(MirLifetimeAwareType { raw: "flutter_rust_bridge::for_generated::RustAutoOpaqueInner<ThirdPartyEnum>" }), codec: Moi, dart_api_type: None, brief_name: true }, raw: MirRustAutoOpaqueRaw { string: MirLifetimeAwareType { raw: "ThirdPartyEnum" }, segments: [NameComponent { ident: "ThirdPartyEnum", args: [] }] }, reason: Some(StructOrEnumRequireOpaque), ignore: false })
Dumping 1_parse_pack.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/mir/1_parse_pack.json"
Dumping 2_filter_trait_impl_transformer.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/mir/2_filter_trait_impl_transformer.json"
Dumping api_dart.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_info/api_dart.json"
Dumping api_dart.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_spec/api_dart.json"
Dumping api_dart/api/minimal.dart into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_text/api_dart/api/minimal.dart"
Dumping api_dart/third_party/model.dart into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_text/api_dart/third_party/model.dart"
Dumping api_dart/frb_generated.dart into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_text/api_dart/frb_generated.dart"
Dumping wire_rust.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_info/wire_rust.json"
Dumping wire_rust.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_spec/wire_rust.json"
Dumping wire_rust.rs into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_text/wire_rust.rs"
Dumping wire_c.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_spec/wire_c.json"
Dumping wire_c/content.h into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_text/wire_c/content.h"
Dumping wire_dart.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_info/wire_dart.json"
Dumping wire_dart.json into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_spec/wire_dart.json"
Dumping wire_dart/Common.dart into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_text/wire_dart/Common.dart"
Dumping wire_dart/Io.dart into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_text/wire_dart/Io.dart"
Dumping wire_dart/Web.dart into "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal/rust/target/frb_dump/generator_text/wire_dart/Web.dart"
Guessing toolchain the runner is run into
Checking presence of freezed in dev_dependencies at "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal"
Checking presence of freezed in dev_dependencies at "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal"
Checking presence of freezed_annotation in dependencies at "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal"
Checking presence of freezed_annotation in dependencies at "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal"
Checking presence of build_runner in dev_dependencies at "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal"
Checking presence of build_runner in dev_dependencies at "/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal"
Running build_runner at dart_root="/home/dev/clone/flutter_rust_bridge/frb_example/dart_minimal"
Guessing toolchain the runner is run into

Expected behavior

A struct-like enum in the first-party crate:

pub enum FirstPartyEnum {
    A(String),
    B(f32),
}

generates the following Dart code:

@freezed
sealed class FirstPartyEnum with _$FirstPartyEnum {
  const FirstPartyEnum._();

  const factory FirstPartyEnum.a(
    String field0,
  ) = FirstPartyEnum_A;
  const factory FirstPartyEnum.b(
    double field0,
  ) = FirstPartyEnum_B;
}

I expected the same behavior for my third-party crate.

Generated binding code

No response

OS

No response

Version of flutter_rust_bridge_codegen

No response

Flutter info

No response

Version of clang++

No response

Additional context

No response

welcome[bot] commented 1 month ago

Hi! Thanks for opening your first issue here! :smile:

fzyzcjy commented 1 month ago

Hi, could you please try the https://cjycode.com/flutter_rust_bridge/guides/third-party. For your own case, https://cjycode.com/flutter_rust_bridge/guides/third-party/manual/external-types may be useful.

p0lloc commented 1 month ago

Thanks for the quick response! I have read the documentation for third-party crates but I can't seem to find more information regarding struct-like enums. Are they simply not supported with automatic scanning?

The documentation for mirroring with the manual approach states "It works with basic enums too. Enums with struct variants are not yet supported".

fzyzcjy commented 1 month ago

Hmm ok, then "mirroring Enums with struct variants" is not supported yet. A workaround is to copy-paste the definition to your first party crate. Also feel free to PR to support this new feature!