def _generate_union(self, union_type): """ Emits a JSDoc @typedef for a union type. """ union_name = fmt_type_name(union_type) self._emit_jsdoc_header(union_type.doc) self.emit(' * @typedef {Object} %s' % union_name) variant_types = [] for variant in union_type.all_fields: variant_types.append("'%s'" % variant.name) variant_data_type, _, _ = unwrap(variant.data_type) # Don't emit fields for void types. if not is_void_type(variant_data_type): variant_doc = ' - Available if .tag is %s.' % variant.name if variant.doc: variant_doc += ' ' + variant.doc self.emit_wrapped_text( '@property {%s} [%s]%s' % ( fmt_type(variant_data_type), variant.name, variant_doc, ), prefix=' * ', ) jsdoc_tag_union = fmt_jsdoc_union(variant_types) self.emit(' * @property {%s} .tag - Tag identifying the union variant.' % jsdoc_tag_union) self.emit(' */')
def _generate_union(self, union_type): """ Emits a JSDoc @typedef for a union type. """ union_name = fmt_type_name(union_type) self._emit_jsdoc_header(union_type.doc) self.emit(' * @typedef {Object} %s' % union_name) variant_types = [] for variant in union_type.all_fields: variant_types.append("'%s'" % variant.name) variant_data_type, _, _ = unwrap(variant.data_type) # Don't emit fields for void types. if not is_void_type(variant_data_type): variant_doc = ' - Available if .tag is %s.' % variant.name if variant.doc: variant_doc += ' ' + variant.doc self.emit_wrapped_text( '@property {%s} [%s]%s' % ( fmt_type(variant_data_type), variant.name, variant_doc, ), prefix=' * ', ) jsdoc_tag_union = fmt_jsdoc_union(variant_types) self.emit( ' * @property {%s} .tag - Tag identifying the union variant.' % jsdoc_tag_union) self.emit(' */')
def _generate_custom_annotation_processors(self, ns, data_type, extra_annotations=()): """ Generates code that will run a custom function 'processor' on every field with a custom annotation, no matter how deep (recursively) it might be located in data_type (incl. in elements of lists or maps). If extra_annotations is passed, it's assumed to be a list of custom annotation applied directly onto data_type (e.g. because it's a field in a struct). Yields pairs of (annotation_type, code) where code is code that evaluates to a function that should be executed with an instance of data_type as the only parameter, and whose return value should replace that instance. """ # annotations applied to members of this type dt, _, _ = unwrap(data_type) if is_struct_type(dt) or is_union_type(dt): annotation_types_seen = set() for annotation in get_custom_annotations_recursive(dt): if annotation.annotation_type not in annotation_types_seen: yield (annotation.annotation_type, generate_func_call( 'bb.make_struct_annotation_processor', args=[ class_name_for_annotation_type( annotation.annotation_type, ns), 'processor' ])) annotation_types_seen.add(annotation.annotation_type) elif is_list_type(dt): for annotation_type, recursive_processor in self._generate_custom_annotation_processors( ns, dt.data_type): # every member needs to be replaced---use handwritten processor yield (annotation_type, generate_func_call('bb.make_list_annotation_processor', args=[recursive_processor])) elif is_map_type(dt): for annotation_type, recursive_processor in self._generate_custom_annotation_processors( ns, dt.value_data_type): # every value needs to be replaced---use handwritten processor yield (annotation_type, generate_func_call( 'bb.make_map_value_annotation_processor', args=[recursive_processor])) # annotations applied directly to this type (through aliases or # passed in from the caller) for annotation in itertools.chain( get_custom_annotations_for_alias(data_type), extra_annotations): yield (annotation.annotation_type, generate_func_call( 'bb.partially_apply', args=[ 'processor', self._generate_custom_annotation_instance( ns, annotation) ]))
def _generate_struct(self, struct_type, extra_parameters=None, nameOverride=None): """ Emits a JSDoc @typedef for a struct. """ extra_parameters = extra_parameters if extra_parameters is not None else [] self._emit_jsdoc_header(struct_type.doc) self.emit( ' * @typedef {Object} %s' % ( nameOverride if nameOverride else fmt_type_name(struct_type) ) ) # Some structs can explicitly list their subtypes. These structs # have a .tag field that indicate which subtype they are. if struct_type.is_member_of_enumerated_subtypes_tree(): if struct_type.has_enumerated_subtypes(): # This struct is the parent to multiple subtypes. # Determine all of the possible values of the .tag # property. tag_values = [] for tags, _ in struct_type.get_all_subtypes_with_tags(): for tag in tags: tag_values.append('"%s"' % tag) jsdoc_tag_union = fmt_jsdoc_union(tag_values) txt = '@property {%s} .tag - Tag identifying the subtype variant.' % \ jsdoc_tag_union self.emit_wrapped_text(txt) else: # This struct is a particular subtype. Find the applicable # .tag value from the parent type, which may be an # arbitrary number of steps up the inheritance hierarchy. parent = struct_type.parent_type while not parent.has_enumerated_subtypes(): parent = parent.parent_type # parent now contains the closest parent type in the # inheritance hierarchy that has enumerated subtypes. # Determine which subtype this is. for subtype in parent.get_enumerated_subtypes(): if subtype.data_type == struct_type: txt = '@property {\'%s\'} [.tag] - Tag identifying ' \ 'this subtype variant. This field is only ' \ 'present when needed to discriminate ' \ 'between multiple possible subtypes.' % \ subtype.name self.emit_wrapped_text(txt) break for param_name, param_type, param_docstring in extra_parameters: param_docstring = ' - %s' % param_docstring if param_docstring else '' self.emit_wrapped_text( '@property {%s} %s%s' % ( param_type, param_name, param_docstring, ), prefix=' * ', ) # NOTE: JSDoc @typedef does not support inheritance. Using @class would be inappropriate, # since these are not nominal types backed by a constructor. Thus, we emit all_fields, # which includes fields on parent types. for field in struct_type.all_fields: field_doc = ' - ' + field.doc if field.doc else '' field_type, nullable, _ = unwrap(field.data_type) field_js_type = fmt_type(field_type) # Translate nullable types into optional properties. field_name = '[' + field.name + ']' if nullable else field.name self.emit_wrapped_text( '@property {%s} %s%s' % ( field_js_type, field_name, self.process_doc(field_doc, self._docf), ), prefix=' * ', ) self.emit(' */')
def _generate_struct(self, struct_type, extra_parameters=None, nameOverride=None): """ Emits a JSDoc @typedef for a struct. """ extra_parameters = extra_parameters if extra_parameters is not None else [] self._emit_jsdoc_header(struct_type.doc) self.emit( ' * @typedef {Object} %s' % (nameOverride if nameOverride else fmt_type_name(struct_type))) # Some structs can explicitly list their subtypes. These structs # have a .tag field that indicate which subtype they are. if struct_type.is_member_of_enumerated_subtypes_tree(): if struct_type.has_enumerated_subtypes(): # This struct is the parent to multiple subtypes. # Determine all of the possible values of the .tag # property. tag_values = [] for tags, _ in struct_type.get_all_subtypes_with_tags(): for tag in tags: tag_values.append('"%s"' % tag) jsdoc_tag_union = fmt_jsdoc_union(tag_values) txt = '@property {%s} .tag - Tag identifying the subtype variant.' % \ jsdoc_tag_union self.emit_wrapped_text(txt) else: # This struct is a particular subtype. Find the applicable # .tag value from the parent type, which may be an # arbitrary number of steps up the inheritance hierarchy. parent = struct_type.parent_type while not parent.has_enumerated_subtypes(): parent = parent.parent_type # parent now contains the closest parent type in the # inheritance hierarchy that has enumerated subtypes. # Determine which subtype this is. for subtype in parent.get_enumerated_subtypes(): if subtype.data_type == struct_type: txt = '@property {\'%s\'} [.tag] - Tag identifying ' \ 'this subtype variant. This field is only ' \ 'present when needed to discriminate ' \ 'between multiple possible subtypes.' % \ subtype.name self.emit_wrapped_text(txt) break for param_name, param_type, param_docstring in extra_parameters: param_docstring = ' - %s' % param_docstring if param_docstring else '' self.emit_wrapped_text( '@property {%s} [%s]%s' % ( param_type, param_name, param_docstring, ), prefix=' * ', ) # NOTE: JSDoc @typedef does not support inheritance. Using @class would be inappropriate, # since these are not nominal types backed by a constructor. Thus, we emit all_fields, # which includes fields on parent types. for field in struct_type.all_fields: field_doc = ' - ' + field.doc if field.doc else '' field_type, nullable, _ = unwrap(field.data_type) field_js_type = fmt_type(field_type) # Translate nullable types into optional properties. field_name = '[' + field.name + ']' if nullable else field.name self.emit_wrapped_text( '@property {%s} %s%s' % ( field_js_type, field_name, self.process_doc(field_doc, self._docf), ), prefix=' * ', ) self.emit(' */')
def _impl_serde_for_union(self, union): type_name = self.enum_name(union) with self._impl_deserialize(type_name): self.emit(u'// union deserializer') self.emit(u'use serde::de::{self, MapAccess, Visitor};') self.emit(u'struct EnumVisitor;') with self.block(u'impl<\'de> Visitor<\'de> for EnumVisitor'): self.emit(u'type Value = {};'.format(type_name)) with self.emit_rust_function_def( u'expecting', [u'&self', u'f: &mut ::std::fmt::Formatter<\'_>'], u'::std::fmt::Result'): self.emit(u'f.write_str("a {} structure")'.format( union.name)) with self.emit_rust_function_def( u'visit_map<V: MapAccess<\'de>>', [u'self', u'mut map: V'], u'Result<Self::Value, V::Error>'): with self.block(u'let tag: &str = match map.next_key()?', after=';'): self.emit(u'Some(".tag") => map.next_value()?,') self.emit( u'_ => return Err(de::Error::missing_field(".tag"))' ) with self.block(u'match tag'): for field in union.all_fields: if field.catch_all: # Handle the 'Other' variant at the end. continue variant_name = self.enum_variant_name(field) ultimate_type = ir.unwrap(field.data_type)[0] if isinstance(field.data_type, ir.Void): with self.block(u'"{}" =>'.format(field.name)): self.emit( u'crate::eat_json_fields(&mut map)?;') self.emit(u'Ok({}::{})'.format( type_name, variant_name)) elif isinstance(ultimate_type, ir.Struct) \ and not ultimate_type.has_enumerated_subtypes(): if isinstance( ir.unwrap_aliases(field.data_type)[0], ir.Nullable): # A nullable here means we might have more fields that can be # deserialized into the inner type, or we might have nothing, # meaning None. if not ultimate_type.all_required_fields: raise RuntimeError( '{}.{}: an optional struct with no' ' required fields is ambiguous'. format(union.name, field.name)) self.emit( u'"{}" => Ok({}::{}({}::internal_deserialize_opt(' u'map, true)?)),'.format( field.name, type_name, variant_name, self._rust_type(ultimate_type))) else: self.emit( u'"{}" => Ok({}::{}({}::internal_deserialize(map)?)),' .format( field.name, type_name, variant_name, self._rust_type(field.data_type))) else: with self.block(u'"{}" =>'.format(field.name)): with self.block(u'match map.next_key()?'): self.emit( u'Some("{}") => Ok({}::{}(map.next_value()?)),' .format(field.name, type_name, variant_name)) if isinstance( ir.unwrap_aliases( field.data_type)[0], ir.Nullable): # if it's null, the field can be omitted entirely self.emit( u'None => Ok({}::{}(None)),'. format(type_name, variant_name)) else: self.emit( u'None => Err(' u'de::Error::missing_field("{}")),' .format(field.name)) self.emit( u'_ => Err(de::Error::unknown_field(' u'tag, VARIANTS))') if not union.closed: with self.block(u'_ =>'): self.emit( u'crate::eat_json_fields(&mut map)?;') self.emit(u'Ok({}::Other)'.format(type_name)) else: self.emit( u'_ => Err(de::Error::unknown_variant(tag, VARIANTS))' ) self.generate_multiline_list( list(u'"{}"'.format(field.name) for field in union.all_fields), before='const VARIANTS: &[&str] = &', after=';', delim=(u'[', u']'), ) self.emit( u'deserializer.deserialize_struct("{}", VARIANTS, EnumVisitor)' .format(union.name)) self.emit() with self._impl_serialize(type_name): self.emit(u'// union serializer') if len(union.all_fields) == 1 and union.all_fields[0].catch_all: # special case: an open union with no variants defined. self.emit(u'#![allow(unused_variables)]') self.emit( u'Err(::serde::ser::Error::custom("cannot serialize an open union with ' u'no defined variants"))') else: self.emit(u'use serde::ser::SerializeStruct;') with self.block(u'match *self'): for field in union.all_fields: if field.catch_all: # Handle the 'Other' variant at the end. continue variant_name = self.enum_variant_name(field) if isinstance(field.data_type, ir.Void): with self.block(u'{}::{} =>'.format( type_name, variant_name)): self.emit(u'// unit') self.emit( u'let mut s = serializer.serialize_struct("{}", 1)?;' .format(union.name)) self.emit(u's.serialize_field(".tag", "{}")?;'. format(field.name)) self.emit(u's.end()') else: ultimate_type = ir.unwrap(field.data_type)[0] needs_x = not (isinstance(field.data_type, ir.Struct) and not field.data_type.all_fields) ref_x = 'ref x' if needs_x else '_' with self.block(u'{}::{}({}) =>'.format( type_name, variant_name, ref_x)): if self.is_enum_type(ultimate_type): # Inner type is a union or polymorphic struct; need to always # emit another nesting level. self.emit( u'// union or polymporphic struct') self.emit( u'let mut s = serializer.serialize_struct("{}", 2)?;' .format(union.name)) self.emit( u's.serialize_field(".tag", "{}")?;'. format(field.name)) self.emit( u's.serialize_field("{}", x)?;'.format( field.name)) self.emit(u's.end()') elif isinstance( ir.unwrap_aliases(field.data_type)[0], ir.Nullable): self.emit( u'// nullable (struct or primitive)') # If it's nullable and the value is None, just emit the tag and # nothing else, otherwise emit the fields directly at the same # level. num_fields = 1 if ir.is_primitive_type(ultimate_type) \ else len(ultimate_type.all_fields) + 1 self.emit( u'let n = if x.is_some() {{ {} }} else {{ 1 }};' .format(num_fields + 1)) self.emit( u'let mut s = serializer.serialize_struct("{}", n)?;' .format(union.name)) self.emit( u's.serialize_field(".tag", "{}")?;'. format(field.name)) with self.block(u'if let Some(ref x) = x'): if ir.is_primitive_type(ultimate_type): self.emit( u's.serialize_field("{}", &x)?;' .format(field.name)) else: self.emit( u'x.internal_serialize::<S>(&mut s)?;' ) self.emit(u's.end()') elif isinstance(ultimate_type, ir.Struct): self.emit(u'// struct') self.emit( u'let mut s = serializer.serialize_struct("{}", {})?;' .format( union.name, len(ultimate_type.all_fields) + 1)) self.emit( u's.serialize_field(".tag", "{}")?;'. format(field.name)) if ultimate_type.all_fields: self.emit( u'x.internal_serialize::<S>(&mut s)?;' ) self.emit(u's.end()') else: self.emit(u'// primitive') self.emit( u'let mut s = serializer.serialize_struct("{}", 2)?;' .format(union.name)) self.emit( u's.serialize_field(".tag", "{}")?;'. format(field.name)) self.emit( u's.serialize_field("{}", x)?;'.format( field.name)) self.emit(u's.end()') if not union.closed: self.emit( u'{}::Other => Err(::serde::ser::Error::custom(' u'"cannot serialize \'Other\' variant"))'.format( type_name)) self.emit()