Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frb_codegen/src/binary/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ pub(crate) struct GenerateCommandArgsPrimary {
#[arg(long)]
pub rust_preamble: Option<String>,

/// Use deep equality for Dart collection fields in generated non-freezed classes.
#[arg(long)]
pub dart_collection_deep_equality: bool,

/// The generated Dart enums will not have their variant names camelCased.
#[arg(long)]
pub no_dart_enums_style: bool,
Expand Down
10 changes: 10 additions & 0 deletions frb_codegen/src/binary/commands_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ fn compute_codegen_config_from_naive_command_args(args: GenerateCommandArgsPrima
dart_format_line_length: args.dart_format_line_length,
dart_preamble: args.dart_preamble,
rust_preamble: args.rust_preamble,
dart_collection_deep_equality: positive_bool_arg(args.dart_collection_deep_equality),
dart_enums_style: negative_bool_arg(args.no_dart_enums_style),
add_mod_to_lib: negative_bool_arg(args.no_add_mod_to_lib),
llvm_path: args.llvm_path,
Expand Down Expand Up @@ -191,6 +192,15 @@ mod tests {
.dart3,
Some(false)
);
assert_eq!(
run_command_line(concat([
common_args.clone(),
vec!["--dart-collection-deep-equality"]
]))
.expect("failed to parse cli args")
.dart_collection_deep_equality,
Some(true)
);
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions frb_codegen/src/library/codegen/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct Config {
pub dart_format_line_length: Option<u32>,
pub dart_preamble: Option<String>,
pub rust_preamble: Option<String>,
pub dart_collection_deep_equality: Option<bool>,
pub dart_enums_style: Option<bool>,
pub add_mod_to_lib: Option<bool>,
pub llvm_path: Option<Vec<String>>,
Expand Down Expand Up @@ -81,6 +82,7 @@ generate_merge!(
dart_format_line_length,
dart_preamble,
rust_preamble,
dart_collection_deep_equality,
dart_enums_style,
add_mod_to_lib,
llvm_path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub(super) fn parse(args: Args) -> anyhow::Result<GeneratorInternalConfig> {

Ok(GeneratorInternalConfig {
api_dart: GeneratorApiDartInternalConfig {
dart_collection_deep_equality: config.dart_collection_deep_equality.unwrap_or(false),
dart_enums_style,
dart3,
dart_decl_base_output_path: dart_output_path_pack.dart_decl_base_output_path.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct GeneratorApiDartInternalConfig {
pub dart_collection_deep_equality: bool,
pub dart_enums_style: bool,
pub dart3: bool,
pub dart_decl_base_output_path: PathBuf,
Expand Down
40 changes: 36 additions & 4 deletions frb_codegen/src/library/codegen/generator/api_dart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ mod tests {
)
}

#[test]
#[serial]
fn test_simple_dart_collection_deep_equality() -> anyhow::Result<()> {
body_with_config(
"library/codegen/generator/api_dart/mod/simple",
HashMap::from([
("api.dart", "expect_output.dart"),
("dep.dart", "expect_output2_deep_collection_equality.dart"),
("frb_generated.dart", "expect_output3.dart"),
]),
Some(true),
)
}

#[test]
#[serial]
fn test_functions() -> anyhow::Result<()> {
Expand All @@ -82,11 +96,20 @@ mod tests {
}

fn body(fixture_name: &str, expect_outputs: HashMap<&str, &str>) -> anyhow::Result<()> {
body_with_config(fixture_name, expect_outputs, None)
}

fn body_with_config(
fixture_name: &str,
expect_outputs: HashMap<&str, &str>,
dart_collection_deep_equality: Option<bool>,
) -> anyhow::Result<()> {
configure_opinionated_test_logging();
let test_fixture_dir = get_test_fixture_dir(fixture_name);
env::set_current_dir(&test_fixture_dir)?;

let config = Config::from_files_auto()?;
let mut config = Config::from_files_auto()?;
config.dart_collection_deep_equality = dart_collection_deep_equality;
let internal_config = InternalConfig::parse(&config, &MetaConfig { watch: false })?;
let mir_pack = crate::codegen::parser::parse(
&internal_config.parser,
Expand All @@ -108,12 +131,21 @@ mod tests {
for path_text in output_texts.0 {
let path = path_text.path.file_name().unwrap().to_str().unwrap();
let expect_output = expect_outputs.get(path).unwrap();
let raw_text = (path_text.text)
.all_code()
.replace(env!("CARGO_PKG_VERSION"), "{VERSION}");
let raw_text = strip_trailing_line_whitespace(
&(path_text.text)
.all_code()
.replace(env!("CARGO_PKG_VERSION"), "{VERSION}"),
);
text_golden_test(raw_text, &test_fixture_dir.join(expect_output))?;
}

Ok(())
}

fn strip_trailing_line_whitespace(text: &str) -> String {
text.lines()
.map(str::trim_end)
.collect::<Vec<_>>()
.join("\n")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use crate::codegen::generator::api_dart::spec_generator::misc::{
generate_dart_comments, generate_dart_maybe_implements_exception,
};
use crate::codegen::ir::mir::field::MirField;
use crate::codegen::ir::mir::ty::delegate::MirTypeDelegate;
use crate::codegen::ir::mir::ty::structure::MirStruct;
use crate::codegen::ir::mir::ty::MirType;
use crate::library::codegen::generator::api_dart::spec_generator::base::*;
use crate::library::codegen::generator::api_dart::spec_generator::info::ApiDartGeneratorInfoTrait;
use itertools::Itertools;
Expand All @@ -30,14 +32,16 @@ impl StructRefApiDartGenerator<'_> {
let maybe_const = if const_capable { "const " } else { "" };
let implements_exception = generate_dart_maybe_implements_exception(self.mir.is_exception);
let methods_str = &methods.code;
let dart_collection_deep_equality =
self.context.config.dart_collection_deep_equality || src.dart_collection_deep_equality;

let hashcode = if src.generate_hash {
generate_hashcode(&src.fields)
generate_hashcode(&src.fields, dart_collection_deep_equality)
} else {
"".to_owned()
};
let equals = if src.generate_eq {
generate_equals(&src.fields, class_name)
generate_equals(&src.fields, class_name, dart_collection_deep_equality)
} else {
"".to_owned()
};
Expand All @@ -56,6 +60,9 @@ impl StructRefApiDartGenerator<'_> {
{equals}
}}"
)
.lines()
.map(str::trim_end)
.join("\n")
}

fn generate_field_declarations(&self, src: &MirStruct) -> String {
Expand Down Expand Up @@ -99,14 +106,20 @@ impl StructRefApiDartGenerator<'_> {
}
}

fn generate_hashcode(fields: &[MirField]) -> String {
fn generate_hashcode(fields: &[MirField], dart_collection_deep_equality: bool) -> String {
let body = if fields.is_empty() {
"0".to_owned()
} else {
fields
.iter()
.map(|x| x.name.dart_style())
.map(|x| format!("{x}.hashCode"))
.map(|f| {
let name = f.name.dart_style();
if dart_collection_deep_equality && needs_deep_equality(&f.ty) {
format!("const DeepCollectionEquality().hash({name})")
} else {
format!("{name}.hashCode")
}
})
.join("^")
};

Expand All @@ -118,11 +131,21 @@ fn generate_hashcode(fields: &[MirField]) -> String {
)
}

fn generate_equals(fields: &[MirField], struct_name: &str) -> String {
fn generate_equals(
fields: &[MirField],
struct_name: &str,
dart_collection_deep_equality: bool,
) -> String {
let cmp = fields
.iter()
.map(|x| x.name.dart_style())
.map(|x| format!("&& {x} == other.{x}"))
.map(|f| {
let name = f.name.dart_style();
if dart_collection_deep_equality && needs_deep_equality(&f.ty) {
format!("&& const DeepCollectionEquality().equals({name}, other.{name})")
} else {
format!("&& {name} == other.{name}")
}
})
.join("");

format!(
Expand All @@ -136,3 +159,60 @@ fn generate_equals(fields: &[MirField], struct_name: &str) -> String {
"
)
}

fn needs_deep_equality(ty: &MirType) -> bool {
match ty {
MirType::Boxed(boxed) => needs_deep_equality(&boxed.inner),
MirType::GeneralList(_) | MirType::PrimitiveList(_) => true,
MirType::Delegate(delegate) => {
matches!(
delegate,
MirTypeDelegate::Array(_) | MirTypeDelegate::Map(_) | MirTypeDelegate::Set(_)
)
}
MirType::Optional(opt) => needs_deep_equality(&opt.inner),
_ => false,
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::ir::mir::ty::boxed::MirTypeBoxed;
use crate::codegen::ir::mir::ty::delegate::{MirTypeDelegateArray, MirTypeDelegateArrayMode};
use crate::codegen::ir::mir::ty::general_list::MirTypeGeneralList;
use crate::codegen::ir::mir::ty::optional::MirTypeOptional;
use crate::codegen::ir::mir::ty::primitive::MirTypePrimitive;
use crate::codegen::ir::mir::ty::primitive_list::MirTypePrimitiveList;
use crate::utils::namespace::Namespace;

#[test]
fn test_needs_deep_equality_handles_collection_wrappers() {
let primitive = MirType::Primitive(MirTypePrimitive::U8);
let list = MirType::GeneralList(MirTypeGeneralList {
inner: Box::new(primitive.clone()),
});
let primitive_list = MirType::PrimitiveList(MirTypePrimitiveList {
primitive: MirTypePrimitive::U8,
strict_dart_type: true,
});
let array = MirType::Delegate(MirTypeDelegate::Array(MirTypeDelegateArray {
namespace: Namespace::default(),
length: 3,
mode: MirTypeDelegateArrayMode::Primitive(MirTypePrimitive::U8),
}));
let boxed_list = MirType::Boxed(MirTypeBoxed {
exist_in_real_api: true,
inner: Box::new(list.clone()),
});
let optional_boxed_list = MirType::Optional(MirTypeOptional {
inner: Box::new(boxed_list),
});

assert!(needs_deep_equality(&list));
assert!(needs_deep_equality(&primitive_list));
assert!(needs_deep_equality(&array));
assert!(needs_deep_equality(&optional_boxed_list));
assert!(!needs_deep_equality(&primitive));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub(crate) fn generate_wire_func(
.into(),
TargetOrCommon::Common => format!(
"fn {func_name}_impl({params}) {return_type} {{
{HANDLER_NAME}.{handler_func_name}({wrap_info_obj}, move || {{ {code_closure} }})
{HANDLER_NAME}.{handler_func_name}({wrap_info_obj}, move || {{{code_closure} }})
}}",
HANDLER_NAME = HANDLER_NAME,
params = params
Expand Down
1 change: 1 addition & 0 deletions frb_codegen/src/library/codegen/ir/mir/ty/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct MirStruct {
pub needs_json_serializable: bool,
pub generate_hash: bool,
pub generate_eq: bool,
pub dart_collection_deep_equality: bool,
pub ui_state: bool,
pub comments: Vec<MirComment>,
}
Expand Down
22 changes: 22 additions & 0 deletions frb_codegen/src/library/codegen/parser/mir/parser/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ impl FrbAttributes {
!self.any_eq(&FrbAttribute::NonEq)
}

pub(crate) fn dart_collection_deep_equality(&self) -> bool {
self.any_eq(&FrbAttribute::DartCollectionDeepEquality)
}

pub(crate) fn positional(&self) -> bool {
self.any_eq(&FrbAttribute::Positional)
}
Expand Down Expand Up @@ -285,6 +289,7 @@ fn parse_syn_attribute(raw: &str) -> anyhow::Result<Attribute> {
}

mod frb_keyword {
syn::custom_keyword!(dart_collection_deep_equality);
syn::custom_keyword!(mirror);
syn::custom_keyword!(non_final);
syn::custom_keyword!(sync);
Expand Down Expand Up @@ -339,6 +344,7 @@ impl Parse for FrbAttributesInner {
// Alphabetical order
#[derive(Eq, PartialEq, Debug, Clone)]
enum FrbAttribute {
DartCollectionDeepEquality,
Dart2Rust(FrbAttributeSerDes),
DartCode(FrbAttributeDartCode),
Default(FrbAttributeDefaultValue),
Expand Down Expand Up @@ -421,6 +427,14 @@ impl Parse for FrbAttribute {
.or_else(|| {
parse_keyword::<type_64bit_int, _>(input, &lookahead, type_64bit_int, Type64bitInt)
})
.or_else(|| {
parse_keyword::<dart_collection_deep_equality, _>(
input,
&lookahead,
dart_collection_deep_equality,
DartCollectionDeepEquality,
)
})
// .or_else(|| {
// parse_keyword::<generate_implementor_enum, _>(
// input,
Expand Down Expand Up @@ -859,6 +873,14 @@ mod tests {
simple_keyword_tester("init", FrbAttribute::Init);
}

#[test]
fn test_dart_collection_deep_equality() {
simple_keyword_tester(
"dart_collection_deep_equality",
FrbAttribute::DartCollectionDeepEquality,
);
}

#[test]
fn test_ignore() {
simple_keyword_tester("ignore", FrbAttribute::Ignore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ impl TypeParserWithContext<'_, '_, '_> {
needs_json_serializable: attributes.json_serializable(),
generate_hash: true,
generate_eq: true,
dart_collection_deep_equality: false,
ui_state: attributes.ui_state(),
comments: parse_comments(attrs),
fields: variant
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ impl TypeParserWithContext<'_, '_, '_> {
needs_json_serializable: attributes.json_serializable(),
generate_hash: attributes.generate_hash(),
generate_eq: attributes.generate_eq(),
dart_collection_deep_equality: attributes.dart_collection_deep_equality(),
ui_state: attributes.ui_state(),
comments,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl TypeParserWithContext<'_, '_, '_> {
needs_json_serializable: false,
generate_hash: true,
generate_eq: true,
dart_collection_deep_equality: false,
ui_state: false,
comments: vec![],
fields: values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"generator": {
"api_dart": {
"dart3": true,
"dart_collection_deep_equality": false,
"dart_decl_base_output_path": "{the-working-directory}/my_dart_folder",
"dart_entrypoint_class_name": "RustLib",
"dart_enums_style": true,
Expand Down Expand Up @@ -141,4 +142,4 @@
"deps_check": true,
"needs_ffigen": false
}
}
}
Loading
Loading