Return a list of basic blocks corresponding to the vertex AST node.

    Args:
        schema: GraphQL schema object, obtained from the graphql library
        current_schema_type: GraphQLType, the schema type at the current location
        ast: GraphQL AST node, obtained from the graphql library
        location: Location object representing the current location in the query
        context: dict, various per-compilation data (e.g. declared tags, whether the current block
                 is optional, etc.). May be mutated in-place in this function!
        unique_local_directives: dict, directive name string -> directive object, containing
                                 unique directives present on the current AST node *only*
        fields: tuple of lists (property_fields, vertex_fields), with lists of field objects
                present on the current vertex AST node

    Returns:
        list of basic blocks, the compiled output of the vertex AST node

def _compile_vertex_ast(schema, current_schema_type, ast,
                        location, context, unique_local_directives, fields):
    """Return a list of basic blocks corresponding to the vertex AST node.

    Args:
        schema: GraphQL schema object, obtained from the graphql library
        current_schema_type: GraphQLType, the schema type at the current location
        ast: GraphQL AST node, obtained from the graphql library
        location: Location object representing the current location in the query
        context: dict, various per-compilation data (e.g. declared tags, whether the current block
                 is optional, etc.). May be mutated in-place in this function!
        unique_local_directives: dict, directive name string -> directive object, containing
                                 unique directives present on the current AST node *only*
        fields: tuple of lists (property_fields, vertex_fields), with lists of field objects
                present on the current vertex AST node

    Returns:
        list of basic blocks, the compiled output of the vertex AST node
    """
    basic_blocks = []
    query_metadata_table = context['metadata']
    current_location_info = query_metadata_table.get_location_info(location)

    vertex_fields, property_fields = fields

    validate_vertex_directives(unique_local_directives)

    # step V-2: step into property fields
    for field_ast in property_fields:
        field_name = get_ast_field_name(field_ast)
        property_schema_type = get_field_type_from_schema(current_schema_type, field_name)

        inner_location = location.navigate_to_field(field_name)
        inner_basic_blocks = _compile_ast_node_to_ir(schema, property_schema_type, field_ast,
                                                     inner_location, context)
        basic_blocks.extend(inner_basic_blocks)

    # step V-3: mark the graph position, and process output_source directive
    basic_blocks.append(_mark_location(location))

    output_source = _process_output_source_directive(schema, current_schema_type, ast,
                                                     location, context, unique_local_directives)
    if output_source:
        basic_blocks.append(output_source)

    # step V-4: step into vertex fields
    for field_ast in vertex_fields:
        field_name = get_ast_field_name(field_ast)
        validate_context_for_visiting_vertex_field(location, field_name, context)

        field_schema_type = get_vertex_field_type(current_schema_type, field_name)
        hinted_base = context['type_equivalence_hints_inverse'].get(field_schema_type, None)
        if hinted_base:
            field_schema_type = hinted_base

        inner_unique_directives = get_unique_directives(field_ast)
        validate_vertex_field_directive_interactions(location, field_name, inner_unique_directives)
        validate_vertex_field_directive_in_context(
            location, field_name, inner_unique_directives, context)

        recurse_directive = inner_unique_directives.get('recurse', None)
        optional_directive = inner_unique_directives.get('optional', None)
        fold_directive = inner_unique_directives.get('fold', None)
        in_topmost_optional_block = False

        edge_traversal_is_optional = optional_directive is not None
        edge_traversal_is_folded = fold_directive is not None
        edge_traversal_is_recursive = recurse_directive is not None

        # This is true for any vertex expanded within an @optional scope.
        within_optional_scope = is_in_optional_scope(context)

        if edge_traversal_is_optional:
            # Invariant: There must always be a marked location corresponding to the query position
            # immediately before any optional Traverse.
            #
            # This invariant is verified in the IR sanity checks module (ir_sanity_checks.py),
            # in the function named _sanity_check_mark_location_preceding_optional_traverse().
            #
            # This marked location is the one that the @optional directive's corresponding
            # optional Backtrack will jump back to. If such a marked location isn't present,
            # the backtrack could rewind to an old marked location and might ignore
            # entire stretches of applied filtering.
            #
            # Assumption: The only way there might not be a marked location here is
            # if the current location already traversed into child locations, not including folds.
            non_fold_child_locations = {
                child_location
                for child_location in query_metadata_table.get_child_locations(location)
                if not isinstance(child_location, FoldScopeLocation)
            }
            if non_fold_child_locations:
                location = query_metadata_table.revisit_location(location)
                basic_blocks.append(_mark_location(location))

        if fold_directive:
            inner_location = location.navigate_to_fold(field_name)
        else:
            inner_location = location.navigate_to_subpath(field_name)

        inner_location_info = LocationInfo(
            parent_location=location,
            type=strip_non_null_from_type(field_schema_type),
            coerced_from_type=None,
            optional_scopes_depth=(
                current_location_info.optional_scopes_depth + edge_traversal_is_optional),
            recursive_scopes_depth=(
                current_location_info.recursive_scopes_depth + edge_traversal_is_recursive),
            is_within_fold=(current_location_info.is_within_fold or edge_traversal_is_folded),
        )
        query_metadata_table.register_location(inner_location, inner_location_info)

        if edge_traversal_is_optional:
            # Remember where the topmost optional context started.
            topmost_optional = get_optional_scope_or_none(context)
            if topmost_optional is None:
                set_optional_scope_data(context, inner_location)
                in_topmost_optional_block = True

        edge_direction, edge_name = get_edge_direction_and_name(field_name)

        if fold_directive:
            fold_block = blocks.Fold(inner_location)
            basic_blocks.append(fold_block)
            set_fold_scope_data(context, inner_location)
        elif recurse_directive:
            _validate_recurse_directive_types(current_schema_type, field_schema_type, context)
            recurse_depth = _get_recurse_directive_depth(field_name, inner_unique_directives)
            basic_blocks.append(blocks.Recurse(edge_direction,
                                               edge_name,
                                               recurse_depth,
                                               within_optional_scope=within_optional_scope))
            query_metadata_table.record_recurse_info(location,
                                                     RecurseInfo(edge_direction=edge_direction,
                                                                 edge_name=edge_name,
                                                                 depth=recurse_depth))
        else:
            basic_blocks.append(blocks.Traverse(edge_direction, edge_name,
                                                optional=edge_traversal_is_optional,
                                                within_optional_scope=within_optional_scope))

        inner_basic_blocks = _compile_ast_node_to_ir(schema, field_schema_type, field_ast,
                                                     inner_location, context)
        basic_blocks.extend(inner_basic_blocks)

        if edge_traversal_is_folded:
            has_count_filter = has_fold_count_filter(context)
            _validate_fold_has_outputs_or_count_filter(
                get_context_fold_info(context), has_count_filter, context['outputs'])
            basic_blocks.append(blocks.Unfold())
            unmark_context_fold_scope(context)
            if has_count_filter:
                unmark_fold_count_filter(context)
            if is_in_fold_innermost_scope(context):
                unmark_fold_innermost_scope(context)

        if in_topmost_optional_block:
            unmark_optional_scope(context)

        # If we are currently evaluating a @fold vertex,
        # we didn't Traverse into it, so we don't need to backtrack out either.
        # We also don't backtrack if we've reached an @output_source.
        backtracking_required = (
            (not fold_directive) and
            (not has_encountered_output_source(context))
        )
        if backtracking_required:
            if edge_traversal_is_optional:
                basic_blocks.append(blocks.EndOptional())
                basic_blocks.append(blocks.Backtrack(location, optional=True))

                # Exiting optional block!
                # Revisit the location so that there is a marked location right after the optional,
                # so that future Backtrack blocks return after the optional set of blocks, and
                # don't accidentally return to a prior location instead.
                location = query_metadata_table.revisit_location(location)

                basic_blocks.append(_mark_location(location))
            else:
                basic_blocks.append(blocks.Backtrack(location))

    return basic_blocks