How to query a custom post type with a taxonomy filter but display post type archive page?

The simplest way is, in all honesty, to hook into wp_title and change things, and just be aware that the warnings are there but not showing in production (because you’ve property configured your server).

What follows is a kind of hacky solution.

If you take a look at get_queried_object

<?php
function get_queried_object() {
    global $wp_query;
    return $wp_query->get_queried_object();
}

Just a thin wrapper around WP_Query::get_queried_object, so let’s take a look at that.

<?php
function get_queried_object() {
    if ( isset($this->queried_object) )
        return $this->queried_object;

    $this->queried_object = null;
    $this->queried_object_id = 0;

    if ( $this->is_category || $this->is_tag || $this->is_tax ) {
        $tax_query_in_and = wp_list_filter( $this->tax_query->queries, array( 'operator' => 'NOT IN' ), 'NOT' );

        $query = reset( $tax_query_in_and );

        if ( 'term_id' == $query['field'] )
            $term = get_term( reset( $query['terms'] ), $query['taxonomy'] );
        elseif ( $query['terms'] )
            $term = get_term_by( $query['field'], reset( $query['terms'] ), $query['taxonomy'] );

        if ( ! empty( $term ) && ! is_wp_error( $term ) )  {
            $this->queried_object = $term;
            $this->queried_object_id = (int) $term->term_id;

            if ( $this->is_category )
                _make_cat_compat( $this->queried_object );
        }
    } elseif ( $this->is_post_type_archive ) {
        $this->queried_object = get_post_type_object( $this->get('post_type') );
    } elseif ( $this->is_posts_page ) {
        $page_for_posts = get_option('page_for_posts');
        $this->queried_object = get_post( $page_for_posts );
        $this->queried_object_id = (int) $this->queried_object->ID;
    } elseif ( $this->is_singular && !is_null($this->post) ) {
        $this->queried_object = $this->post;
        $this->queried_object_id = (int) $this->post->ID;
    } elseif ( $this->is_author ) {
        $this->queried_object_id = (int) $this->get('author');
        $this->queried_object = get_userdata( $this->queried_object_id );
    }

    return $this->queried_object;
}

As you can see, WordPress checks a bunch of stuff to see just what type of query this is and where it’s set. The first check is to see if this is a category, tag, or taxonomy archive. Since that succeeds, the post type archive object is never set.

What’s really weird is why both is_post_type_archive and is_tax are returning true. Due to the way WP_Query::parse_query works, it’s possible that an archive can be both a post type archive as well as a taxonomy archive.

Anyway, WP_Query::$queried_object is a public property, do just hook into someplace before wp_title fires and replace it. template_redirect would work.

This is very much a hack, but there’s no pre_wp_title filter or the like where you could prevent the warnings. Untested example below. Be aware that if the WP core decides to make $queried_object a private or protected property in future release this will not work. It could also negatively impact other areas of your site.

<?php
add_action('template_redirect', 'wpse87222_change_object');
function wpse87222_change_object()
{
    global $wp_the_query;

    // check for post type archive
    if (is_post_type_archive('product')) {
        $wp_the_query->queried_object = get_post_type_object('product');
    }
}