nimbus_fml/backends/kotlin/gen_structs/
structural.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::fmt::Display;
6
7use super::common::{self, code_type};
8use crate::backends::{LiteralRenderer, VariablesType};
9use crate::{
10    backends::{CodeOracle, CodeType, TypeIdentifier},
11    intermediate_representation::Literal,
12};
13
14pub(crate) struct OptionalCodeType {
15    inner: TypeIdentifier,
16}
17
18impl OptionalCodeType {
19    pub(crate) fn new(inner: &TypeIdentifier) -> Self {
20        Self {
21            inner: inner.clone(),
22        }
23    }
24}
25
26impl CodeType for OptionalCodeType {
27    /// The language specific label used to reference this type. This will be used in
28    /// method signatures and property declarations.
29    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
30        format!(
31            "{item}?",
32            item = oracle.find(&self.inner).type_label(oracle),
33        )
34    }
35
36    /// The language specific expression that gets a value of the `prop` from the `vars` object.
37    fn property_getter(
38        &self,
39        oracle: &dyn CodeOracle,
40        vars: &dyn Display,
41        prop: &dyn Display,
42        default: &dyn Display,
43    ) -> String {
44        // all getters are optional.
45        code_type::property_getter(self, oracle, vars, prop, default)
46    }
47
48    fn value_getter(
49        &self,
50        oracle: &dyn CodeOracle,
51        vars: &dyn Display,
52        prop: &dyn Display,
53    ) -> String {
54        code_type::value_getter(self, oracle, vars, prop)
55    }
56
57    /// The name of the type as it's represented in the `Variables` object.
58    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
59    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
60        oracle.find(&self.inner).create_transform(oracle)
61    }
62
63    /// The name of the type as it's represented in the `Variables` object.
64    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
65    fn variables_type(&self, oracle: &dyn CodeOracle) -> VariablesType {
66        oracle.find(&self.inner).variables_type(oracle)
67    }
68
69    /// The method call here will use the `create_transform` to transform the value coming out of
70    /// the `Variables` object into the desired type.
71    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
72        oracle.find(&self.inner).value_mapper(oracle)
73    }
74
75    /// The method call to merge the value with the defaults.
76    ///
77    /// This may use the `merge_transform`.
78    ///
79    /// If this returns `None`, no merging happens, and implicit `null` replacement happens.
80    fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
81        oracle.find(&self.inner).value_merger(oracle, default)
82    }
83
84    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
85        let inner = oracle.find(&self.inner).defaults_type(oracle);
86        format!("{}?", inner)
87    }
88
89    fn defaults_mapper(
90        &self,
91        oracle: &dyn CodeOracle,
92        value: &dyn Display,
93        vars: &dyn Display,
94    ) -> Option<String> {
95        let id = "it";
96        let mapper = oracle
97            .find(&self.inner)
98            .defaults_mapper(oracle, &id, vars)?;
99        Some(format!(
100            "{value}?.let {{ {mapper} }}",
101            value = value,
102            mapper = mapper
103        ))
104    }
105
106    /// Implement these in different code types, and call recursively from different code types.
107    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
108        // We want to return None if the inner's json transform is none,
109        // but if it's not, then use `prop?` as the new prop
110        let prop = format!("{}?", prop);
111        oracle.find(&self.inner).as_json_transform(oracle, &prop)
112    }
113
114    /// A representation of the given literal for this type.
115    /// N.B. `Literal` is aliased from `serde_json::Value`.
116    fn literal(
117        &self,
118        oracle: &dyn CodeOracle,
119        ctx: &dyn Display,
120        renderer: &dyn LiteralRenderer,
121        literal: &Literal,
122    ) -> String {
123        match literal {
124            serde_json::Value::Null => "null".to_string(),
125            _ => oracle
126                .find(&self.inner)
127                .literal(oracle, ctx, renderer, literal),
128        }
129    }
130}
131
132// Map type
133
134pub(crate) struct MapCodeType {
135    k_type: TypeIdentifier,
136    v_type: TypeIdentifier,
137}
138
139impl MapCodeType {
140    pub(crate) fn new(k: &TypeIdentifier, v: &TypeIdentifier) -> Self {
141        Self {
142            k_type: k.clone(),
143            v_type: v.clone(),
144        }
145    }
146}
147
148impl CodeType for MapCodeType {
149    fn property_getter(
150        &self,
151        oracle: &dyn CodeOracle,
152        vars: &dyn Display,
153        prop: &dyn Display,
154        default: &dyn Display,
155    ) -> String {
156        code_type::property_getter(self, oracle, vars, prop, default)
157    }
158
159    /// The language specific label used to reference this type. This will be used in
160    /// method signatures and property declarations.
161    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
162        format!(
163            "Map<{k}, {v}>",
164            k = oracle.find(&self.k_type).type_label(oracle),
165            v = oracle.find(&self.v_type).type_label(oracle),
166        )
167    }
168
169    fn value_getter(
170        &self,
171        oracle: &dyn CodeOracle,
172        vars: &dyn Display,
173        prop: &dyn Display,
174    ) -> String {
175        let v_type = oracle.find(&self.v_type);
176        format!(
177            "{vars}.get{vt}Map({prop})",
178            vars = vars,
179            vt = v_type.variables_type(oracle),
180            prop = common::quoted(prop),
181        )
182    }
183
184    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
185        let k_type = oracle.find(&self.k_type);
186        let v_type = oracle.find(&self.v_type);
187        Some(
188            match (
189                k_type.create_transform(oracle),
190                v_type.create_transform(oracle),
191            ) {
192                (Some(k), Some(v)) => {
193                    if v.starts_with('{') {
194                        format!("mapEntriesNotNull({k}) {v}", k = k, v = v)
195                    } else {
196                        format!("mapEntriesNotNull({k}, {v})", k = k, v = v)
197                    }
198                }
199                (None, Some(v)) => {
200                    if v.starts_with('{') {
201                        format!("mapValuesNotNull {v}", v = v)
202                    } else {
203                        format!("mapValuesNotNull({v})", v = v)
204                    }
205                }
206                // We could do something with keys, but it's only every strings and enums.
207                (Some(k), None) => format!("mapKeysNotNull({k})", k = k),
208                _ => return None,
209            },
210        )
211    }
212
213    fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
214        let v_type = oracle.find(&self.v_type);
215        Some(match v_type.merge_transform(oracle) {
216            Some(transform) if transform.starts_with('{') => format!(
217                "mergeWith({default}) {transform}",
218                default = default,
219                transform = transform
220            ),
221            Some(transform) => format!(
222                "mergeWith({default}, {transform})",
223                default = default,
224                transform = transform
225            ),
226            None => format!("mergeWith({})", default),
227        })
228    }
229
230    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
231        let vtype = oracle.find(&self.v_type).variables_type(oracle);
232
233        self.value_mapper(oracle)
234            .map(|mapper| {
235                format!(
236                    r#"{{ _vars -> _vars.as{vtype}Map()?.{mapper} }}"#,
237                    vtype = vtype,
238                    mapper = mapper
239                )
240            })
241            .or_else(|| {
242                Some(format!(
243                    r#"{{ _vars -> _vars.as{vtype}Map()? }}"#,
244                    vtype = vtype
245                ))
246            })
247    }
248
249    fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
250        let overrides = "_overrides";
251        let defaults = "_defaults";
252
253        self.value_merger(oracle, &defaults).map(|merger| {
254            format!(
255                r#"{{ {overrides}, {defaults} -> {overrides}.{merger} }}"#,
256                overrides = overrides,
257                defaults = defaults,
258                merger = merger
259            )
260        })
261    }
262
263    /// The name of the type as it's represented in the `Variables` object.
264    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
265    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
266        VariablesType::Variables
267    }
268
269    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
270        let k_type = oracle.find(&self.k_type).defaults_type(oracle);
271        let v_type = oracle.find(&self.v_type).defaults_type(oracle);
272        format!("Map<{}, {}>", k_type, v_type)
273    }
274
275    fn defaults_mapper(
276        &self,
277        oracle: &dyn CodeOracle,
278        value: &dyn Display,
279        vars: &dyn Display,
280    ) -> Option<String> {
281        let id = "it.value";
282        let mapper = oracle
283            .find(&self.v_type)
284            .defaults_mapper(oracle, &id, vars)?;
285        Some(format!(
286            "{value}.mapValues {{ {mapper} }}",
287            value = value,
288            mapper = mapper
289        ))
290    }
291
292    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
293        let k_type = oracle.find(&self.k_type);
294        let v_type = oracle.find(&self.v_type);
295        Some(
296            match (
297                k_type.as_json_transform(oracle, &"it".to_string()),
298                v_type.as_json_transform(oracle, &"it".to_string()),
299            ) {
300                (Some(k), Some(v)) => {
301                    format!(
302                        "{prop}.mapEntriesNotNull({{ {k} }}, {{ {v} }})",
303                        prop = prop,
304                        k = k,
305                        v = v
306                    )
307                }
308                (None, Some(v)) => {
309                    format!("{prop}.mapValuesNotNull {{ {v} }}", prop = prop, v = v)
310                }
311                // We could do something with keys, but it's only every strings and enums.
312                (Some(k), None) => {
313                    format!("{prop}.mapKeysNotNull {{ {k} }}", prop = prop, k = k)
314                }
315                _ => return None,
316            },
317        )
318    }
319
320    /// A representation of the given literal for this type.
321    /// N.B. `Literal` is aliased from `serde_json::Value`.
322    fn literal(
323        &self,
324        oracle: &dyn CodeOracle,
325        ctx: &dyn Display,
326        renderer: &dyn LiteralRenderer,
327        literal: &Literal,
328    ) -> String {
329        let variant = match literal {
330            serde_json::Value::Object(v) => v,
331            _ => unreachable!(),
332        };
333        let k_type = oracle.find(&self.k_type);
334        let v_type = oracle.find(&self.v_type);
335        let src: Vec<String> = variant
336            .iter()
337            .map(|(k, v)| {
338                format!(
339                    "{k} to {v}",
340                    k = k_type.literal(oracle, ctx, renderer, &Literal::String(k.clone())),
341                    v = v_type.literal(oracle, ctx, renderer, v)
342                )
343            })
344            .collect();
345
346        format!("mapOf({})", src.join(", "))
347    }
348
349    fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>> {
350        let k_type = oracle.find(&self.k_type);
351        let v_type = oracle.find(&self.v_type);
352        let mapper = map_functions(
353            k_type.create_transform(oracle),
354            v_type.create_transform(oracle),
355        );
356
357        let json_mapper = map_functions(
358            k_type.as_json_transform(oracle, &"k".to_string()),
359            v_type.as_json_transform(oracle, &"v".to_string()),
360        );
361
362        let merger = Some("org.mozilla.experiments.nimbus.internal.mergeWith".to_string());
363        Some(
364            [mapper, json_mapper, merger]
365                .iter()
366                .filter_map(|i| i.to_owned())
367                .collect(),
368        )
369    }
370}
371
372fn map_functions(k: Option<String>, v: Option<String>) -> Option<String> {
373    match (k, v) {
374        (Some(_), Some(_)) => {
375            Some("org.mozilla.experiments.nimbus.internal.mapEntriesNotNull".to_string())
376        }
377        (None, Some(_)) => {
378            Some("org.mozilla.experiments.nimbus.internal.mapValuesNotNull".to_string())
379        }
380        (Some(_), None) => {
381            Some("org.mozilla.experiments.nimbus.internal.mapKeysNotNull".to_string())
382        }
383        _ => None,
384    }
385}
386
387// List type
388
389pub(crate) struct ListCodeType {
390    inner: TypeIdentifier,
391}
392
393impl ListCodeType {
394    pub(crate) fn new(inner: &TypeIdentifier) -> Self {
395        Self {
396            inner: inner.clone(),
397        }
398    }
399}
400
401impl CodeType for ListCodeType {
402    /// The language specific label used to reference this type. This will be used in
403    /// method signatures and property declarations.
404    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
405        format!(
406            "List<{item}>",
407            item = oracle.find(&self.inner).type_label(oracle),
408        )
409    }
410
411    fn property_getter(
412        &self,
413        oracle: &dyn CodeOracle,
414        vars: &dyn Display,
415        prop: &dyn Display,
416        default: &dyn Display,
417    ) -> String {
418        code_type::property_getter(self, oracle, vars, prop, default)
419    }
420
421    fn value_getter(
422        &self,
423        oracle: &dyn CodeOracle,
424        vars: &dyn Display,
425        prop: &dyn Display,
426    ) -> String {
427        let vtype = oracle.find(&self.inner).variables_type(oracle);
428        format!(
429            "{vars}.get{vt}List(\"{prop}\")",
430            vars = vars,
431            vt = vtype,
432            prop = prop
433        )
434    }
435
436    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
437        let transform = oracle.find(&self.inner).create_transform(oracle)?;
438        Some(if transform.starts_with('{') {
439            format!("mapNotNull {}", transform)
440        } else {
441            format!("mapNotNull({})", transform)
442        })
443    }
444
445    fn value_merger(&self, _oracle: &dyn CodeOracle, _default: &dyn Display) -> Option<String> {
446        // We never merge lists.
447        None
448    }
449
450    /// The name of the type as it's represented in the `Variables` object.
451    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
452    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
453        // Our current implementation of Variables doesn't have a getListList() or getListMap().
454        // We do allow getVariablesList and getVariablesMap, but not an vars.asList().
455        unimplemented!("Lists and maps of lists aren't supported. The workaround is to use a list of map of list holder objects")
456    }
457
458    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
459        let inner = oracle.find(&self.inner).defaults_type(oracle);
460        format!("List<{}>", inner)
461    }
462
463    fn defaults_mapper(
464        &self,
465        oracle: &dyn CodeOracle,
466        value: &dyn Display,
467        vars: &dyn Display,
468    ) -> Option<String> {
469        let id = "it";
470        let mapper = oracle
471            .find(&self.inner)
472            .defaults_mapper(oracle, &id, vars)?;
473        Some(format!(
474            "{value}.map {{ {mapper} }}",
475            value = value,
476            mapper = mapper
477        ))
478    }
479
480    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
481        let mapper = oracle
482            .find(&self.inner)
483            .as_json_transform(oracle, &"it".to_string())?;
484        Some(format!(
485            "{prop}.map {{ {mapper} }}",
486            prop = prop,
487            mapper = mapper
488        ))
489    }
490
491    /// A representation of the given literal for this type.
492    /// N.B. `Literal` is aliased from `serde_json::Value`.
493    fn literal(
494        &self,
495        oracle: &dyn CodeOracle,
496        ctx: &dyn Display,
497        renderer: &dyn LiteralRenderer,
498        literal: &Literal,
499    ) -> String {
500        let variant = match literal {
501            serde_json::Value::Array(v) => v,
502            _ => unreachable!(),
503        };
504
505        let v_type = oracle.find(&self.inner);
506        let src: Vec<String> = variant
507            .iter()
508            .map(|v| v_type.literal(oracle, ctx, renderer, v))
509            .collect();
510
511        format!("listOf({})", src.join(", "))
512    }
513}
514
515#[cfg(test)]
516mod unit_tests {
517
518    use serde_json::json;
519
520    use crate::backends::kotlin::gen_structs::{
521        enum_::EnumCodeType, object::ObjectCodeType, primitives::StringCodeType,
522    };
523    use crate::backends::TypeIdentifier;
524
525    use super::*;
526
527    struct TestCodeOracle;
528    impl CodeOracle for TestCodeOracle {
529        fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
530            match type_ {
531                TypeIdentifier::String => Box::new(StringCodeType) as Box<dyn CodeType>,
532                TypeIdentifier::Enum(s) => {
533                    Box::new(EnumCodeType::new(s.clone())) as Box<dyn CodeType>
534                }
535                TypeIdentifier::Object(s) => {
536                    Box::new(ObjectCodeType::new(s.clone())) as Box<dyn CodeType>
537                }
538                TypeIdentifier::List(i) => Box::new(ListCodeType::new(i)),
539                TypeIdentifier::EnumMap(k, v) => Box::new(MapCodeType::new(k, v)),
540                _ => unreachable!(),
541            }
542        }
543    }
544
545    struct TestRenderer;
546    impl LiteralRenderer for TestRenderer {
547        fn literal(
548            &self,
549            _oracle: &dyn CodeOracle,
550            _typ: &TypeIdentifier,
551            _value: &Literal,
552            _ctx: &dyn Display,
553        ) -> String {
554            unreachable!()
555        }
556    }
557
558    fn oracle() -> Box<dyn CodeOracle> {
559        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
560    }
561
562    fn type_(nm: &str) -> TypeIdentifier {
563        match nm {
564            "String" => TypeIdentifier::String,
565            "AnObject" => TypeIdentifier::Object("AnObject".to_string()),
566            nm => TypeIdentifier::Enum(nm.to_string()),
567        }
568    }
569
570    fn list_type(item: &str) -> Box<dyn CodeType> {
571        Box::new(ListCodeType::new(&type_(item)))
572    }
573
574    fn map_type(k: &str, v: &str) -> Box<dyn CodeType> {
575        Box::new(MapCodeType::new(&type_(k), &type_(v)))
576    }
577
578    fn getter_with_fallback(
579        ct: &dyn CodeType,
580        vars: &dyn Display,
581        prop: &dyn Display,
582        def: &dyn Display,
583    ) -> String {
584        let oracle = &*oracle();
585        ct.property_getter(oracle, vars, prop, def)
586    }
587
588    #[test]
589    fn test_list_type_label() {
590        let oracle = &*oracle();
591        let ct = list_type("String");
592        assert_eq!("List<String>".to_string(), ct.type_label(oracle));
593
594        let ct = list_type("AnEnum");
595        assert_eq!("List<AnEnum>".to_string(), ct.type_label(oracle));
596    }
597
598    #[test]
599    fn test_list_literal() {
600        let oracle = &*oracle();
601        let finder = &TestRenderer;
602
603        let ct = list_type("String");
604        let ctx = "_context".to_string();
605        assert_eq!(
606            r#"listOf("x", "y", "z")"#.to_string(),
607            ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
608        );
609
610        let ct = list_type("AnEnum");
611        assert_eq!(
612            r#"listOf(AnEnum.X, AnEnum.Y, AnEnum.Z)"#.to_string(),
613            ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
614        );
615    }
616
617    #[test]
618    fn test_list_get_value() {
619        let oracle = &*oracle();
620
621        let ct = list_type("AnEnum");
622        assert_eq!(
623            r#"v.getStringList("the-property")"#.to_string(),
624            ct.value_getter(oracle, &"v", &"the-property")
625        );
626
627        let ct = list_type("AnObject");
628        assert_eq!(
629            r#"v.getVariablesList("the-property")"#.to_string(),
630            ct.value_getter(oracle, &"v", &"the-property")
631        );
632
633        let ct = list_type("String");
634        assert_eq!(
635            r#"v.getStringList("the-property")"#.to_string(),
636            ct.value_getter(oracle, &"v", &"the-property")
637        );
638    }
639
640    #[test]
641    fn test_list_getter_with_fallback() {
642        let ct = list_type("String");
643        assert_eq!(
644            r#"vars.getStringList("the-property") ?: default"#.to_string(),
645            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
646        );
647
648        let ct = list_type("AnEnum");
649        assert_eq!(
650            r#"vars.getStringList("the-property")?.mapNotNull(AnEnum::enumValue) ?: default"#
651                .to_string(),
652            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
653        );
654
655        let ct = list_type("AnObject");
656        assert_eq!(
657            r#"vars.getVariablesList("the-property")?.mapNotNull(AnObject::create) ?: default"#
658                .to_string(),
659            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
660        );
661    }
662
663    #[test]
664    fn test_map_type_label() {
665        let oracle = &*oracle();
666        let ct = map_type("String", "String");
667        assert_eq!("Map<String, String>".to_string(), ct.type_label(oracle));
668
669        let ct = map_type("String", "AnEnum");
670        assert_eq!("Map<String, AnEnum>".to_string(), ct.type_label(oracle));
671    }
672
673    #[test]
674    fn test_map_literal() {
675        let oracle = &*oracle();
676        let finder = &TestRenderer;
677        let ctx = "context".to_string();
678        let ct = map_type("String", "AnEnum");
679        assert_eq!(
680            r#"mapOf("a" to AnEnum.A, "b" to AnEnum.B)"#.to_string(),
681            ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
682        );
683
684        let ct = map_type("AnEnum", "String");
685        assert_eq!(
686            r#"mapOf(AnEnum.A to "a", AnEnum.B to "b")"#.to_string(),
687            ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
688        );
689    }
690
691    #[test]
692    fn test_map_get_value() {
693        let oracle = &*oracle();
694
695        let ct = map_type("String", "AnEnum");
696        assert_eq!(
697            r#"v.getStringMap("the-property")"#.to_string(),
698            ct.value_getter(oracle, &"v", &"the-property")
699        );
700
701        let ct = map_type("AnEnum", "String");
702        assert_eq!(
703            r#"v.getStringMap("the-property")"#.to_string(),
704            ct.value_getter(oracle, &"v", &"the-property")
705        );
706
707        let ct = map_type("AnEnum", "Another");
708        assert_eq!(
709            r#"v.getStringMap("the-property")"#.to_string(),
710            ct.value_getter(oracle, &"v", &"the-property")
711        );
712    }
713
714    #[test]
715    fn test_map_getter_with_fallback() {
716        let oracle = &*oracle();
717
718        let ct = map_type("String", "AnEnum");
719        assert_eq!(
720            r#"v.getStringMap("the-property")?.mapValuesNotNull(AnEnum::enumValue)?.mergeWith(def) ?: def"#.to_string(),
721            ct.property_getter(oracle, &"v", &"the-property", &"def")
722        );
723
724        let ct = map_type("AnEnum", "String");
725        assert_eq!(
726            r#"v.getStringMap("the-property")?.mapKeysNotNull(AnEnum::enumValue)?.mergeWith(def) ?: def"#
727                .to_string(),
728            ct.property_getter(oracle, &"v", &"the-property", &"def")
729        );
730
731        let ct = map_type("AnEnum", "Another");
732        assert_eq!(
733            r#"v.getStringMap("the-property")?.mapEntriesNotNull(AnEnum::enumValue, Another::enumValue)?.mergeWith(def) ?: def"#
734                .to_string(),
735            ct.property_getter(oracle, &"v", &"the-property", &"def")
736        );
737
738        let ct = map_type("AnEnum", "AnObject");
739        assert_eq!(
740            r#"v.getVariablesMap("the-property")?.mapEntriesNotNull(AnEnum::enumValue, AnObject::create)?.mergeWith(def, AnObject::mergeWith) ?: def"#.to_string(),
741            ct.property_getter(oracle, &"v", &"the-property", &"def"));
742    }
743}