Skip to main content

serde_generate/
dart.rs

1// Copyright (c) Facebook, Inc. and its affiliates
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{
5    common,
6    indent::{IndentConfig, IndentedWriter},
7    CodeGeneratorConfig, Encoding,
8};
9use heck::{CamelCase, MixedCase, SnakeCase};
10use include_dir::include_dir as include_directory;
11use serde_reflection::{ContainerFormat, Format, FormatHolder, Named, Registry, VariantFormat};
12use std::{
13    collections::{BTreeMap, HashMap},
14    io::{Result, Write},
15    path::{Path, PathBuf},
16};
17
18/// Main configuration object for code-generation in Dart.
19pub struct CodeGenerator<'a> {
20    /// Language-independent configuration.
21    config: &'a CodeGeneratorConfig,
22}
23
24/// Shared state for the code generation of a Dart source file.
25struct DartEmitter<'a, T> {
26    /// Writer.
27    out: IndentedWriter<T>,
28    /// Generator.
29    generator: &'a CodeGenerator<'a>,
30    /// Current namespace (e.g. vec!["my_package", "my_module", "MyClass"])
31    current_namespace: Vec<String>,
32    // A reference to the registry so we can look up information for special cases
33    registry: &'a Registry,
34}
35
36impl<'a> CodeGenerator<'a> {
37    /// Create a Dart code generator for the given config.
38    pub fn new(config: &'a CodeGeneratorConfig) -> Self {
39        let mut external_qualified_names = HashMap::new();
40        for (namespace, names) in &config.external_definitions {
41            for name in names {
42                external_qualified_names.insert(name.to_string(), format!("{namespace}.{name}"));
43            }
44        }
45        Self { config }
46    }
47
48    /// Output class definitions for `registry`.
49    pub fn output(&self, install_dir: std::path::PathBuf, registry: &Registry) -> Result<()> {
50        let current_namespace = self
51            .config
52            .module_name
53            .split('.')
54            .map(String::from)
55            .collect::<Vec<_>>();
56
57        let mut dir_path = install_dir;
58        std::fs::create_dir_all(&dir_path)?;
59        dir_path = dir_path.join("lib").join("src");
60        for part in &current_namespace {
61            dir_path = dir_path.join(part);
62        }
63        std::fs::create_dir_all(&dir_path)?;
64
65        for (name, format) in registry {
66            self.write_container_class(
67                &dir_path,
68                current_namespace.clone(),
69                name,
70                format,
71                registry,
72            )?;
73        }
74        self.write_helper_class(&dir_path, current_namespace.clone(), registry)?;
75        self.write_library(&dir_path, current_namespace, registry)?;
76        Ok(())
77    }
78
79    fn write_library(
80        &self,
81        install_dir: &Path,
82        current_namespace: Vec<String>,
83        registry: &Registry,
84    ) -> Result<()> {
85        let mut file =
86            std::fs::File::create(install_dir.join(self.config.module_name.clone() + ".dart"))?;
87        let mut emitter = DartEmitter {
88            out: IndentedWriter::new(&mut file, IndentConfig::Space(2)),
89            generator: self,
90            current_namespace,
91            registry,
92        };
93
94        writeln!(
95            &mut emitter.out,
96            r#"// ignore_for_file: unused_import
97library {}_types;
98
99import 'dart:typed_data';
100import 'package:meta/meta.dart';
101import 'package:tuple/tuple.dart';
102import '../serde/serde.dart';"#,
103            self.config.module_name,
104        )?;
105
106        for encoding in &self.config.encodings {
107            writeln!(
108                &mut emitter.out,
109                "import '../{0}/{0}.dart';",
110                encoding.name()
111            )?;
112        }
113
114        if let Some(files) = &self.config.external_definitions.get("import") {
115            for file in *files {
116                writeln!(&mut emitter.out, "import '{file}';")?;
117            }
118        }
119
120        writeln!(&mut emitter.out, "\nexport '../serde/serde.dart';")?;
121
122        writeln!(&mut emitter.out, "\npart 'trait_helpers.dart';")?;
123        for name in registry.keys() {
124            writeln!(&mut emitter.out, "part '{}.dart';", name.to_snake_case())?;
125        }
126
127        Ok(())
128    }
129
130    fn write_container_class(
131        &self,
132        dir_path: &std::path::Path,
133        current_namespace: Vec<String>,
134        name: &str,
135        format: &ContainerFormat,
136        registry: &Registry,
137    ) -> Result<()> {
138        let mut file =
139            std::fs::File::create(dir_path.join(name.to_string().to_snake_case() + ".dart"))?;
140        let mut emitter = DartEmitter {
141            out: IndentedWriter::new(&mut file, IndentConfig::Space(2)),
142            generator: self,
143            current_namespace,
144            registry,
145        };
146
147        emitter.output_preamble()?;
148        emitter.output_container(name, format)
149    }
150
151    fn write_helper_class(
152        &self,
153        dir_path: &std::path::Path,
154        current_namespace: Vec<String>,
155        registry: &Registry,
156    ) -> Result<()> {
157        let mut file = std::fs::File::create(dir_path.join("trait_helpers.dart"))?;
158        let mut emitter = DartEmitter {
159            out: IndentedWriter::new(&mut file, IndentConfig::Space(2)),
160            generator: self,
161            current_namespace,
162            registry,
163        };
164
165        emitter.output_preamble()?;
166        emitter.output_trait_helpers(registry)
167    }
168}
169
170impl<'a, T> DartEmitter<'a, T>
171where
172    T: Write,
173{
174    fn output_preamble(&mut self) -> Result<()> {
175        writeln!(
176            self.out,
177            "// ignore_for_file: type=lint, type=warning\npart of '{}.dart';",
178            self.generator.config.module_name
179        )?;
180
181        Ok(())
182    }
183
184    fn get_field_container_type(&self, name: &str) -> Option<&ContainerFormat> {
185        match self.registry.get(name) {
186            Some(container) => Some(container),
187            None => None,
188        }
189    }
190
191    // in Dart enums cannot have a static method added to them
192    // yet so we must call the extension class instead
193    fn get_class(&self, name: &str) -> String {
194        if self.generator.config.enums.c_style {
195            use ContainerFormat::Enum;
196            match self.get_field_container_type(name) {
197                // if we have an enum AND all of that enum's members are Unit
198                // then we will generate an extension class name
199                Some(Enum(variants))
200                    if variants.values().all(|f| f.value == VariantFormat::Unit) =>
201                {
202                    format!("{name}Extension")
203                }
204                _ => name.to_string(),
205            }
206        } else {
207            name.to_string()
208        }
209    }
210
211    fn quote_qualified_name(&self, name: &str) -> String {
212        match name {
213            "List" => "List_".to_string(),
214            "Map" => "Map_".to_string(),
215            name => name.to_string(),
216        }
217    }
218
219    fn quote_field(&self, name: &str) -> String {
220        match name {
221            "hashCode" => "hashCode_".to_string(),
222            "runtimeType" => "runtimeType_".to_string(),
223            name => name.to_string(),
224        }
225    }
226
227    fn quote_type(&self, format: &Format) -> String {
228        use Format::*;
229        match format {
230            TypeName(x) => self.quote_qualified_name(x),
231            Unit => "Unit".into(),
232            Bool => "bool".into(),
233            I8 => "int".into(),
234            I16 => "int".into(),
235            I32 => "int".into(),
236            I64 => "int".into(),
237            I128 => "Int128".into(),
238            U8 => "int".into(),
239            U16 => "int".into(),
240            U32 => "int".into(),
241            U64 => "Uint64".into(),
242            U128 => "Uint128".into(),
243            F32 => "double".into(),
244            F64 => "double".into(),
245            Char => "int".into(),
246            Str => "String".into(),
247            Bytes => "Bytes".into(),
248
249            Option(format) => format!("{}?", self.quote_type(format)),
250            Seq(format) => format!("List<{}>", self.quote_type(format)),
251            Map { key, value } => {
252                format!("Map<{}, {}>", self.quote_type(key), self.quote_type(value))
253            }
254            Tuple(formats) => format!("Tuple{}<{}>", formats.len(), self.quote_types(formats)),
255            TupleArray { content, size: _ } => format!("List<{}>", self.quote_type(content)),
256            Variable(_) => panic!("unexpected value"),
257        }
258    }
259
260    fn quote_types(&self, formats: &[Format]) -> String {
261        formats
262            .iter()
263            .map(|f| self.quote_type(f))
264            .collect::<Vec<_>>()
265            .join(", ")
266    }
267
268    fn quote_serialize_value(&self, value: &str, format: &Format) -> String {
269        use Format::*;
270        match format {
271            TypeName(_) => format!("{value}.serialize(serializer);"),
272            Unit => format!("serializer.serializeUnit({value});"),
273            Bool => format!("serializer.serializeBool({value});"),
274            I8 => format!("serializer.serializeInt8({value});"),
275            I16 => format!("serializer.serializeInt16({value});"),
276            I32 => format!("serializer.serializeInt32({value});"),
277            I64 => format!("serializer.serializeInt64({value});"),
278            I128 => format!("serializer.serializeInt128({value});"),
279            U8 => format!("serializer.serializeUint8({value});"),
280            U16 => format!("serializer.serializeUint16({value});"),
281            U32 => format!("serializer.serializeUint32({value});"),
282            U64 => format!("serializer.serializeUint64({value});"),
283            U128 => format!("serializer.serializeUint128({value});"),
284            F32 => format!("serializer.serializeFloat32({value});"),
285            F64 => format!("serializer.serializeFloat64({value});"),
286            Char => format!("serializer.serializeChar({value});"),
287            Str => format!("serializer.serializeString({value});"),
288            Bytes => format!("serializer.serializeBytes({value});"),
289            _ => format!(
290                "{}.serialize{}({}, serializer);",
291                self.quote_qualified_name("TraitHelpers"),
292                common::mangle_type(format).to_camel_case(),
293                value
294            ),
295        }
296    }
297
298    fn quote_deserialize(&self, format: &Format) -> String {
299        use Format::*;
300        match format {
301            TypeName(name) => {
302                format!(
303                    "{}.deserialize(deserializer)",
304                    self.quote_qualified_name(&self.get_class(name))
305                )
306            }
307            Unit => "deserializer.deserializeUnit()".to_string(),
308            Bool => "deserializer.deserializeBool()".to_string(),
309            I8 => "deserializer.deserializeInt8()".to_string(),
310            I16 => "deserializer.deserializeInt16()".to_string(),
311            I32 => "deserializer.deserializeInt32()".to_string(),
312            I64 => "deserializer.deserializeInt64()".to_string(),
313            I128 => "deserializer.deserializeInt128()".to_string(),
314            U8 => "deserializer.deserializeUint8()".to_string(),
315            U16 => "deserializer.deserializeUint16()".to_string(),
316            U32 => "deserializer.deserializeUint32()".to_string(),
317            U64 => "deserializer.deserializeUint64()".to_string(),
318            U128 => "deserializer.deserializeUint128()".to_string(),
319            F32 => "deserializer.deserializeFloat32()".to_string(),
320            F64 => "deserializer.deserializeFloat64()".to_string(),
321            Char => "deserializer.deserializeChar()".to_string(),
322            Str => "deserializer.deserializeString()".to_string(),
323            Bytes => "deserializer.deserializeBytes()".to_string(),
324            _ => format!(
325                "{}.deserialize{}(deserializer)",
326                self.quote_qualified_name("TraitHelpers"),
327                common::mangle_type(format).to_camel_case(),
328            ),
329        }
330    }
331
332    fn enter_class(&mut self, name: &str) {
333        self.out.indent();
334        self.current_namespace.push(name.to_string());
335    }
336
337    fn leave_class(&mut self) {
338        self.out.unindent();
339        self.current_namespace.pop();
340    }
341
342    fn output_trait_helpers(&mut self, registry: &Registry) -> Result<()> {
343        let mut subtypes = BTreeMap::new();
344        for format in registry.values() {
345            format
346                .visit(&mut |f| {
347                    if Self::needs_helper(f) {
348                        subtypes.insert(common::mangle_type(f), f.clone());
349                    }
350                    Ok(())
351                })
352                .unwrap();
353        }
354        writeln!(self.out, "class TraitHelpers {{")?;
355        self.enter_class("TraitHelpers");
356        for (mangled_name, subtype) in &subtypes {
357            self.output_serialization_helper(mangled_name, subtype)?;
358            self.output_deserialization_helper(mangled_name, subtype)?;
359        }
360        self.leave_class();
361        writeln!(self.out, "}}\n")
362    }
363
364    fn needs_helper(format: &Format) -> bool {
365        use Format::*;
366        matches!(
367            format,
368            Option(_) | Seq(_) | Map { .. } | Tuple(_) | TupleArray { .. }
369        )
370    }
371
372    fn output_serialization_helper(&mut self, name: &str, format0: &Format) -> Result<()> {
373        use Format::*;
374
375        write!(
376            self.out,
377            "static void serialize{}({} value, BinarySerializer serializer) {{",
378            name.to_camel_case(),
379            self.quote_type(format0)
380        )?;
381        self.out.indent();
382        match format0 {
383            Option(format) => {
384                write!(
385                    self.out,
386                    r#"
387if (value == null) {{
388    serializer.serializeOptionTag(false);
389}} else {{
390    serializer.serializeOptionTag(true);
391    {}
392}}
393"#,
394                    self.quote_serialize_value("value", format)
395                )?;
396            }
397
398            Seq(format) => {
399                write!(
400                    self.out,
401                    r#"
402serializer.serializeLength(value.length);
403for (final item in value) {{
404    {}
405}}
406"#,
407                    self.quote_serialize_value("item", format)
408                )?;
409            }
410
411            Map { key, value } => {
412                write!(
413                    self.out,
414                    r#"
415serializer.serializeLength(value.length);
416final offsets = List<int>.filled(value.length, 0);
417var count = 0;
418value.entries.forEach((entry) {{
419  offsets[count++] = serializer.offset;
420  {}
421  {}
422}});
423"#,
424                    self.quote_serialize_value("entry.key", key),
425                    self.quote_serialize_value("entry.value", value)
426                )?;
427            }
428
429            Tuple(formats) => {
430                writeln!(self.out)?;
431                for (index, format) in formats.iter().enumerate() {
432                    let expr = format!("value.item{}", index + 1);
433                    writeln!(self.out, "{}", self.quote_serialize_value(&expr, format))?;
434                }
435            }
436
437            TupleArray { content, size } => {
438                write!(
439                    self.out,
440                    r#"
441assert (value.length == {});
442for (final item in value) {{
443    {}
444}}
445"#,
446                    size,
447                    self.quote_serialize_value("item", content),
448                )?;
449            }
450
451            _ => panic!("unexpected case"),
452        }
453        self.out.unindent();
454        writeln!(self.out, "}}\n")
455    }
456
457    fn output_deserialization_helper(&mut self, name: &str, format0: &Format) -> Result<()> {
458        use Format::*;
459
460        write!(
461            self.out,
462            "static {} deserialize{}(BinaryDeserializer deserializer) {{",
463            self.quote_type(format0),
464            name.to_camel_case(),
465        )?;
466        self.out.indent();
467        match format0 {
468            Option(format) => {
469                write!(
470                    self.out,
471                    r#"
472final tag = deserializer.deserializeOptionTag();
473if (tag) {{
474    return {};
475}} else {{
476    return null;
477}}
478"#,
479                    self.quote_deserialize(format),
480                )?;
481            }
482
483            Seq(format) => {
484                write!(
485                    self.out,
486                    r#"
487final length = deserializer.deserializeLength();
488return List.generate(length, (_) => {0});
489"#,
490                    self.quote_deserialize(format)
491                )?;
492            }
493
494            Map { key, value } => {
495                write!(
496                    self.out,
497                    r#"
498final length = deserializer.deserializeLength();
499final obj = <{0}, {1}>{{}};
500var previousKeyStart = 0;
501var previousKeyEnd = 0;
502for (var i = 0; i < length; i++) {{
503    final keyStart = deserializer.offset;
504    {0} key = {2};
505    final keyEnd = deserializer.offset;
506    if (i > 0) {{
507        deserializer.checkThatKeySlicesAreIncreasing(
508            Slice(previousKeyStart, previousKeyEnd),
509            Slice(keyStart, keyEnd),
510        );
511    }}
512    previousKeyStart = keyStart;
513    previousKeyEnd = keyEnd;
514    {1} value = {3};
515    obj.putIfAbsent(key, () => value);
516}}
517return obj;
518"#,
519                    self.quote_type(key),
520                    self.quote_type(value),
521                    self.quote_deserialize(key),
522                    self.quote_deserialize(value),
523                )?;
524            }
525
526            Tuple(formats) => {
527                write!(
528                    self.out,
529                    r#"
530return {}({}
531);
532"#,
533                    self.quote_type(format0),
534                    formats
535                        .iter()
536                        .map(|f| format!("\n    {}", self.quote_deserialize(f)))
537                        .collect::<Vec<_>>()
538                        .join(",")
539                )?;
540            }
541
542            TupleArray { content, size } => {
543                write!(
544                    self.out,
545                    r#"
546final obj = List<{0}>.filled({1}, 0);
547for (var i = 0; i < {1}; i++) {{
548    obj[i] = {2};
549}}
550return obj;
551"#,
552                    self.quote_type(content),
553                    size,
554                    self.quote_deserialize(content)
555                )?;
556            }
557
558            _ => panic!("unexpected case"),
559        }
560        self.out.unindent();
561        writeln!(self.out, "}}\n")
562    }
563
564    fn output_container(&mut self, name: &str, format: &ContainerFormat) -> Result<()> {
565        use ContainerFormat::*;
566        let fields = match format {
567            UnitStruct => Vec::new(),
568            NewTypeStruct(format) => {
569                vec![Named {
570                    name: "value".to_string(),
571                    value: format.as_ref().clone(),
572                }]
573            }
574            TupleStruct(formats) => formats
575                .iter()
576                .enumerate()
577                .map(|(i, f)| Named {
578                    name: format!("field{i}"),
579                    value: f.clone(),
580                })
581                .collect::<Vec<_>>(),
582            Struct(fields) => fields.clone(),
583            Enum(variants) => {
584                // When we find an enum with all Unit variants, we ser/de as a regular Dart enum.
585                if ((self.generator.config.enums.c_style
586                    && !self.generator.config.enums.output_type.contains_key(name))
587                    || self.generator.config.enums.output_type.get(name) == Some(&"enum"))
588                    && variants.values().all(|f| f.value == VariantFormat::Unit)
589                {
590                    self.output_enum_container(name, variants)?;
591                } else if (self.generator.config.enums.sealed
592                    && !self.generator.config.enums.output_type.contains_key(name))
593                    || self.generator.config.enums.output_type.get(name) == Some(&"sealed")
594                {
595                    self.output_enum_class_container(name, variants, "sealed")?;
596                } else {
597                    self.output_enum_class_container(name, variants, "abstract")?;
598                }
599                return Ok(());
600            }
601        };
602        self.output_struct_or_variant_container(None, None, name, &fields)
603    }
604
605    fn output_struct_or_variant_container(
606        &mut self,
607        variant_base: Option<&str>,
608        variant_index: Option<u32>,
609        name: &str,
610        fields: &[Named<Format>],
611    ) -> Result<()> {
612        let field_count = fields.len();
613
614        // Beginning of class
615        writeln!(self.out)?;
616        self.output_comment(name)?;
617        if let Some(base) = variant_base {
618            writeln!(
619                self.out,
620                "@immutable\nclass {} extends {} {{",
621                self.quote_qualified_name(name),
622                base
623            )?;
624        } else {
625            writeln!(
626                self.out,
627                "@immutable\nclass {} {{",
628                self.quote_qualified_name(name)
629            )?;
630        }
631        self.enter_class(name);
632
633        // Constructor.
634        writeln!(
635            self.out,
636            "const {}({}",
637            self.quote_qualified_name(name),
638            if !fields.is_empty() { "{" } else { "" }
639        )?;
640        self.out.indent();
641        for field in fields.iter() {
642            let field_name = self.quote_field(&field.name.to_mixed_case());
643            match &field.value {
644                Format::Option(_) => writeln!(self.out, "this.{field_name},")?,
645                _ => writeln!(self.out, "required this.{field_name},")?,
646            }
647        }
648        self.out.unindent();
649        if variant_base.is_some() {
650            writeln!(
651                self.out,
652                "{}) : super();",
653                if !fields.is_empty() { "}" } else { "" }
654            )?;
655        } else {
656            writeln!(self.out, "{});", if !fields.is_empty() { "}" } else { "" })?;
657        }
658
659        if self.generator.config.serialization {
660            // Deserialize (struct) or Load (variant)
661            if variant_index.is_none() {
662                writeln!(
663                    self.out,
664                    "\nstatic {} deserialize(BinaryDeserializer deserializer) {{",
665                    self.quote_qualified_name(name)
666                )?;
667            } else {
668                writeln!(
669                    self.out,
670                    "\nstatic {} load(BinaryDeserializer deserializer) {{",
671                    self.quote_qualified_name(name)
672                )?;
673            }
674
675            self.out.indent();
676            writeln!(self.out, "deserializer.increaseContainerDepth();")?;
677            writeln!(
678                self.out,
679                "final instance = {}(",
680                self.quote_qualified_name(name)
681            )?;
682            self.out.indent();
683            for field in fields {
684                writeln!(
685                    self.out,
686                    "{}: {},",
687                    self.quote_field(&field.name.to_mixed_case()),
688                    self.quote_deserialize(&field.value)
689                )?;
690            }
691            self.out.unindent();
692            writeln!(self.out, ");")?;
693            writeln!(self.out, "deserializer.decreaseContainerDepth();")?;
694            writeln!(self.out, "return instance;")?;
695            self.out.unindent();
696            writeln!(self.out, "}}")?;
697
698            if variant_index.is_none() {
699                for encoding in &self.generator.config.encodings {
700                    self.output_class_deserialize_for_encoding(name, *encoding)?;
701                }
702            }
703        }
704
705        // Fields
706        if !fields.is_empty() {
707            writeln!(self.out)?;
708        }
709        for field in fields {
710            writeln!(
711                self.out,
712                "final {} {};",
713                self.quote_type(&field.value),
714                self.quote_field(&field.name.to_mixed_case())
715            )?;
716        }
717        if !fields.is_empty() {
718            writeln!(self.out)?;
719
720            let cls_name = self.quote_qualified_name(name);
721            writeln!(self.out, "{cls_name} copyWith({{")?;
722            self.out.indent();
723            for field in fields {
724                let field_name = self.quote_field(&field.name.to_mixed_case());
725                let field_type = self.quote_type(&field.value);
726
727                match field.value {
728                    Format::Option(_) => {
729                        writeln!(self.out, "{field_type} Function()? {field_name},")?
730                    }
731                    _ => writeln!(self.out, "{field_type}? {field_name},")?,
732                }
733            }
734            self.out.unindent();
735            writeln!(self.out, "}}) {{")?;
736            self.out.indent();
737            writeln!(self.out, "return {cls_name}(")?;
738            self.out.indent();
739
740            for field in fields {
741                let field_name = self.quote_field(&field.name.to_mixed_case());
742
743                match field.value {
744                    Format::Option(_) => writeln!(
745                        self.out,
746                        "{field_name}: {field_name} == null ? this.{field_name} : {field_name}(),"
747                    )?,
748                    _ => writeln!(self.out, "{field_name}: {field_name} ?? this.{field_name},")?,
749                }
750            }
751            self.out.unindent();
752            writeln!(self.out, ");")?;
753            self.out.unindent();
754            writeln!(self.out, "}}")?;
755        }
756
757        // Serialize
758        if self.generator.config.serialization {
759            writeln!(self.out, "\nvoid serialize(BinarySerializer serializer) {{",)?;
760            self.out.indent();
761            writeln!(self.out, "serializer.increaseContainerDepth();")?;
762            if let Some(index) = variant_index {
763                writeln!(self.out, "serializer.serializeVariantIndex({index});")?;
764            }
765            for field in fields {
766                writeln!(
767                    self.out,
768                    "{}",
769                    self.quote_serialize_value(
770                        &self.quote_field(&field.name.to_mixed_case()),
771                        &field.value
772                    )
773                )?;
774            }
775            writeln!(self.out, "serializer.decreaseContainerDepth();")?;
776            self.out.unindent();
777            writeln!(self.out, "}}")?;
778
779            if variant_index.is_none() {
780                for encoding in &self.generator.config.encodings {
781                    self.output_class_serialize_for_encoding(*encoding)?;
782                }
783            }
784        }
785
786        // Equality
787        write!(self.out, "\n@override")?;
788        write!(self.out, "\nbool operator ==(Object other) {{")?;
789        self.out.indent();
790
791        writeln!(self.out, "\nif (identical(this, other)) return true;")?;
792        writeln!(
793            self.out,
794            "if (other.runtimeType != runtimeType) return false;"
795        )?;
796        write!(self.out, "\nreturn other is {name}")?;
797
798        self.out.indent();
799        for field in fields.iter() {
800            // because the Dart functions of listEquals and mapEquals accept nullable
801            // we only care about the data type and can discard the enclosing Format::Option
802            let value = if let Format::Option(value) = &field.value {
803                value
804            } else {
805                &field.value
806            };
807
808            let stmt = match value {
809                Format::Seq(_) => {
810                    format!(
811                        "listEquals({0}, other.{0})",
812                        self.quote_field(&field.name.to_mixed_case())
813                    )
814                }
815                Format::TupleArray {
816                    content: _,
817                    size: _,
818                } => format!(
819                    "listEquals({0}, other.{0})",
820                    self.quote_field(&field.name.to_mixed_case())
821                ),
822                Format::Map { .. } => {
823                    format!(
824                        "mapEquals({0}, other.{0})",
825                        self.quote_field(&field.name.to_mixed_case())
826                    )
827                }
828                _ => format!(
829                    "{0} == other.{0}",
830                    self.quote_field(&field.name.to_mixed_case())
831                ),
832            };
833
834            write!(self.out, "\n&& {stmt}")?;
835        }
836        writeln!(self.out, ";")?;
837        self.out.unindent();
838
839        self.out.unindent();
840        writeln!(self.out, "}}")?;
841
842        // Hashing
843        write!(self.out, "\n@override")?;
844        if field_count == 0 {
845            writeln!(self.out, "\nint get hashCode => runtimeType.hashCode;")?;
846        } else if field_count == 1 {
847            writeln!(
848                self.out,
849                "\nint get hashCode => {}.hashCode;",
850                fields.first().unwrap().name.to_mixed_case()
851            )?;
852        } else {
853            let use_hash_all = field_count > 20;
854
855            if use_hash_all {
856                writeln!(self.out, "\nint get hashCode => Object.hashAll([")?;
857            } else {
858                writeln!(self.out, "\nint get hashCode => Object.hash(")?;
859            }
860
861            self.out.indent();
862            self.out.indent();
863            self.out.indent();
864
865            for field in fields {
866                writeln!(
867                    self.out,
868                    "{},",
869                    self.quote_field(&field.name.to_mixed_case())
870                )?;
871            }
872
873            self.out.unindent();
874
875            if use_hash_all {
876                writeln!(self.out, "]);")?;
877            } else {
878                writeln!(self.out, ");")?;
879            }
880
881            self.out.unindent();
882            self.out.unindent();
883        }
884
885        // Generate a toString implementation in each class
886        writeln!(self.out, "\n@override\nString toString() {{")?;
887        self.out.indent();
888        writeln!(self.out, "String? fullString;")?;
889        writeln!(self.out, "\nassert(() {{")?;
890        self.out.indent();
891        writeln!(self.out, "fullString = '$runtimeType('")?;
892        self.out.indent();
893        for (index, field) in fields.iter().enumerate() {
894            if index == field_count - 1 {
895                writeln!(
896                    self.out,
897                    "'{0}: ${0}'",
898                    self.quote_field(&field.name.to_mixed_case())
899                )?;
900            } else {
901                writeln!(
902                    self.out,
903                    "'{0}: ${0}, '",
904                    self.quote_field(&field.name.to_mixed_case())
905                )?;
906            }
907        }
908        writeln!(self.out, "')';")?;
909        self.out.unindent();
910        writeln!(self.out, "return true;")?;
911        self.out.unindent();
912        writeln!(self.out, "}}());")?;
913        writeln!(self.out, "\nreturn fullString ?? '{name}';")?;
914        self.out.unindent();
915        writeln!(self.out, "}}")?;
916
917        self.out.unindent();
918        // End of class
919        self.leave_class();
920        writeln!(self.out, "}}")
921    }
922
923    fn output_class_serialize_for_encoding(&mut self, encoding: Encoding) -> Result<()> {
924        writeln!(
925            self.out,
926            r#"
927Uint8List {0}Serialize() {{
928    final serializer = {1}Serializer();
929    serialize(serializer);
930    return serializer.bytes;
931}}"#,
932            encoding.name(),
933            encoding.name().to_camel_case(),
934        )
935    }
936
937    fn output_class_deserialize_for_encoding(
938        &mut self,
939        name: &str,
940        encoding: Encoding,
941    ) -> Result<()> {
942        writeln!(
943            self.out,
944            r#"
945static {klass} {encoding}Deserialize(Uint8List input) {{
946  final deserializer = {encoding_class}Deserializer(input);
947  final value = {static_class}.deserialize(deserializer);
948  if (deserializer.offset < input.length) {{
949    throw Exception('Some input bytes were not read');
950  }}
951  return value;
952}}"#,
953            klass = self.quote_qualified_name(name),
954            static_class = self.quote_qualified_name(&self.get_class(name)),
955            encoding = encoding.name(),
956            encoding_class = encoding.name().to_camel_case()
957        )
958    }
959
960    fn output_enum_container(
961        &mut self,
962        name: &str,
963        variants: &BTreeMap<u32, Named<VariantFormat>>,
964    ) -> Result<()> {
965        writeln!(self.out)?;
966        self.output_comment(name)?;
967        writeln!(self.out, "enum {} {{", self.quote_qualified_name(name))?;
968        self.enter_class(name);
969
970        for variant in variants.values() {
971            writeln!(
972                self.out,
973                "{},",
974                self.quote_field(&variant.name.to_mixed_case())
975            )?;
976        }
977
978        self.out.unindent();
979        writeln!(self.out, "}}\n")?;
980
981        if self.generator.config.serialization {
982            writeln!(
983                self.out,
984                "extension {name}Extension on {n} {{",
985                name = name,
986                n = self.quote_qualified_name(name)
987            )?;
988            self.out.indent();
989            write!(
990                self.out,
991                "static {} deserialize(BinaryDeserializer deserializer) {{",
992                self.quote_qualified_name(name)
993            )?;
994            self.out.indent();
995            writeln!(
996                self.out,
997                r#"
998final index = deserializer.deserializeVariantIndex();
999switch (index) {{"#,
1000            )?;
1001            self.out.indent();
1002            for (index, variant) in variants {
1003                writeln!(
1004                    self.out,
1005                    "case {}: return {}.{};",
1006                    index,
1007                    self.quote_qualified_name(name),
1008                    self.quote_field(&variant.name.to_mixed_case()),
1009                )?;
1010            }
1011            writeln!(
1012                self.out,
1013                "default: throw Exception('Unknown variant index for {}: ' + index.toString());",
1014                self.quote_qualified_name(name),
1015            )?;
1016            self.out.unindent();
1017            writeln!(self.out, "}}")?;
1018            self.out.unindent();
1019            writeln!(self.out, "}}\n")?;
1020
1021            write!(self.out, "void serialize(BinarySerializer serializer) {{")?;
1022
1023            self.out.indent();
1024            writeln!(
1025                self.out,
1026                r#"
1027switch (this) {{"#,
1028            )?;
1029            self.out.indent();
1030            for (index, variant) in variants {
1031                writeln!(
1032                    self.out,
1033                    "case {}.{}: return serializer.serializeVariantIndex({});",
1034                    self.quote_qualified_name(name),
1035                    self.quote_field(&variant.name.to_mixed_case()),
1036                    index,
1037                )?;
1038            }
1039            self.out.unindent();
1040            writeln!(self.out, "}}")?;
1041            self.out.unindent();
1042            writeln!(self.out, "}}")?;
1043
1044            for encoding in &self.generator.config.encodings {
1045                self.output_class_serialize_for_encoding(*encoding)?;
1046                self.output_class_deserialize_for_encoding(name, *encoding)?;
1047            }
1048        }
1049        self.out.unindent();
1050        self.out.unindent();
1051
1052        writeln!(self.out, "}}\n")?;
1053
1054        self.leave_class();
1055        Ok(())
1056    }
1057
1058    fn output_enum_class_container(
1059        &mut self,
1060        name: &str,
1061        variants: &BTreeMap<u32, Named<VariantFormat>>,
1062        class_type: &str,
1063    ) -> Result<()> {
1064        writeln!(self.out)?;
1065        self.output_comment(name)?;
1066        writeln!(
1067            self.out,
1068            "{} class {} {{",
1069            class_type,
1070            self.quote_qualified_name(name)
1071        )?;
1072        self.enter_class(name);
1073        writeln!(self.out, "const {}();", self.quote_qualified_name(name))?;
1074
1075        if self.generator.config.serialization {
1076            writeln!(self.out, "\nvoid serialize(BinarySerializer serializer);")?;
1077            write!(
1078                self.out,
1079                "\nstatic {} deserialize(BinaryDeserializer deserializer) {{",
1080                self.quote_qualified_name(name)
1081            )?;
1082            self.out.indent();
1083            writeln!(
1084                self.out,
1085                r#"
1086int index = deserializer.deserializeVariantIndex();
1087switch (index) {{"#,
1088            )?;
1089            self.out.indent();
1090            for (index, variant) in variants {
1091                writeln!(
1092                    self.out,
1093                    "case {}: return {}{}.load(deserializer);",
1094                    index,
1095                    self.quote_qualified_name(name).to_camel_case(),
1096                    self.quote_field(&variant.name),
1097                )?;
1098            }
1099            writeln!(
1100                self.out,
1101                "default: throw Exception('Unknown variant index for {}: ' + index.toString());",
1102                self.quote_qualified_name(name),
1103            )?;
1104            self.out.unindent();
1105            writeln!(self.out, "}}")?;
1106            self.out.unindent();
1107            writeln!(self.out, "}}")?;
1108
1109            for encoding in &self.generator.config.encodings {
1110                self.output_class_serialize_for_encoding(*encoding)?;
1111                self.output_class_deserialize_for_encoding(name, *encoding)?;
1112            }
1113        }
1114        self.out.unindent();
1115        self.out.unindent();
1116
1117        writeln!(self.out, "}}\n")?;
1118
1119        self.output_variants(name, variants)?;
1120        self.leave_class();
1121        Ok(())
1122    }
1123
1124    fn output_variants(
1125        &mut self,
1126        base: &str,
1127        variants: &BTreeMap<u32, Named<VariantFormat>>,
1128    ) -> Result<()> {
1129        for (index, variant) in variants {
1130            self.output_variant(
1131                base,
1132                *index,
1133                &format!("{}{}", base, &variant.name),
1134                &variant.value,
1135            )?;
1136        }
1137        Ok(())
1138    }
1139
1140    fn output_variant(
1141        &mut self,
1142        base: &str,
1143        index: u32,
1144        name: &str,
1145        variant: &VariantFormat,
1146    ) -> Result<()> {
1147        use VariantFormat::*;
1148        let fields = match variant {
1149            Unit => Vec::new(),
1150            NewType(format) => vec![Named {
1151                name: "value".to_string(),
1152                value: format.as_ref().clone(),
1153            }],
1154            Tuple(formats) => formats
1155                .iter()
1156                .enumerate()
1157                .map(|(i, f)| Named {
1158                    name: format!("field{i}"),
1159                    value: f.clone(),
1160                })
1161                .collect(),
1162            Struct(fields) => fields.clone(),
1163            Variable(_) => panic!("incorrect value"),
1164        };
1165        self.output_struct_or_variant_container(
1166            Some(&self.quote_qualified_name(base)),
1167            Some(index),
1168            name,
1169            &fields,
1170        )
1171    }
1172
1173    fn output_comment(&mut self, name: &str) -> std::io::Result<()> {
1174        let mut path = self.current_namespace.clone();
1175        path.push(name.to_string());
1176        if let Some(doc) = self.generator.config.comments.get(&path) {
1177            let text = textwrap::indent(doc, "/// ").replace("\n\n", "\n///\n");
1178            write!(self.out, "{text}")?;
1179        }
1180        Ok(())
1181    }
1182}
1183
1184/// Installer for generated source files in Dart.
1185pub struct Installer {
1186    install_dir: PathBuf,
1187}
1188
1189impl Installer {
1190    pub fn new(install_dir: PathBuf) -> Self {
1191        Installer { install_dir }
1192    }
1193
1194    fn install_runtime(
1195        &self,
1196        source_dir: include_dir::Dir,
1197        path: &str,
1198    ) -> std::result::Result<(), Box<dyn std::error::Error>> {
1199        let dir_path = self.install_dir.join(path);
1200        std::fs::create_dir_all(&dir_path)?;
1201        for entry in source_dir.files() {
1202            let mut file = std::fs::File::create(dir_path.join(entry.path()))?;
1203            file.write_all(entry.contents())?;
1204        }
1205        Ok(())
1206    }
1207
1208    fn write_package(&self, install_dir: &Path, module_name: &str) -> Result<()> {
1209        let mut file = std::fs::File::create(install_dir.join("pubspec.yaml"))?;
1210        let mut out = IndentedWriter::new(&mut file, IndentConfig::Space(2));
1211        writeln!(
1212            &mut out,
1213            r#"name: {module_name}
1214
1215environment:
1216  sdk: '>=3.0.0 <4.0.0'
1217
1218dependencies:
1219  meta: ^1.0.0
1220  tuple: ^2.0.0
1221"#
1222        )?;
1223        Ok(())
1224    }
1225}
1226
1227impl crate::SourceInstaller for Installer {
1228    type Error = Box<dyn std::error::Error>;
1229
1230    fn install_module(
1231        &self,
1232        config: &CodeGeneratorConfig,
1233        registry: &Registry,
1234    ) -> std::result::Result<(), Self::Error> {
1235        let generator = CodeGenerator::new(config);
1236        generator.output(self.install_dir.clone(), registry)?;
1237
1238        // Write the `pubspec.yaml` package manifest file.
1239        if config.package_manifest {
1240            self.write_package(&self.install_dir, &config.module_name)?;
1241        }
1242
1243        // Write the main module file to export the public API.
1244        std::fs::write(
1245            self.install_dir
1246                .join("lib")
1247                .join(format!("{}.dart", &config.module_name)),
1248            format!(
1249                "export 'src/{name}/{name}.dart';",
1250                name = &config.module_name
1251            ),
1252        )?;
1253
1254        Ok(())
1255    }
1256
1257    fn install_serde_runtime(&self) -> std::result::Result<(), Self::Error> {
1258        self.install_runtime(include_directory!("runtime/dart/serde"), "lib/src/serde")
1259    }
1260
1261    fn install_bincode_runtime(&self) -> std::result::Result<(), Self::Error> {
1262        self.install_runtime(
1263            include_directory!("runtime/dart/bincode"),
1264            "lib/src/bincode",
1265        )
1266    }
1267
1268    fn install_bcs_runtime(&self) -> std::result::Result<(), Self::Error> {
1269        self.install_runtime(include_directory!("runtime/dart/bcs"), "lib/src/bcs")
1270    }
1271}