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