def _expose_only_preferred_locations(match_query, location_types, coerced_locations,
                                     preferred_locations, eligible_locations):
    """Return a MATCH query where only preferred locations are valid as query start locations."""
    preferred_location_types = dict()
    eligible_location_types = dict()

    new_match_traversals = []
    for current_traversal in match_query.match_traversals:
        new_traversal = []
        for match_step in current_traversal:
            new_step = match_step
            current_step_location = match_step.as_block.location

            if current_step_location in preferred_locations:
                # This location is preferred. We have to make sure that at least one occurrence
                # of this location in the MATCH query has an associated "class:" clause,
                # which would be generated by a type bound at the corresponding MATCH step.
                current_type_bound = _calculate_type_bound_at_step(match_step)
                previous_type_bound = preferred_location_types.get(current_step_location, None)

                if previous_type_bound is not None:
                    # The location is already valid. If so, make sure that this step either does
                    # not have any type bounds (e.g. via QueryRoot or CoerceType blocks),
                    # or has type bounds that match the previously-decided type bound.
                    _assert_type_bounds_are_not_conflicting(
                        current_type_bound, previous_type_bound, current_step_location, match_query)
                else:
                    # The location is not yet known to be valid. If it does not have
                    # a type bound in this MATCH step, add a type coercion to the type
                    # registered in "location_types".
                    if current_type_bound is None:
                        current_type_bound = location_types[current_step_location].name
                        new_step = match_step._replace(
                            coerce_type_block=CoerceType({current_type_bound}))

                    preferred_location_types[current_step_location] = current_type_bound
            elif current_step_location in eligible_locations:
                # This location is eligible, but not preferred. We have not make sure
                # none of the MATCH steps with this location have type bounds, and therefore
                # will not produce a corresponding "class:" clause in the resulting MATCH query.
                current_type_bound = _calculate_type_bound_at_step(match_step)
                previous_type_bound = eligible_location_types.get(current_step_location, None)
                if current_type_bound is not None:
                    # There is a type bound here that we need to neutralize.
                    _assert_type_bounds_are_not_conflicting(
                        current_type_bound, previous_type_bound, current_step_location, match_query)

                    # Record the deduced type bound, so that if we encounter this location again,
                    # we ensure that we again infer the same type bound.
                    eligible_location_types[current_step_location] = current_type_bound

                    if (current_step_location not in coerced_locations or
                            previous_type_bound is not None):
                        # The type bound here is already implied by the GraphQL query structure,
                        # or has already been applied at a previous occurrence of this location.
                        # We can simply delete the QueryRoot / CoerceType blocks that impart it.
                        if isinstance(match_step.root_block, QueryRoot):
                            new_root_block = None
                        else:
                            new_root_block = match_step.root_block

                        new_step = match_step._replace(
                            root_block=new_root_block, coerce_type_block=None)
                    else:
                        # The type bound here is not already implied by the GraphQL query structure.
                        # This should only be possible via a CoerceType block. Lower this CoerceType
                        # block into a Filter with INSTANCEOF to ensure the resulting query has the
                        # same semantics, while making the location invalid as a query start point.
                        if (isinstance(match_step.root_block, QueryRoot) or
                                match_step.coerce_type_block is None):
                            raise AssertionError(u'Unexpected MATCH step applying a type bound not '
                                                 u'already implied by the GraphQL query structure: '
                                                 u'{} {}'.format(match_step, match_query))

                        new_where_block = convert_coerce_type_and_add_to_where_block(
                            match_step.coerce_type_block, match_step.where_block)
                        new_step = match_step._replace(
                            coerce_type_block=None, where_block=new_where_block)
                else:
                    # There is no type bound that OrientDB can find defined at this location.
                    # No action is necessary.
                    pass
            else:
                # This location is neither preferred nor eligible.
                # No action is necessary at this location.
                pass

            new_traversal.append(new_step)
        new_match_traversals.append(new_traversal)
    return match_query._replace(match_traversals=new_match_traversals)
Return a MATCH query where only preferred locations are valid as query start locations.