def get_custom_annotations_recursive(data_type):
    """
    Given a Stone data type, returns all custom annotations applied to any of
    its memebers, as well as submembers, ..., to an arbitrary depth.
    """
    # 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)
Given a Stone data type, returns all custom annotations applied to any of
    its memebers, as well as submembers, ..., to an arbitrary depth.