docs: def _collect_pops(stack, depth, pops, skip):
    """Recursively collects stack entries off the top of the stack according to the stack entry's depth."""
    if depth >= 0:
      return pops
  
    set_current_depth_after_recursion = False
    set_skip_for_current_entry_children = False
    set_skip_after_current_entry = False
    extract_next_tokens = False
    expect_extracted_tokens = []
  
    se = stack.pop()
    pops_len = len(pops)
    if (pops_len > 1
        and se.opname == 'BUILD_TUPLE'
        and pops[-1].opname == 'LOAD_CONST'
        and pops[-1].oparg_repr[0] in ['lambda', '{', '(', '[']
        and pops[-2].opname in ['MAKE_CLOSURE', 'MAKE_FUNCTION']):
      # Skip BUILD_TUPLE and its children if they are storing arguments for a closure, since those don't show up in the code.
      skip = -se.stack_depth + 1
  
    if (pops_len > 2
        and sys.version_info[0] >= 3
        and se.opname == 'BUILD_TUPLE'
        and pops[-1].opname == 'LOAD_CONST'
        and pops[-1].oparg_repr[0] in ['lambda', '{', '(', '[']
        and pops[-2].opname == 'LOAD_CONST'
        and pops[-3].opname in ['MAKE_CLOSURE', 'MAKE_FUNCTION']):
      # Skip BUILD_TUPLE and its children if they are storing arguments for a closure, since those don't show up in the code.
      skip = -se.stack_depth + 1
  
    if (pops_len > 0
        and se.opname == 'GET_ITER'
        and pops[-1].opname == 'CALL_FUNCTION'):
      # CALL_FUNCTION followed by GET_ITER means we are calling one of the comprehensions and we are about to load its arguments.
      # The CALL_FUNCTION at the top of the stack should be invisible, since it expects a ')' which won't appear in the code.
      pops[-1].oparg_repr = ['']
      # We need to extract the arguments that we're about to load so that we can store their tokens inside of the upcoming comprehension.
      extract_next_tokens = -1
  
    if (len(stack)
        and se.opname == 'BUILD_TUPLE_UNPACK_WITH_CALL'):
      extract_next_tokens = se.stack_depth
      expect_extracted_tokens = [
          # Expect LOAD_FAST as the first element (required=True), and prepend its oparg_repr with '*'.
          (0, 'LOAD_FAST', True, 'prepend', ['*']),
          # Expect BUILD_TUPLE as the last stack token extracted (required=False) and replace its oparg_repr with ''.
          (abs(extract_next_tokens) - 1, 'BUILD_TUPLE', False, 'replace', [''])
      ]
      set_current_depth_after_recursion = se.stack_depth
      se.stack_depth = 0
  
    if (pops_len > 0
        and sys.version_info[0] >= 3
        and se.opname == 'LOAD_CONST'
        and pops[-1].opname == 'MAKE_FUNCTION'):
      # In python 3, MAKE_FUNCTION followed by LOAD_CONST is loading the name of the function, which won't appear in the code.
      se.oparg_repr = ['']
      # Additionally, this entry shouldn't impact future stack computations, as MAKE_FUNCTION will be removed.
      set_current_depth_after_recursion = 0
  
    if pops_len and pops[-1].opname == 'LIST_APPEND':
      # Skip all but the first stack entry of list comprehensions. Sets the skip value to be all remaining stack entries.
      # The BUILD_LIST check below will disable skip at the right time.
      set_skip_after_current_entry = len(stack)
  
    if skip > 0 and se.opname == 'BUILD_LIST' and se.stack_depth == 0:
      # If we're in skip mode and we just hit what might be the beginning of a list comprehension, check for a LIST_APPEND in the current pops.
      for popped_se in pops[::-1]:
        if popped_se.opname == 'LIST_APPEND':
          skip = 0
          break
  
    children_skip = skip
    if (se.opname.startswith('UNARY_')
        or (se.opname.startswith('BINARY_') and se.opname != 'BINARY_SUBSCR')
        or se.opname == 'SLICE+2'
        or se.opname == 'SLICE+3'
        or se.opname == 'COMPARE_OP'):
      # Unary and binary ops come after their operand(s) on the stack, but before (or between) their operand(s) in code, so we need to reverse that.
  
      if set_skip_for_current_entry_children or skip > 0:
        children_skip = 1
      pops = _collect_pops(stack, -1, pops, children_skip)
  
      if skip <= 0:
        pops.append(se)
        qj._DEBUG_QJ and qj.LOG_FN('added se: %r' % se)
      else:
        qj._DEBUG_QJ and qj.LOG_FN('(skipping se: %r %r)' % (se.opname, se.oparg_repr))
  
      popped_depth = se.stack_depth + 1
    else:
      # Non prefix/infix ops -- their representations come after their children in code, or they don't have children.
      if skip <= 0:
        pops.append(se)
        qj._DEBUG_QJ and qj.LOG_FN('added se: %r' % se)
      else:
        qj._DEBUG_QJ and qj.LOG_FN('(skipping se: %r %r)' % (se.opname, se.oparg_repr))
  
      if ((se.stack_depth < 0
           and se.opname != 'BUILD_SLICE'
           and se.opname.startswith('BUILD_'))
          or se.stack_depth >= 0):
        next_depth = se.stack_depth
      else:
        next_depth = se.stack_depth - 1
  
      if set_skip_for_current_entry_children or skip > 0:
        children_skip = abs(next_depth)
      if se.opname == 'BUILD_SLICE':
        # BUILD_SLICE's arguments need to be collected, as missing args are replaced with Nones which don't appear in the code.
        slice_pops = _collect_pops(stack, next_depth, [], children_skip)
        added_colon = 0
        for slice_se in slice_pops:
          if slice_se.opname == 'LOAD_CONST' and slice_se.oparg_repr[0] == 'None':
            if added_colon >= 1:
              slice_se.oparg_repr = ['']
            else:
              slice_se.oparg_repr = [':']
              added_colon += 1
          pops.append(slice_se)
      else:
        pops = _collect_pops(stack, next_depth, pops, children_skip)
  
      # BUILD_LIST 0 marks the start of a list comprehension, but we need it to consume a slot on the stack.
      if se.stack_depth == 0 and se.opname != 'BUILD_LIST':
        popped_depth = 0
      else:
        popped_depth = 1
  
    tokens = []
    if extract_next_tokens < 0:
      tokens = _collect_pops(stack, extract_next_tokens, [], skip)
      for index, expected_token, required, fixup_type, fixup_value in expect_extracted_tokens:
        if qj._DEBUG_QJ:
          assert (index < 0 and index + len(tokens) > 0) or 0 <= index < len(tokens)
          if required:
            assert tokens[index].opname == expected_token
        if (index < 0 and index + len(tokens) > 0) or 0 <= index < len(tokens) and tokens[index].opname == expected_token:
          if fixup_type == 'prepend':
            tokens[index].oparg_repr = fixup_value + tokens[index].oparg_repr
          elif fixup_type == 'replace':
            tokens[index].oparg_repr = fixup_value
      tokens.reverse()
      popped_depth -= extract_next_tokens
  
    if children_skip > 0:
      skip -= popped_depth
  
    if set_skip_after_current_entry > 0:
      skip = set_skip_after_current_entry + max(0, skip)
  
    pops = _collect_pops(stack, depth + popped_depth, pops, skip)
  
    if len(tokens):  # pylint: disable=g-explicit-length-test
      target_se = pops[-1]
      target_se.children.append(tokens)
      target_se.oparg_repr = target_se.oparg_repr[:1] + [t for token in tokens for t in token.oparg_repr] + target_se.oparg_repr[1:]
  
    if set_current_depth_after_recursion is not False:
      se.stack_depth = set_current_depth_after_recursion
  
    return pops