<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def rewrite_filters_in_optional_blocks(ir_blocks):
 """In optional contexts, add a check for null that allows non-existent optional data through. Optional traversals in Gremlin represent missing optional data by setting the current vertex to null until the exit from the optional scope. Therefore, filtering and type coercions (which should have been lowered into filters by this point) must check for null before applying their filtering predicates. Since missing optional data isn't filtered, the new filtering predicate should be "(it == null) || existing_predicate". Args: ir_blocks: list of IR blocks to lower into Gremlin-compatible form Returns: new list of IR blocks with this lowering step applied """

    new_ir_blocks = []
    optional_context_depth = 0

    for block in ir_blocks:
        new_block = block
        if isinstance(block, CoerceType):
            raise AssertionError(u'Found a CoerceType block after all such blocks should have been '
                                 u'lowered to Filter blocks: {}'.format(ir_blocks))
        elif isinstance(block, Traverse) and block.optional:
            optional_context_depth += 1
        elif isinstance(block, Backtrack) and block.optional:
            optional_context_depth -= 1
            if optional_context_depth < 0:
                raise AssertionError(u'Reached negative optional context depth for blocks: '
                                     u'{}'.format(ir_blocks))
        elif isinstance(block, Filter) and optional_context_depth > 0:
            null_check = BinaryComposition(u'=', LocalField('@this'), NullLiteral)
            new_block = Filter(BinaryComposition(u'||', null_check, block.predicate))
        else:
            pass

        new_ir_blocks.append(new_block)

    return new_ir_blocks