From f4cf70db96091cd25ff97e029fd5b116918b250c Mon Sep 17 00:00:00 2001 From: Julian Locke Date: Tue, 6 Feb 2024 15:28:40 -0500 Subject: [PATCH] Handle mapping struct fields with subtypes to Objective-C (#319) --- stone/backends/swift_rsrc/ObjcTypes.jinja | 32 +++++++++- stone/backends/swift_types.py | 73 ++++++++++++++++++++++- 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/stone/backends/swift_rsrc/ObjcTypes.jinja b/stone/backends/swift_rsrc/ObjcTypes.jinja index 10eb2c0d..a0444807 100644 --- a/stone/backends/swift_rsrc/ObjcTypes.jinja +++ b/stone/backends/swift_rsrc/ObjcTypes.jinja @@ -22,7 +22,37 @@ public class DBX{{ namespace_class_name }}{{ data_type_class_name }}: {{ 'NSObje {% for field in data_type.fields %} {{ struct_field_doc(field, ' ') }} @objc - public var {{ fmt_var(field.name) }}: {{ fmt_objc_type(field.data_type) }} { {{ objc_return_field_value(data_type, field) }} } + {% if objc_return_field_value_specified_in_jinja(field) %} + public var {{ fmt_var(field.name) }}: {{ fmt_objc_type(field.data_type) }} { + {% if (field_is_user_defined(field)) or (field_is_user_defined_optional(field)) %} + switch {{ swift_var_name }}.{{ fmt_var(field.name) }} { + {% for tuple in objc_return_field_value_type_tuples(field) %} + case let {{ tuple[0] }} as {{ tuple[1] }}: + return {{ tuple[2] }}(swift: {{ tuple[0] }}) + {% endfor %} + default: + {% if field_is_user_defined_optional(field) %} + return {{ swift_var_name }}.{{ fmt_var(field.name) }}.flatMap { {{ fmt_objc_type(field.data_type, False) }}(swift: $0) } + {% else %} + return {{ fmt_objc_type(field.data_type) }}(swift: {{ swift_var_name }}.{{ fmt_var(field.name) }}) + {% endif %} + } + {% elif (field_is_user_defined_map(field)) or (field_is_user_defined_list(field)) %} + {{ swift_var_name }}.{{ fmt_var(field.name) }}.{{ 'mapValues' if field_is_user_defined_map(field) else 'map' }} { + switch $0 { + {% for tuple in objc_return_field_value_type_tuples(field) %} {#extract this snippet?#} + case let {{ tuple[0] }} as {{ tuple[1] }}: + return {{ tuple[2] }}(swift: {{ tuple[0] }}) + {% endfor %} + default: + return {{ fmt_objc_type(field.data_type.data_type) }}(swift: $0) + } + } + {% endif %} + } + {% else %} + public var {{ fmt_var(field.name) }}: {{ fmt_objc_type(field.data_type) }} { {{ objc_return_field_value_oneliner(data_type, field) }} } + {% endif %} {% endfor %} {% if data_type.fields %} diff --git a/stone/backends/swift_types.py b/stone/backends/swift_types.py index 4c7108d4..90f86c97 100644 --- a/stone/backends/swift_types.py +++ b/stone/backends/swift_types.py @@ -176,7 +176,16 @@ def generate(self, api): template_globals['data_objc_type_doc'] = self._data_objc_type_doc template_globals['objc_init_args'] = self._objc_init_args template_globals['fmt_objc_type'] = fmt_objc_type - template_globals['objc_return_field_value'] = self._objc_return_field_value + oneliner_func_key = 'objc_return_field_value_oneliner' + template_globals[oneliner_func_key] = self._objc_return_field_value_oneliner + template_globals['field_is_user_defined'] = self._field_is_user_defined + template_globals['field_is_user_defined_optional'] = self._field_is_user_defined_optional + template_globals['field_is_user_defined_list'] = self._field_is_user_defined_list + template_globals['field_is_user_defined_map'] = self._field_is_user_defined_map + in_jinja_key = 'objc_return_field_value_specified_in_jinja' + template_globals[in_jinja_key] = self._objc_return_field_value_specified_in_jinja + field_value_tuples_key = 'objc_return_field_value_type_tuples' + template_globals[field_value_tuples_key] = self._objc_return_field_value_type_tuples template_globals['objc_init_args_to_swift'] = self._objc_init_args_to_swift template_globals['objc_union_arg'] = self._objc_union_arg template_globals['objc_swift_var_name'] = self._objc_swift_var_name @@ -334,7 +343,67 @@ def _route_schema_attrs(self, route_schema, route): result = ',\n '.join(attrs) return result - def _objc_return_field_value(self, parent_type, field): + # List[typing.Tuple[let_name: str, swift_type: str, objc_type: str]] + def _objc_return_field_value_type_tuples(self, field): + data_type = field.data_type + ret = [] + + # if list type get the data type of the item + if is_list_type(data_type): + data_type = data_type.data_type + + # if map type get the data type of the value + if is_map_type(data_type): + data_type = data_type.value_data_type + + # if data_type is a struct type and has subtypes, process them into labels and types + if is_struct_type(data_type) and data_type.has_enumerated_subtypes(): + all_subtypes = data_type.get_all_subtypes_with_tags() + + for subtype in all_subtypes: + # subtype[0] is the tag name and subtype[1] is the subtype struct itself + struct = subtype[1] + case_let_name = fmt_var(struct.name) + swift_type = fmt_type(struct) + objc_type = fmt_objc_type(struct) + ret.append((case_let_name, swift_type, objc_type)) + + return ret + + def _field_is_user_defined(self, field): + data_type, nullable = unwrap_nullable(field.data_type) + return is_user_defined_type(data_type) and not nullable + + def _field_is_user_defined_optional(self, field): + data_type, nullable = unwrap_nullable(field.data_type) + return is_user_defined_type(data_type) and nullable + + def _field_is_user_defined_map(self, field): + data_type, _ = unwrap_nullable(field.data_type) + return is_map_type(data_type) and is_user_defined_type(data_type.value_data_type) + + def _field_is_user_defined_list(self, field): + data_type, _ = unwrap_nullable(field.data_type) + if is_list_type(data_type): + list_data_type, _ = unwrap_nullable(data_type.data_type) + return is_user_defined_type(list_data_type) + else: + return False + + def _objc_return_field_value_specified_in_jinja(self, field) -> bool: + eligible_kind = self._field_is_user_defined(field) or \ + self._field_is_user_defined_optional(field) or \ + self._field_is_user_defined_map(field) or \ + self._field_is_user_defined_list(field) + + if not eligible_kind: + return False + + requires_iterating_over_subtypes = len(self._objc_return_field_value_type_tuples(field)) > 0 + + return requires_iterating_over_subtypes + + def _objc_return_field_value_oneliner(self, parent_type, field): data_type, nullable = unwrap_nullable(field.data_type) swift_var_name = self._objc_swift_var_name(parent_type)