Repository: vespa-engine/vespa
File: container-search/src/main/java/com/yahoo/prelude/semantics/rule/LiteralTermProduction.java (Java)

Code before:
        if (replacing) {
            Match matched = e.getNonreferencedMatch(0);
            newItem.setWeight(matched.getItem().getWeight());
            insertMatch(e, matched, List.of(newItem), offset);
        }
        else {
            newItem.setWeight(getWeight());
            if (e.getTraceLevel() >= 6)
                e.trace(6, "Adding '" + newItem + "'");
            if (shouldInsertAtMatch(e)) {
                // Add to the match's parent when it's a nested composite with default type
                Match matched = e.getNonreferencedMatch(0);
                insertMatch(e, matched, List.of(newItem), offset);
            }
            else {
                // Use root-level combining for specific types (RANK, OR, etc.) and non-nested cases
                e.addItems(List.of(newItem), getTermType());
            }
        }
    }

    /**
     * Returns true if we should insert at the match position rather than adding to root.
     * This is the case when the match's parent is a nested composite (not directly under QueryTree).
     */
    private boolean shouldInsertAtMatch(RuleEvaluation e) {
        if (getTermType() != TermType.DEFAULT) return false;
        if (e.getNonreferencedMatchCount() == 0) return false;
        Match matched = e.getNonreferencedMatch(0);
        CompositeItem parent = matched.getParent();
        if (parent == null) return false;
        // Insert at match if parent is not QueryTree and not a direct child of QueryTree
        CompositeItem grandparent = parent.getParent();
        return grandparent != null && !(grandparent instanceof com.yahoo.search.query.QueryTree);
    }

    public String toInnerTermString() {
        return getLabelString() + literal;
    }

}


Review comment (suggestion):
The `shouldInsertAtMatch` logic is nearly identical in `LiteralTermProduction` and `ReferenceTermProduction`, but this version checks `getTermType() != TermType.DEFAULT` while the one in `ReferenceTermProduction` does not. This means a `ReferenceTermProduction` with a non-DEFAULT term type (e.g. RANK, OR) in a nested composite would insert at the match position, while `LiteralTermProduction` would add at root level instead.

Is this intentional? If the logic should be the same, consider moving `shouldInsertAtMatch` to `TermProduction` to avoid them diverging.

Also minor: this uses `com.yahoo.search.query.QueryTree` as FQCN while `ReferenceTermProduction` imports it.

Code after:
        WordItem newItem = new WordItem(literal, getLabel(), true);
        if (replacing) {
            Match matched = e.getNonreferencedMatch(0);
            newItem.setWeight(matched.getItem().getWeight());
            insertMatch(e, matched, List.of(newItem), offset);
        }
        else {
            newItem.setWeight(getWeight());
            if (e.getTraceLevel() >= 6)
                e.trace(6, "Adding '" + newItem + "'");
            if (shouldInsertAtMatch(e)) {
                // Add to the match's parent when it's a nested composite with default type
                Match matched = e.getNonreferencedMatch(0);
                insertMatch(e, matched, List.of(newItem), offset);
            }
            else {
                // Use root-level combining for specific types (RANK, OR, etc.) and non-nested cases
                e.addItems(List.of(newItem), getTermType());
            }
        }
    }

    private boolean shouldInsertAtMatch(RuleEvaluation e) {
        if (e.getNonreferencedMatchCount() == 0) return false;
        return shouldInsertAtMatch(e.getNonreferencedMatch(0));
    }

    public String toInnerTermString() {
        return getLabelString() + literal;
    }

}