code: def get_custom_annotations_recursive(data_type):
      
      # because Stone structs can contain references to themselves (or otherwise
      # be cyclical), we need ot keep track of the data types we've already seen
      data_types_seen = set()
  
      def recurse(data_type):
          if data_type in data_types_seen:
              return
          data_types_seen.add(data_type)
  
          dt, _, _ = unwrap(data_type)
          if is_struct_type(dt) or is_union_type(dt):
              for field in dt.fields:
                  for annotation in recurse(field.data_type):
                      yield annotation
                  for annotation in field.custom_annotations:
                      yield annotation
          elif is_list_type(dt):
              for annotation in recurse(dt.data_type):
                  yield annotation
          elif is_map_type(dt):
              for annotation in recurse(dt.value_data_type):
                  yield annotation
  
          for annotation in get_custom_annotations_for_alias(data_type):
              yield annotation
  
      return recurse(data_type)
docs: Given a Stone data type, returns all custom annotations applied to any of
      its memebers, as well as submembers, ..., to an arbitrary depth.