code: def rewrite_filters_in_optional_blocks(ir_blocks):
      
      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
docs: 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