xpath FEATURE use hashes when possible to find specific node instances

Some refactoring included such as removing
special LYXP_SET_EMPTY set type because it
only caused problems.
diff --git a/src/xpath.c b/src/xpath.c
index bd57bd4..92783be 100644
--- a/src/xpath.c
+++ b/src/xpath.c
@@ -32,6 +32,7 @@
 #include "xml.h"
 #include "printer_data.h"
 #include "tree_schema_internal.h"
+#include "tree_data_internal.h"
 #include "plugins_types.h"
 
 static LY_ERR reparse_or_expr(struct ly_ctx *ctx, struct lyxp_expr *exp, uint16_t *exp_idx);
@@ -47,8 +48,6 @@
 print_set_type(struct lyxp_set *set)
 {
     switch (set->type) {
-    case LYXP_SET_EMPTY:
-        return "empty";
     case LYXP_SET_NODE_SET:
         return "node set";
     case LYXP_SET_SCNODE_SET:
@@ -244,10 +243,6 @@
         }
         break;
 
-    case LYXP_SET_EMPTY:
-        LOGDBG(LY_LDGXPATH, "set EMPTY");
-        break;
-
     case LYXP_SET_BOOLEAN:
         LOGDBG(LY_LDGXPATH, "set BOOLEAN");
         LOGDBG(LY_LDGXPATH, "\t%s", (set->val.bool ? "true" : "false"));
@@ -696,13 +691,8 @@
     return LY_SUCCESS;
 }
 
-/**
- * @brief Free dynamic content of a set.
- *
- * @param[in] set Set to modify.
- */
-static void
-set_free_content(struct lyxp_set *set)
+void
+lyxp_set_free_content(struct lyxp_set *set)
 {
     if (!set) {
         return;
@@ -711,13 +701,22 @@
     if (set->type == LYXP_SET_NODE_SET) {
         free(set->val.nodes);
         lyht_free(set->ht);
-        set->ht = NULL;
     } else if (set->type == LYXP_SET_SCNODE_SET) {
         free(set->val.scnodes);
-    } else if (set->type == LYXP_SET_STRING) {
-        free(set->val.str);
+        lyht_free(set->ht);
+    } else {
+        if (set->type == LYXP_SET_STRING) {
+            free(set->val.str);
+        }
+        set->type = LYXP_SET_NODE_SET;
     }
-    set->type = LYXP_SET_EMPTY;
+
+    set->val.nodes = NULL;
+    set->used = 0;
+    set->size = 0;
+    set->ht = NULL;
+    set->ctx_pos = 0;
+    set->ctx_pos = 0;
 }
 
 /**
@@ -732,7 +731,7 @@
         return;
     }
 
-    set_free_content(set);
+    lyxp_set_free_content(set);
     free(set);
 }
 
@@ -822,7 +821,7 @@
 static void
 set_fill_string(struct lyxp_set *set, const char *string, uint16_t str_len)
 {
-    set_free_content(set);
+    lyxp_set_free_content(set);
 
     set->type = LYXP_SET_STRING;
     if ((str_len == 0) && (string[0] != '\0')) {
@@ -840,7 +839,7 @@
 static void
 set_fill_number(struct lyxp_set *set, long double number)
 {
-    set_free_content(set);
+    lyxp_set_free_content(set);
 
     set->type = LYXP_SET_NUMBER;
     set->val.num = number;
@@ -855,7 +854,7 @@
 static void
 set_fill_boolean(struct lyxp_set *set, int boolean)
 {
-    set_free_content(set);
+    lyxp_set_free_content(set);
 
     set->type = LYXP_SET_BOOLEAN;
     set->val.bool = boolean;
@@ -903,25 +902,21 @@
             free(trg->val.str);
         }
 
-        if (src->type == LYXP_SET_EMPTY) {
-            trg->type = LYXP_SET_EMPTY;
+        assert(src->type == LYXP_SET_NODE_SET);
+
+        trg->type = LYXP_SET_NODE_SET;
+        trg->used = src->used;
+        trg->size = src->used;
+        trg->ctx_pos = src->ctx_pos;
+        trg->ctx_size = src->ctx_size;
+
+        trg->val.nodes = malloc(trg->used * sizeof *trg->val.nodes);
+        LY_CHECK_ERR_RET(!trg->val.nodes, LOGMEM(src->ctx); memset(trg, 0, sizeof *trg), );
+        memcpy(trg->val.nodes, src->val.nodes, src->used * sizeof *src->val.nodes);
+        if (src->ht) {
+            trg->ht = lyht_dup(src->ht);
         } else {
-            assert(src->type == LYXP_SET_NODE_SET);
-
-            trg->type = LYXP_SET_NODE_SET;
-            trg->used = src->used;
-            trg->size = src->used;
-            trg->ctx_pos = src->ctx_pos;
-            trg->ctx_size = src->ctx_size;
-
-            trg->val.nodes = malloc(trg->used * sizeof *trg->val.nodes);
-            LY_CHECK_ERR_RET(!trg->val.nodes, LOGMEM(src->ctx); memset(trg, 0, sizeof *trg), );
-            memcpy(trg->val.nodes, src->val.nodes, src->used * sizeof *src->val.nodes);
-            if (src->ht) {
-                trg->ht = lyht_dup(src->ht);
-            } else {
-                trg->ht = NULL;
-            }
+            trg->ht = NULL;
         }
     }
 }
@@ -965,8 +960,7 @@
         memmove(&set->val.nodes[idx], &set->val.nodes[idx + 1],
                 (set->used - idx) * sizeof *set->val.nodes);
     } else {
-        set_free_content(set);
-        set->type = LYXP_SET_EMPTY;
+        lyxp_set_free_content(set);
     }
 }
 
@@ -1000,7 +994,7 @@
     uint16_t i, orig_used, end;
     int32_t start;
 
-    assert(set && (set->type != LYXP_SET_EMPTY));
+    assert(set);
 
     orig_used = set->used;
     set->used = 0;
@@ -1029,12 +1023,6 @@
             set->used += end - start;
         }
     }
-
-    if (!set->used) {
-        set_free_content(set);
-        /* this changes it to LYXP_SET_EMPTY */
-        memset(set, 0, sizeof *set);
-    }
 }
 
 /**
@@ -1091,14 +1079,13 @@
 {
     uint32_t orig_used, i, j;
 
-    assert(((set1->type == LYXP_SET_SCNODE_SET) || (set1->type == LYXP_SET_EMPTY))
-        && ((set2->type == LYXP_SET_SCNODE_SET) || (set2->type == LYXP_SET_EMPTY)));
+    assert((set1->type == LYXP_SET_SCNODE_SET) && (set2->type == LYXP_SET_SCNODE_SET));
 
-    if (set2->type == LYXP_SET_EMPTY) {
+    if (!set2->used) {
         return;
     }
 
-    if (set1->type == LYXP_SET_EMPTY) {
+    if (!set1->used) {
         memcpy(set1, set2, sizeof *set1);
         return;
     }
@@ -1125,8 +1112,8 @@
         }
     }
 
-    set_free_content(set2);
-    set2->type = LYXP_SET_EMPTY;
+    lyxp_set_free_content(set2);
+    set2->type = LYXP_SET_SCNODE_SET;
 }
 
 /**
@@ -1141,9 +1128,9 @@
 static void
 set_insert_node(struct lyxp_set *set, const struct lyd_node *node, uint32_t pos, enum lyxp_node_type node_type, uint32_t idx)
 {
-    assert(set && ((set->type == LYXP_SET_NODE_SET) || (set->type == LYXP_SET_EMPTY)));
+    assert(set && (set->type == LYXP_SET_NODE_SET));
 
-    if (set->type == LYXP_SET_EMPTY) {
+    if (!set->size) {
         /* first item */
         if (idx) {
             /* no real harm done, but it is a bug */
@@ -1665,16 +1652,15 @@
     int cmp;
     const struct lyd_node *root;
 
-    if (((trg->type != LYXP_SET_NODE_SET) && (trg->type != LYXP_SET_EMPTY))
-            || ((src->type != LYXP_SET_NODE_SET) && (src->type != LYXP_SET_EMPTY))) {
+    if ((trg->type != LYXP_SET_NODE_SET) || (src->type != LYXP_SET_NODE_SET)) {
         return LY_EINVAL;
     }
 
-    if (src->type == LYXP_SET_EMPTY) {
+    if (!src->used) {
         return LY_SUCCESS;
-    } else if (trg->type == LYXP_SET_EMPTY) {
+    } else if (!trg->used) {
         set_fill_set(trg, src);
-        lyxp_set_cast(src, LYXP_SET_EMPTY);
+        lyxp_set_free_content(src);
         return LY_SUCCESS;
     }
 
@@ -1774,7 +1760,7 @@
     print_set_debug(trg);
 #endif
 
-    lyxp_set_cast(src, LYXP_SET_EMPTY);
+    lyxp_set_free_content(src);
     return LY_SUCCESS;
 }
 
@@ -3234,7 +3220,7 @@
         return rc;
     }
 
-    if ((args[0]->type != LYXP_SET_NODE_SET) && (args[0]->type != LYXP_SET_EMPTY)) {
+    if (args[0]->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "bit-is-set(node-set, string)");
         return LY_EVALID;
     }
@@ -3242,7 +3228,7 @@
     LY_CHECK_RET(rc);
 
     set_fill_boolean(set, 0);
-    if (args[0]->type == LYXP_SET_NODE_SET) {
+    if (args[0]->used) {
         leaf = (struct lyd_node_term *)args[0]->val.nodes[0].node;
         if ((leaf->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST))
                 && (((struct lysc_node_leaf *)leaf->schema)->type->basetype == LY_TYPE_BITS)) {
@@ -3373,7 +3359,7 @@
     }
 
     /* free, kind of */
-    lyxp_set_cast(set, LYXP_SET_EMPTY);
+    lyxp_set_free_content(set);
     set->type = LYXP_SET_STRING;
     set->val.str = str;
 
@@ -3455,11 +3441,6 @@
         return rc;
     }
 
-    if (args[0]->type == LYXP_SET_EMPTY) {
-        set_fill_number(set, 0);
-        return LY_SUCCESS;
-    }
-
     if (args[0]->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "count(node-set)");
         return LY_EVALID;
@@ -3492,7 +3473,7 @@
 
         lyxp_set_scnode_insert_node(set, set->ctx_scnode, LYXP_NODE_ELEM);
     } else {
-        lyxp_set_cast(set, LYXP_SET_EMPTY);
+        lyxp_set_free_content(set);
 
         /* position is filled later */
         set_insert_node(set, set->ctx_node, 0, LYXP_NODE_ELEM, 0);
@@ -3546,13 +3527,13 @@
         return rc;
     }
 
-    if ((args[0]->type != LYXP_SET_NODE_SET) && (args[0]->type != LYXP_SET_EMPTY)) {
+    if (args[0]->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "deref(node-set)");
         return LY_EVALID;
     }
 
-    lyxp_set_cast(set, LYXP_SET_EMPTY);
-    if (args[0]->type != LYXP_SET_EMPTY) {
+    lyxp_set_free_content(set);
+    if (args[0]->used) {
         leaf = (struct lyd_node_term *)args[0]->val.nodes[0].node;
         sleaf = (struct lysc_node_leaf *)leaf->schema;
         if (sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
@@ -3633,41 +3614,40 @@
         return rc;
     }
 
-    if ((args[0]->type != LYXP_SET_NODE_SET) && (args[0]->type != LYXP_SET_EMPTY)) {
-        LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "derived-from(node-set, string)");
+    if (args[0]->type != LYXP_SET_NODE_SET) {
+        LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]),
+               "derived-from(node-set, string)");
         return LY_EVALID;
     }
     rc = lyxp_set_cast(args[1], LYXP_SET_STRING);
     LY_CHECK_RET(rc);
 
     set_fill_boolean(set, 0);
-    if (args[0]->type != LYXP_SET_EMPTY) {
-        found = 0;
-        for (i = 0; i < args[0]->used; ++i) {
-            leaf = (struct lyd_node_term *)args[0]->val.nodes[i].node;
-            sleaf = (struct lysc_node_leaf *)leaf->schema;
-            if ((sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && (sleaf->type->basetype == LY_TYPE_IDENT)) {
-                /* store args[1] into ident */
-                rc = sleaf->type->plugin->store(set->ctx, sleaf->type, args[1]->val.str, strlen(args[1]->val.str),
-                                                LY_TYPE_OPTS_STORE, lys_resolve_prefix, (void *)sleaf->dflt_mod, set->format,
-                                                (struct lyd_node *)leaf, set->tree, &data, NULL, &err);
-                if (err) {
-                    ly_err_print(err);
-                    ly_err_free(err);
-                }
-                LY_CHECK_RET(rc);
+    found = 0;
+    for (i = 0; i < args[0]->used; ++i) {
+        leaf = (struct lyd_node_term *)args[0]->val.nodes[i].node;
+        sleaf = (struct lysc_node_leaf *)leaf->schema;
+        if ((sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && (sleaf->type->basetype == LY_TYPE_IDENT)) {
+            /* store args[1] into ident */
+            rc = sleaf->type->plugin->store(set->ctx, sleaf->type, args[1]->val.str, strlen(args[1]->val.str),
+                                            LY_TYPE_OPTS_STORE, lys_resolve_prefix, (void *)sleaf->dflt_mod, set->format,
+                                            (struct lyd_node *)leaf, set->tree, &data, NULL, &err);
+            if (err) {
+                ly_err_print(err);
+                ly_err_free(err);
+            }
+            LY_CHECK_RET(rc);
 
-                LY_ARRAY_FOR(data.ident->derived, u) {
-                    if (data.ident->derived[u] == leaf->value.ident) {
-                        set_fill_boolean(set, 1);
-                        found = 1;
-                        break;
-                    }
-                }
-                if (found) {
+            LY_ARRAY_FOR(data.ident->derived, u) {
+                if (data.ident->derived[u] == leaf->value.ident) {
+                    set_fill_boolean(set, 1);
+                    found = 1;
                     break;
                 }
             }
+            if (found) {
+                break;
+            }
         }
     }
 
@@ -3717,44 +3697,43 @@
         return rc;
     }
 
-    if ((args[0]->type != LYXP_SET_NODE_SET) && (args[0]->type != LYXP_SET_EMPTY)) {
-        LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "derived-from-or-self(node-set, string)");
+    if (args[0]->type != LYXP_SET_NODE_SET) {
+        LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]),
+               "derived-from-or-self(node-set, string)");
         return LY_EVALID;
     }
     rc = lyxp_set_cast(args[1], LYXP_SET_STRING);
     LY_CHECK_RET(rc);
 
     set_fill_boolean(set, 0);
-    if (args[0]->type != LYXP_SET_EMPTY) {
-        found = 0;
-        for (i = 0; i < args[0]->used; ++i) {
-            leaf = (struct lyd_node_term *)args[0]->val.nodes[i].node;
-            sleaf = (struct lysc_node_leaf *)leaf->schema;
-            if ((sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && (sleaf->type->basetype == LY_TYPE_IDENT)) {
-                /* store args[1] into ident */
-                rc = sleaf->type->plugin->store(set->ctx, sleaf->type, args[1]->val.str, strlen(args[1]->val.str),
-                                                LY_TYPE_OPTS_STORE, lys_resolve_prefix, (void *)sleaf->dflt_mod, set->format,
-                                                (struct lyd_node *)leaf, set->tree, &data, NULL, &err);
-                if (err) {
-                    ly_err_print(err);
-                    ly_err_free(err);
-                }
-                LY_CHECK_RET(rc);
+    found = 0;
+    for (i = 0; i < args[0]->used; ++i) {
+        leaf = (struct lyd_node_term *)args[0]->val.nodes[i].node;
+        sleaf = (struct lysc_node_leaf *)leaf->schema;
+        if ((sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && (sleaf->type->basetype == LY_TYPE_IDENT)) {
+            /* store args[1] into ident */
+            rc = sleaf->type->plugin->store(set->ctx, sleaf->type, args[1]->val.str, strlen(args[1]->val.str),
+                                            LY_TYPE_OPTS_STORE, lys_resolve_prefix, (void *)sleaf->dflt_mod, set->format,
+                                            (struct lyd_node *)leaf, set->tree, &data, NULL, &err);
+            if (err) {
+                ly_err_print(err);
+                ly_err_free(err);
+            }
+            LY_CHECK_RET(rc);
 
-                if (data.ident == leaf->value.ident) {
+            if (data.ident == leaf->value.ident) {
+                set_fill_boolean(set, 1);
+                break;
+            }
+            LY_ARRAY_FOR(data.ident->derived, u) {
+                if (data.ident->derived[u] == leaf->value.ident) {
                     set_fill_boolean(set, 1);
+                    found = 1;
                     break;
                 }
-                LY_ARRAY_FOR(data.ident->derived, u) {
-                    if (data.ident->derived[u] == leaf->value.ident) {
-                        set_fill_boolean(set, 1);
-                        found = 1;
-                        break;
-                    }
-                }
-                if (found) {
-                    break;
-                }
+            }
+            if (found) {
+                break;
             }
         }
     }
@@ -3791,13 +3770,13 @@
         return rc;
     }
 
-    if ((args[0]->type != LYXP_SET_NODE_SET) && (args[0]->type != LYXP_SET_EMPTY)) {
+    if (args[0]->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "enum-value(node-set)");
         return LY_EVALID;
     }
 
     set_fill_number(set, NAN);
-    if (args[0]->type == LYXP_SET_NODE_SET) {
+    if (args[0]->used) {
         leaf = (struct lyd_node_term *)args[0]->val.nodes[0].node;
         sleaf = (struct lysc_node_leaf *)leaf->schema;
         if ((sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && (sleaf->type->basetype == LY_TYPE_ENUM)) {
@@ -3889,13 +3868,12 @@
     rc = lyxp_set_cast(args[0], LYXP_SET_STRING);
     LY_CHECK_RET(rc);
 
-    if (set->type == LYXP_SET_EMPTY) {
-        set_fill_boolean(set, 0);
-        return LY_SUCCESS;
-    }
     if (set->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INCTX, print_set_type(set), "lang(string)");
         return LY_EVALID;
+    } else if (!set->used) {
+        set_fill_boolean(set, 0);
+        return LY_SUCCESS;
     }
 
     switch (set->val.nodes[0].type) {
@@ -3970,13 +3948,12 @@
         return LY_SUCCESS;
     }
 
-    if (set->type == LYXP_SET_EMPTY) {
-        set_fill_number(set, 0);
-        return LY_SUCCESS;
-    }
     if (set->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INCTX, print_set_type(set), "last()");
         return LY_EVALID;
+    } else if (!set->used) {
+        set_fill_number(set, 0);
+        return LY_SUCCESS;
     }
 
     set_fill_number(set, set->ctx_size);
@@ -4006,13 +3983,12 @@
     }
 
     if (arg_count) {
-        if (args[0]->type == LYXP_SET_EMPTY) {
-            set_fill_string(set, "", 0);
-            return LY_SUCCESS;
-        }
         if (args[0]->type != LYXP_SET_NODE_SET) {
             LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "local-name(node-set?)");
             return LY_EVALID;
+        } else if (!args[0]->used) {
+            set_fill_string(set, "", 0);
+            return LY_SUCCESS;
         }
 
         /* we need the set sorted, it affects the result */
@@ -4020,13 +3996,12 @@
 
         item = &args[0]->val.nodes[0];
     } else {
-        if (set->type == LYXP_SET_EMPTY) {
-            set_fill_string(set, "", 0);
-            return LY_SUCCESS;
-        }
         if (set->type != LYXP_SET_NODE_SET) {
             LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INCTX, print_set_type(set), "local-name(node-set?)");
             return LY_EVALID;
+        } else if (!set->used) {
+            set_fill_string(set, "", 0);
+            return LY_SUCCESS;
         }
 
         /* we need the set sorted, it affects the result */
@@ -4079,13 +4054,12 @@
     }
 
     if (arg_count) {
-        if (args[0]->type == LYXP_SET_EMPTY) {
-            set_fill_string(set, "", 0);
-            return LY_SUCCESS;
-        }
         if (args[0]->type != LYXP_SET_NODE_SET) {
             LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "name(node-set?)");
             return LY_EVALID;
+        } else if (!args[0]->used) {
+            set_fill_string(set, "", 0);
+            return LY_SUCCESS;
         }
 
         /* we need the set sorted, it affects the result */
@@ -4093,13 +4067,12 @@
 
         item = &args[0]->val.nodes[0];
     } else {
-        if (set->type == LYXP_SET_EMPTY) {
-            set_fill_string(set, "", 0);
-            return LY_SUCCESS;
-        }
         if (set->type != LYXP_SET_NODE_SET) {
             LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INCTX, print_set_type(set), "name(node-set?)");
             return LY_EVALID;
+        } else if (!set->used) {
+            set_fill_string(set, "", 0);
+            return LY_SUCCESS;
         }
 
         /* we need the set sorted, it affects the result */
@@ -4172,27 +4145,26 @@
     }
 
     if (arg_count) {
-        if (args[0]->type == LYXP_SET_EMPTY) {
+        if (args[0]->type != LYXP_SET_NODE_SET) {
+            LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]),
+                   "namespace-uri(node-set?)");
+            return LY_EVALID;
+        } else if (!args[0]->used) {
             set_fill_string(set, "", 0);
             return LY_SUCCESS;
         }
-        if (args[0]->type != LYXP_SET_NODE_SET) {
-            LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "namespace-uri(node-set?)");
-            return LY_EVALID;
-        }
 
         /* we need the set sorted, it affects the result */
         assert(!set_sort(args[0]));
 
         item = &args[0]->val.nodes[0];
     } else {
-        if (set->type == LYXP_SET_EMPTY) {
-            set_fill_string(set, "", 0);
-            return LY_SUCCESS;
-        }
         if (set->type != LYXP_SET_NODE_SET) {
             LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INCTX, print_set_type(set), "namespace-uri(node-set?)");
             return LY_EVALID;
+        } else if (!set->used) {
+            set_fill_string(set, "", 0);
+            return LY_SUCCESS;
         }
 
         /* we need the set sorted, it affects the result */
@@ -4245,7 +4217,7 @@
     }
 
     if (set->type != LYXP_SET_NODE_SET) {
-        lyxp_set_cast(set, LYXP_SET_EMPTY);
+        lyxp_set_free_content(set);
     }
     return LY_SUCCESS;
 }
@@ -4419,13 +4391,12 @@
         return LY_SUCCESS;
     }
 
-    if (set->type == LYXP_SET_EMPTY) {
-        set_fill_number(set, 0);
-        return LY_SUCCESS;
-    }
     if (set->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INCTX, print_set_type(set), "position()");
         return LY_EVALID;
+    } else if (!set->used) {
+        set_fill_number(set, 0);
+        return LY_SUCCESS;
     }
 
     set_fill_number(set, set->ctx_pos);
@@ -4928,13 +4899,12 @@
     }
 
     set_fill_number(set, 0);
-    if (args[0]->type == LYXP_SET_EMPTY) {
-        return LY_SUCCESS;
-    }
 
     if (args[0]->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "sum(node-set)");
         return LY_EVALID;
+    } else if (!args[0]->used) {
+        return LY_SUCCESS;
     }
 
     set_init(&set_item, set);
@@ -4981,9 +4951,6 @@
         return LY_SUCCESS;
     }
 
-    if (set->type == LYXP_SET_EMPTY) {
-        return LY_SUCCESS;
-    }
     if (set->type != LYXP_SET_NODE_SET) {
         LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INCTX, print_set_type(set), "text()");
         return LY_EVALID;
@@ -5105,7 +5072,7 @@
     }
     new[new_used] = '\0';
 
-    lyxp_set_cast(set, LYXP_SET_EMPTY);
+    lyxp_set_free_content(set);
     set->type = LYXP_SET_STRING;
     set->val.str = new;
 
@@ -5222,7 +5189,8 @@
         set_scnode_clear_ctx(set);
         lyxp_set_scnode_insert_node(set, NULL, set->root_type);
     } else {
-        lyxp_set_cast(set, LYXP_SET_EMPTY);
+        set->type = LYXP_SET_NODE_SET;
+        set->used = 0;
         set_insert_node(set, NULL, 0, set->root_type, 0);
     }
 }
@@ -5258,8 +5226,8 @@
  *
  * @param[in] node Node to check.
  * @param[in] root_type XPath root node type.
- * @param[in] node_name Node name to move to. Must be in the dictionary!
- * @param[in] moveto_mod Expected module of the node.
+ * @param[in] node_name Node name in the dictionary to move to, NULL for any node.
+ * @param[in] moveto_mod Expected module of the node, NULL for any.
  * @return LY_ERR (LY_ENOT if node does not match, LY_EINCOMPLETE on unresolved when,
  * LY_EINVAL if netither node nor any children match)
  */
@@ -5278,7 +5246,7 @@
     }
 
     /* name check */
-    if (strcmp(node_name, "*") && (node->schema->name != node_name)) {
+    if (node_name && (node->schema->name != node_name)) {
         return LY_ENOT;
     }
 
@@ -5296,8 +5264,8 @@
  *
  * @param[in] node Schema node to check.
  * @param[in] root_type XPath root node type.
- * @param[in] node_name Node name to move to. Must be in the dictionary!
- * @param[in] moveto_mod Expected module of the node.
+ * @param[in] node_name Node name in the dictionary to move to, NULL for any nodes.
+ * @param[in] moveto_mod Expected module of the node, NULL for any.
  * @return LY_ERR (LY_ENOT if node does not match, LY_EINVAL if neither node nor any children match)
  */
 static LY_ERR
@@ -5305,7 +5273,7 @@
                     const struct lys_module *moveto_mod)
 {
     /* module check */
-    if (strcmp(node_name, "*") && (node->module != moveto_mod)) {
+    if (moveto_mod && (node->module != moveto_mod)) {
         return LY_ENOT;
     }
 
@@ -5315,7 +5283,7 @@
     }
 
     /* name check */
-    if (strcmp(node_name, "*") && (node->name != node_name)) {
+    if (node_name && (node->name != node_name)) {
         return LY_ENOT;
     }
 
@@ -5324,26 +5292,22 @@
 }
 
 /**
- * @brief Move context @p set to a node. Handles '/' and '*', 'NAME', 'PREFIX:*', or 'PREFIX:NAME'.
- *        Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY). Context position aware.
+ * @brief Move context @p set to a node. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY). Context position aware.
  *
  * @param[in,out] set Set to use.
- * @param[in] qname Qualified node name to move to.
- * @param[in] qname_len Length of @p qname.
- * @param[in] options XPath options.
+ * @param[in] mod Matching node module, NULL for any.
+ * @param[in] ncname Matching node name in the dictionary, NULL for any.
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
  */
 static LY_ERR
-moveto_node(struct lyxp_set *set, const char *qname, uint16_t qname_len)
+moveto_node(struct lyxp_set *set, const struct lys_module *mod, const char *ncname)
 {
     uint32_t i;
     int replaced;
-    const char *name_dict = NULL; /* optimization - so we can do (==) instead (!strncmp(...)) in moveto_node_check() */
-    const struct lys_module *moveto_mod;
-    const struct lyd_node *sub;
+    const struct lyd_node *siblings, *sub;
     LY_ERR rc;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -5352,52 +5316,32 @@
         return LY_EVALID;
     }
 
-    rc = moveto_resolve_model(&qname, &qname_len, set, &moveto_mod);
-    LY_CHECK_RET(rc);
-
-    /* name */
-    name_dict = lydict_insert(set->ctx, qname, qname_len);
-
     for (i = 0; i < set->used; ) {
         replaced = 0;
+        siblings = NULL;
 
         if ((set->val.nodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.nodes[i].type == LYXP_NODE_ROOT)) {
             assert(!set->val.nodes[i].node);
+
             /* search in all the trees */
-            for (sub = set->tree; sub; sub = sub->next) {
-                rc = moveto_node_check(sub, set->root_type, name_dict, moveto_mod);
-                if (rc == LY_SUCCESS) {
-                    /* pos filled later */
-                    if (!replaced) {
-                        set_replace_node(set, sub, 0, LYXP_NODE_ELEM, i);
-                        replaced = 1;
-                    } else {
-                        set_insert_node(set, sub, 0, LYXP_NODE_ELEM, i);
-                    }
-                    ++i;
-                } else if (rc == LY_EINCOMPLETE) {
-                    lydict_remove(set->ctx, name_dict);
-                    return rc;
-                }
-            }
-
-        /* skip nodes without children - leaves, leaflists, anyxmls (ouput root will eval to true) */
+            siblings = set->tree;
         } else if (!(set->val.nodes[i].node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA))) {
+            /* search in children */
+            siblings = lyd_node_children(set->val.nodes[i].node);
+        }
 
-            for (sub = lyd_node_children(set->val.nodes[i].node); sub; sub = sub->next) {
-                rc = moveto_node_check(sub, set->root_type, name_dict, moveto_mod);
-                if (rc == LY_SUCCESS) {
-                    if (!replaced) {
-                        set_replace_node(set, sub, 0, LYXP_NODE_ELEM, i);
-                        replaced = 1;
-                    } else {
-                        set_insert_node(set, sub, 0, LYXP_NODE_ELEM, i);
-                    }
-                    ++i;
-                } else if (rc == LY_EINCOMPLETE) {
-                    lydict_remove(set->ctx, name_dict);
-                    return rc;
+        for (sub = siblings; sub; sub = sub->next) {
+            rc = moveto_node_check(sub, set->root_type, ncname, mod);
+            if (rc == LY_SUCCESS) {
+                if (!replaced) {
+                    set_replace_node(set, sub, 0, LYXP_NODE_ELEM, i);
+                    replaced = 1;
+                } else {
+                    set_insert_node(set, sub, 0, LYXP_NODE_ELEM, i);
                 }
+                ++i;
+            } else if (rc == LY_EINCOMPLETE) {
+                return rc;
             }
         }
 
@@ -5406,32 +5350,110 @@
             set_remove_node(set, i);
         }
     }
-    lydict_remove(set->ctx, name_dict);
 
     return LY_SUCCESS;
 }
 
 /**
- * @brief Move context @p set to a schema node. Handles '/' and '*', 'NAME', 'PREFIX:*', or 'PREFIX:NAME'.
- *        Result is LYXP_SET_SCNODE_SET (or LYXP_SET_EMPTY).
+ * @brief Move context @p set to a node using hashes. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
+ * Context position aware.
  *
  * @param[in,out] set Set to use.
- * @param[in] qname Qualified node name to move to.
- * @param[in] qname_len Length of @p qname.
+ * @param[in] scnode Matching node schema.
+ * @param[in] list_inst If @p scnode is ::LYS_LIST, the matching list instance. Ignored otherwise.
+ * @param[in] llist_val If @p scnode is ::LYS_LEAFLIST, the matching leaf-list value. Ignored otherwise.
+ * @param[in] llist_val_len Length of @p llist_val.
+ * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
+ */
+static LY_ERR
+moveto_node_hash(struct lyxp_set *set, const struct lysc_node *scnode, const struct lyd_node *list_inst,
+                 const char *llist_val, uint16_t llist_val_len)
+{
+    uint32_t i;
+    int replaced;
+    const struct lyd_node *siblings;
+    struct lyd_node *sub;
+
+    assert(scnode && ((scnode->nodetype != LYS_LIST) || list_inst) && ((scnode->nodetype != LYS_LEAFLIST) || llist_val));
+
+    if (!set) {
+        return LY_SUCCESS;
+    }
+
+    if (set->type != LYXP_SET_NODE_SET) {
+        LOGVAL(set->ctx, LY_VLOG_LYD, set->ctx_node, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set));
+        return LY_EVALID;
+    }
+
+    /* context check for all the nodes since we have the schema node */
+    if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (scnode->flags & LYS_CONFIG_R)) {
+        lyxp_set_free_content(set);
+        return LY_SUCCESS;
+    }
+
+    for (i = 0; i < set->used; ) {
+        replaced = 0;
+        siblings = NULL;
+
+        if ((set->val.nodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.nodes[i].type == LYXP_NODE_ROOT)) {
+            assert(!set->val.nodes[i].node);
+
+            /* search in all the trees */
+            siblings = set->tree;
+        } else if (set->val.nodes[i].type == LYXP_NODE_ELEM) {
+            /* search in children */
+            siblings = lyd_node_children(set->val.nodes[i].node);
+        }
+
+        /* find the node using hashes */
+        if (scnode->nodetype == LYS_LIST) {
+            lyd_find_sibling_first(siblings, list_inst, &sub);
+        } else {
+            lyd_find_sibling_val(siblings, scnode, llist_val, llist_val_len, &sub);
+        }
+
+        /* when check */
+        if (sub && moveto_when_check(sub)) {
+            return LY_EINCOMPLETE;
+        }
+
+        if (sub) {
+            /* pos filled later */
+            if (!replaced) {
+                set_replace_node(set, sub, 0, LYXP_NODE_ELEM, i);
+                replaced = 1;
+            } else {
+                set_insert_node(set, sub, 0, LYXP_NODE_ELEM, i);
+            }
+            ++i;
+        }
+
+        if (!replaced) {
+            /* no match */
+            set_remove_node(set, i);
+        }
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Move context @p set to a schema node. Result is LYXP_SET_SCNODE_SET (or LYXP_SET_EMPTY).
+ *
+ * @param[in,out] set Set to use.
+ * @param[in] mod Matching node module, NULL for any.
+ * @param[in] ncname Matching node name in the dictionary, NULL for any.
  * @param[in] options XPath options.
  * @return LY_ERR
  */
 static LY_ERR
-moveto_scnode(struct lyxp_set *set, const char *qname, uint16_t qname_len, int options)
+moveto_scnode(struct lyxp_set *set, const struct lys_module *mod, const char *ncname, int options)
 {
     int i, orig_used, idx, temp_ctx = 0, getnext_opts;
     uint32_t mod_idx;
-    const char *name_dict = NULL; /* optimization - so we can do (==) instead (!strncmp(...)) in moveto_node_check() */
-    const struct lys_module *moveto_mod;
     const struct lysc_node *sub, *start_parent;
-    LY_ERR rc;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -5440,12 +5462,6 @@
         return LY_EVALID;
     }
 
-    rc = moveto_resolve_model(&qname, &qname_len, set, &moveto_mod);
-    LY_CHECK_RET(rc);
-
-    /* name */
-    name_dict = lydict_insert(set->ctx, qname, qname_len);
-
     /* getnext opts */
     getnext_opts = LYS_GETNEXT_NOSTATECHECK;
     if (options & LYXP_SCNODE_OUTPUT) {
@@ -5468,14 +5484,14 @@
         start_parent = set->val.scnodes[i].scnode;
 
         if ((set->val.scnodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.scnodes[i].type == LYXP_NODE_ROOT)) {
-            /* it can actually be in any module, it's all <running>, but we know it's moveto_mod (if set),
+            /* it can actually be in any module, it's all <running>, but we know it's mod (if set),
              * so use it directly (root node itself is useless in this case) */
             mod_idx = 0;
-            while (moveto_mod || (moveto_mod = (struct lys_module *)ly_ctx_get_module_iter(set->ctx, &mod_idx))) {
+            while (mod || (mod = (struct lys_module *)ly_ctx_get_module_iter(set->ctx, &mod_idx))) {
                 sub = NULL;
                 /* module may not be implemented */
-                while (moveto_mod->implemented && (sub = lys_getnext(sub, NULL, moveto_mod->compiled, getnext_opts))) {
-                    if (!moveto_scnode_check(sub, set->root_type, name_dict, moveto_mod)) {
+                while (mod->implemented && (sub = lys_getnext(sub, NULL, mod->compiled, getnext_opts))) {
+                    if (!moveto_scnode_check(sub, set->root_type, ncname, mod)) {
                         idx = lyxp_set_scnode_insert_node(set, sub, LYXP_NODE_ELEM);
                         /* we need to prevent these nodes from being considered in this moveto */
                         if ((idx < orig_used) && (idx > i)) {
@@ -5486,18 +5502,18 @@
                 }
 
                 if (!mod_idx) {
-                    /* moveto_mod was specified, we are not going through the whole context */
+                    /* mod was specified, we are not going through the whole context */
                     break;
                 }
                 /* next iteration */
-                moveto_mod = NULL;
+                mod = NULL;
             }
 
         /* skip nodes without children - leaves, leaflists, and anyxmls (ouput root will eval to true) */
         } else if (!(start_parent->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA))) {
             sub = NULL;
             while ((sub = lys_getnext(sub, start_parent, NULL, getnext_opts))) {
-                if (!moveto_scnode_check(sub, set->root_type, name_dict, (moveto_mod ? moveto_mod : set->local_mod))) {
+                if (!moveto_scnode_check(sub, set->root_type, ncname, (mod ? mod : set->local_mod))) {
                     idx = lyxp_set_scnode_insert_node(set, sub, LYXP_NODE_ELEM);
                     if ((idx < orig_used) && (idx > i)) {
                         set->val.scnodes[idx].in_ctx = 2;
@@ -5507,7 +5523,6 @@
             }
         }
     }
-    lydict_remove(set->ctx, name_dict);
 
     /* correct temporary in_ctx values */
     if (temp_ctx) {
@@ -5522,26 +5537,23 @@
 }
 
 /**
- * @brief Move context @p set to a node and all its descendants. Handles '//' and '*', 'NAME',
- *        'PREFIX:*', or 'PREFIX:NAME'. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
+ * @brief Move context @p set to a node and all its descendants. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
  *        Context position aware.
  *
  * @param[in] set Set to use.
- * @param[in] qname Qualified node name to move to.
- * @param[in] qname_len Length of @p qname.
+ * @param[in] mod Matching node module, NULL for any.
+ * @param[in] ncname Matching node name in the dictionary, NULL for any.
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
  */
 static LY_ERR
-moveto_node_alldesc(struct lyxp_set *set, const char *qname, uint16_t qname_len)
+moveto_node_alldesc(struct lyxp_set *set, const struct lys_module *mod, const char *ncname)
 {
     uint32_t i;
-    const char *name_dict = NULL; /* optimization - so we can do (==) instead (!strncmp(...)) in moveto_node_check() */
     const struct lyd_node *next, *elem, *start;
-    const struct lys_module *moveto_mod;
     struct lyxp_set ret_set;
     LY_ERR rc;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -5550,16 +5562,10 @@
         return LY_EVALID;
     }
 
-    rc = moveto_resolve_model(&qname, &qname_len, set, &moveto_mod);
-    LY_CHECK_RET(rc);
-
     /* replace the original nodes (and throws away all text and meta nodes, root is replaced by a child) */
-    rc = moveto_node(set, "*", 1);
+    rc = moveto_node(set, NULL, NULL);
     LY_CHECK_RET(rc);
 
-    /* name */
-    name_dict = lydict_insert(set->ctx, qname, qname_len);
-
     /* this loop traverses all the nodes in the set and adds/keeps only those that match qname */
     set_init(&ret_set, set);
     for (i = 0; i < set->used; ++i) {
@@ -5567,7 +5573,7 @@
         /* TREE DFS */
         start = set->val.nodes[i].node;
         for (elem = next = start; elem; elem = next) {
-            rc = moveto_node_check(elem, set->root_type, name_dict, moveto_mod);
+            rc = moveto_node_check(elem, set->root_type, ncname, mod);
             if (!rc) {
                 /* add matching node into result set */
                 set_insert_node(&ret_set, elem, 0, LYXP_NODE_ELEM, ret_set.used);
@@ -5576,7 +5582,6 @@
                     goto skip_children;
                 }
             } else if (rc == LY_EINCOMPLETE) {
-                lydict_remove(set->ctx, name_dict);
                 return rc;
             } else if (rc == LY_EINVAL) {
                 goto skip_children;
@@ -5584,11 +5589,7 @@
 
             /* TREE DFS NEXT ELEM */
             /* select element for the next run - children first */
-            if (elem->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)) {
-                next = NULL;
-            } else {
-                next = lyd_node_children(elem);
-            }
+            next = lyd_node_children(elem);
             if (!next) {
 skip_children:
                 /* no children, so try siblings, but only if it's not the start,
@@ -5615,32 +5616,29 @@
     /* make the temporary set the current one */
     ret_set.ctx_pos = set->ctx_pos;
     ret_set.ctx_size = set->ctx_size;
-    set_free_content(set);
+    lyxp_set_free_content(set);
     memcpy(set, &ret_set, sizeof *set);
 
-    lydict_remove(set->ctx, name_dict);
     return LY_SUCCESS;
 }
 
 /**
- * @brief Move context @p set to a schema node and all its descendants. Handles '//' and '*', 'NAME',
- *        'PREFIX:*', or 'PREFIX:NAME'. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
+ * @brief Move context @p set to a schema node and all its descendants. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
  *
  * @param[in] set Set to use.
- * @param[in] qname Qualified node name to move to.
- * @param[in] qname_len Length of @p qname.
+ * @param[in] mod Matching node module, NULL for any.
+ * @param[in] ncname Matching node name in the dictionary, NULL for any.
  * @param[in] options XPath options.
  * @return LY_ERR
  */
 static LY_ERR
-moveto_scnode_alldesc(struct lyxp_set *set, const char *qname, uint16_t qname_len, int options)
+moveto_scnode_alldesc(struct lyxp_set *set, const struct lys_module *mod, const char *ncname, int options)
 {
     int i, orig_used, idx;
     const struct lysc_node *next, *elem, *start;
-    const struct lys_module *moveto_mod;
     LY_ERR rc;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -5649,9 +5647,6 @@
         return LY_EVALID;
     }
 
-    rc = moveto_resolve_model(&qname, &qname_len, set, &moveto_mod);
-    LY_CHECK_RET(rc);
-
     orig_used = set->used;
     for (i = 0; i < orig_used; ++i) {
         if (set->val.scnodes[i].in_ctx != 1) {
@@ -5673,7 +5668,7 @@
                 goto next_iter;
             }
 
-            rc = moveto_scnode_check(elem, set->root_type, qname, moveto_mod);
+            rc = moveto_scnode_check(elem, set->root_type, ncname, mod);
             if (!rc) {
                 if ((idx = lyxp_set_scnode_dup_node_check(set, elem, LYXP_NODE_ELEM, i)) > -1) {
                     set->val.scnodes[idx].in_ctx = 1;
@@ -5692,9 +5687,6 @@
             /* TREE DFS NEXT ELEM */
             /* select element for the next run - children first */
             next = lysc_node_children(elem, options & LYXP_SCNODE_OUTPUT ? LYS_CONFIG_R : LYS_CONFIG_W);
-            if (elem->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA)) {
-                next = NULL;
-            }
             if (!next) {
 skip_children:
                 /* no children, so try siblings, but only if it's not the start,
@@ -5722,25 +5714,23 @@
 }
 
 /**
- * @brief Move context @p set to an attribute. Handles '/' and '@*', '@NAME', '@PREFIX:*',
- *        or '@PREFIX:NAME'. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
+ * @brief Move context @p set to an attribute.
+ *        Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
  *        Indirectly context position aware.
  *
  * @param[in,out] set Set to use.
- * @param[in] qname Qualified node name to move to.
- * @param[in] qname_len Length of @p qname.
+ * @param[in] mod Matching metadata module, NULL for any.
+ * @param[in] ncname Matching metadata name in the dictionary, NULL for any.
  * @return LY_ERR
  */
 static LY_ERR
-moveto_attr(struct lyxp_set *set, const char *qname, uint16_t qname_len)
+moveto_attr(struct lyxp_set *set, const struct lys_module *mod, const char *ncname)
 {
     uint32_t i;
-    int replaced, all = 0;
-    const struct lys_module *moveto_mod;
+    int replaced;
     struct lyd_meta *sub;
-    LY_ERR rc;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -5749,13 +5739,6 @@
         return LY_EVALID;
     }
 
-    rc = moveto_resolve_model(&qname, &qname_len, set, &moveto_mod);
-    LY_CHECK_RET(rc);
-
-    if ((qname_len == 1) && (qname[0] == '*')) {
-        all = 1;
-    }
-
     for (i = 0; i < set->used; ) {
         replaced = 0;
 
@@ -5765,11 +5748,11 @@
             for (sub = set->val.nodes[i].node->meta; sub; sub = sub->next) {
 
                 /* check "namespace" */
-                if (moveto_mod && (sub->annotation->module != moveto_mod)) {
+                if (mod && (sub->annotation->module != mod)) {
                     continue;
                 }
 
-                if (all || (!strncmp(sub->name, qname, qname_len) && !sub->name[qname_len])) {
+                if (!ncname || (sub->name == ncname)) {
                     /* match */
                     if (!replaced) {
                         set->val.meta[i].meta = sub;
@@ -5806,21 +5789,20 @@
 {
     LY_ERR rc;
 
-    if (((set1->type != LYXP_SET_NODE_SET) && (set1->type != LYXP_SET_EMPTY))
-            || ((set2->type != LYXP_SET_NODE_SET) && (set2->type != LYXP_SET_EMPTY))) {
+    if ((set1->type != LYXP_SET_NODE_SET) || (set2->type != LYXP_SET_NODE_SET)) {
         LOGVAL(set1->ctx, LY_VLOG_LYD, set1->ctx_node, LY_VCODE_XP_INOP_2, "union", print_set_type(set1), print_set_type(set2));
         return LY_EVALID;
     }
 
     /* set2 is empty or both set1 and set2 */
-    if (set2->type == LYXP_SET_EMPTY) {
+    if (!set2->used) {
         return LY_SUCCESS;
     }
 
-    if (set1->type == LYXP_SET_EMPTY) {
+    if (!set1->used) {
         memcpy(set1, set2, sizeof *set1);
         /* dynamic memory belongs to set1 now, do not free */
-        set2->type = LYXP_SET_EMPTY;
+        memset(set2, 0, sizeof *set2);
         return LY_SUCCESS;
     }
 
@@ -5838,26 +5820,25 @@
 }
 
 /**
- * @brief Move context @p set to an attribute in any of the descendants. Handles '//' and '@*',
- *        '@NAME', '@PREFIX:*', or '@PREFIX:NAME'. Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
+ * @brief Move context @p set to an attribute in any of the descendants.
+ *        Result is LYXP_SET_NODE_SET (or LYXP_SET_EMPTY).
  *        Context position aware.
  *
  * @param[in,out] set Set to use.
- * @param[in] qname Qualified node name to move to.
- * @param[in] qname_len Length of @p qname.
+ * @param[in] mod Matching metadata module, NULL for any.
+ * @param[in] ncname Matching metadata name in the dictionary, NULL for any.
  * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
  */
 static int
-moveto_attr_alldesc(struct lyxp_set *set, const char *qname, uint16_t qname_len)
+moveto_attr_alldesc(struct lyxp_set *set, const struct lys_module *mod, const char *ncname)
 {
     uint32_t i;
-    int replaced, all = 0;
+    int replaced;
     struct lyd_meta *sub;
-    const struct lys_module *moveto_mod;
     struct lyxp_set *set_all_desc = NULL;
     LY_ERR rc;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -5866,15 +5847,12 @@
         return LY_EVALID;
     }
 
-    rc = moveto_resolve_model(&qname, &qname_len, set, &moveto_mod);
-    LY_CHECK_RET(rc);
-
     /* can be optimized similarly to moveto_node_alldesc() and save considerable amount of memory,
      * but it likely won't be used much, so it's a waste of time */
     /* copy the context */
     set_all_desc = set_copy(set);
     /* get all descendant nodes (the original context nodes are removed) */
-    rc = moveto_node_alldesc(set_all_desc, "*", 1);
+    rc = moveto_node_alldesc(set_all_desc, NULL, NULL);
     if (rc != LY_SUCCESS) {
         lyxp_set_free(set_all_desc);
         return rc;
@@ -5887,10 +5865,6 @@
     }
     lyxp_set_free(set_all_desc);
 
-    if ((qname_len == 1) && (qname[0] == '*')) {
-        all = 1;
-    }
-
     for (i = 0; i < set->used; ) {
         replaced = 0;
 
@@ -5899,11 +5873,11 @@
         if (set->val.nodes[i].type == LYXP_NODE_ELEM) {
             for (sub = set->val.nodes[i].node->meta; sub; sub = sub->next) {
                 /* check "namespace" */
-                if (moveto_mod && (sub->annotation->module != moveto_mod)) {
+                if (mod && (sub->annotation->module != mod)) {
                     continue;
                 }
 
-                if (all || (!strncmp(sub->name, qname, qname_len) && !sub->name[qname_len])) {
+                if (!ncname || (sub->name == ncname)) {
                     /* match */
                     if (!replaced) {
                         set->val.meta[i].meta = sub;
@@ -6019,7 +5993,7 @@
     struct lyxp_set ret_set;
     LY_ERR rc;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -6053,7 +6027,7 @@
         rc = moveto_self_add_children_r(set->val.nodes[i].node, set->val.nodes[i].pos, set->val.nodes[i].type, &ret_set,
                                         set, options);
         if (rc != LY_SUCCESS) {
-            set_free_content(&ret_set);
+            lyxp_set_free_content(&ret_set);
             return rc;
         }
     }
@@ -6061,7 +6035,7 @@
     /* use the temporary set as the current one */
     ret_set.ctx_pos = set->ctx_pos;
     ret_set.ctx_size = set->ctx_size;
-    set_free_content(set);
+    lyxp_set_free_content(set);
     memcpy(set, &ret_set, sizeof *set);
 
     return LY_SUCCESS;
@@ -6082,7 +6056,7 @@
     const struct lysc_node *sub;
     uint32_t i;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -6153,7 +6127,7 @@
     struct lyd_node *node, *new_node;
     enum lyxp_node_type new_type;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -6230,7 +6204,7 @@
     enum lyxp_node_type new_type;
     LY_ERR rc;
 
-    if (!set || (set->type == LYXP_SET_EMPTY)) {
+    if (!set) {
         return LY_SUCCESS;
     }
 
@@ -6261,9 +6235,7 @@
         node = set->val.scnodes[i].scnode;
 
         if (set->val.scnodes[i].type == LYXP_NODE_ELEM) {
-            for (new_node = node->parent;
-                 new_node && (new_node->nodetype & (LYS_CHOICE | LYS_CASE));
-                 new_node = new_node->parent);
+            new_node = lysc_data_parent(node);
         } else {
             /* root does not have a parent */
             continue;
@@ -6345,13 +6317,7 @@
     int64_t i;
     LY_ERR rc;
 
-    iter1.type = LYXP_SET_EMPTY;
-
-    /* empty node-sets are always false */
-    if ((set1->type == LYXP_SET_EMPTY) || (set2->type == LYXP_SET_EMPTY)) {
-        set_fill_boolean(set1, 0);
-        return LY_SUCCESS;
-    }
+    iter1.type = LYXP_SET_NODE_SET;
 
     /* iterative evaluation with node-sets */
     if ((set1->type == LYXP_SET_NODE_SET) || (set2->type == LYXP_SET_NODE_SET)) {
@@ -6372,7 +6338,7 @@
 
                 rc = moveto_op_comp(&iter1, set2, op, options);
                 if (rc != LY_SUCCESS) {
-                    set_free_content(&iter1);
+                    lyxp_set_free_content(&iter1);
                     return rc;
                 }
 
@@ -6401,11 +6367,11 @@
 
                 rc = moveto_op_comp(&iter1, &iter2, op, options);
                 if (rc != LY_SUCCESS) {
-                    set_free_content(&iter1);
-                    set_free_content(&iter2);
+                    lyxp_set_free_content(&iter1);
+                    lyxp_set_free_content(&iter2);
                     return rc;
                 }
-                set_free_content(&iter2);
+                lyxp_set_free_content(&iter2);
 
                 /* lazy evaluation until true */
                 if (iter1.val.bool) {
@@ -6556,144 +6522,9 @@
  */
 
 /**
- * @brief Evaluate Literal. Logs directly on error.
- *
- * @param[in] exp Parsed XPath expression.
- * @param[in] exp_idx Position in the expression @p exp.
- * @param[in,out] set Context and result set. On NULL the rule is only parsed.
- */
-static void
-eval_literal(struct lyxp_expr *exp, uint16_t *exp_idx, struct lyxp_set *set)
-{
-    if (set) {
-        if (exp->tok_len[*exp_idx] == 2) {
-            set_fill_string(set, "", 0);
-        } else {
-            set_fill_string(set, &exp->expr[exp->tok_pos[*exp_idx] + 1], exp->tok_len[*exp_idx] - 2);
-        }
-    }
-    LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-               print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
-    ++(*exp_idx);
-}
-
-/**
- * @brief Evaluate NodeTest. Logs directly on error.
- *
- * [6] NodeTest ::= NameTest | NodeType '(' ')'
- *
- * @param[in] exp Parsed XPath expression.
- * @param[in] exp_idx Position in the expression @p exp.
- * @param[in] attr_axis Whether to search attributes or standard nodes.
- * @param[in] all_desc Whether to search all the descendants or children only.
- * @param[in,out] set Context and result set. On NULL the rule is only parsed.
- * @param[in] options XPath options.
- * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
- */
-static int
-eval_node_test(struct lyxp_expr *exp, uint16_t *exp_idx, int attr_axis, int all_desc,
-               struct lyxp_set *set, int options)
-{
-    int i;
-    char *path;
-    LY_ERR rc;
-
-    switch (exp->tokens[*exp_idx]) {
-    case LYXP_TOKEN_NAMETEST:
-        if (attr_axis) {
-            if (set && (options & LYXP_SCNODE_ALL)) {
-                set_scnode_clear_ctx(set);
-                rc = LY_SUCCESS;
-            } else {
-                if (all_desc) {
-                    rc = moveto_attr_alldesc(set, &exp->expr[exp->tok_pos[*exp_idx]], exp->tok_len[*exp_idx]);
-                } else {
-                    rc = moveto_attr(set, &exp->expr[exp->tok_pos[*exp_idx]], exp->tok_len[*exp_idx]);
-                }
-            }
-        } else {
-            if (all_desc) {
-                if (set && (options & LYXP_SCNODE_ALL)) {
-                    rc = moveto_scnode_alldesc(set, &exp->expr[exp->tok_pos[*exp_idx]], exp->tok_len[*exp_idx], options);
-                } else {
-                    rc = moveto_node_alldesc(set, &exp->expr[exp->tok_pos[*exp_idx]], exp->tok_len[*exp_idx]);
-                }
-            } else {
-                if (set && (options & LYXP_SCNODE_ALL)) {
-                    rc = moveto_scnode(set, &exp->expr[exp->tok_pos[*exp_idx]], exp->tok_len[*exp_idx], options);
-                } else {
-                    rc = moveto_node(set, &exp->expr[exp->tok_pos[*exp_idx]], exp->tok_len[*exp_idx]);
-                }
-            }
-
-            if ((rc == LY_SUCCESS) && set && (options & LYXP_SCNODE_ALL)) {
-                for (i = set->used - 1; i > -1; --i) {
-                    if (set->val.scnodes[i].in_ctx > 0) {
-                        break;
-                    }
-                }
-                if (i == -1) {
-                    path = lysc_path(set->ctx_scnode, LYSC_PATH_LOG, NULL, 0);
-                    LOGWRN(set->ctx, "Schema node \"%.*s\" not found (%.*s) with context node \"%s\".",
-                           exp->tok_len[*exp_idx], &exp->expr[exp->tok_pos[*exp_idx]],
-                           exp->tok_pos[*exp_idx] + exp->tok_len[*exp_idx], exp->expr, path);
-                    free(path);
-                }
-            }
-        }
-        LY_CHECK_RET(rc);
-
-        LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-               print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
-        ++(*exp_idx);
-        break;
-
-    case LYXP_TOKEN_NODETYPE:
-        if (set) {
-            assert(exp->tok_len[*exp_idx] == 4);
-            if (set->type == LYXP_SET_SCNODE_SET) {
-                set_scnode_clear_ctx(set);
-                /* just for the debug message underneath */
-                set = NULL;
-            } else {
-                if (!strncmp(&exp->expr[exp->tok_pos[*exp_idx]], "node", 4)) {
-                    rc = xpath_node(NULL, 0, set, options);
-                    LY_CHECK_RET(rc);
-                } else {
-                    assert(!strncmp(&exp->expr[exp->tok_pos[*exp_idx]], "text", 4));
-                    rc = xpath_text(NULL, 0, set, options);
-                    LY_CHECK_RET(rc);
-                }
-            }
-        }
-        LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-               print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
-        ++(*exp_idx);
-
-        /* '(' */
-        assert(exp->tokens[*exp_idx] == LYXP_TOKEN_PAR1);
-        LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-               print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
-        ++(*exp_idx);
-
-        /* ')' */
-        assert(exp->tokens[*exp_idx] == LYXP_TOKEN_PAR2);
-        LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-               print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
-        ++(*exp_idx);
-        break;
-
-    default:
-        LOGINT_RET(set ? set->ctx : NULL);
-    }
-
-    return LY_SUCCESS;
-}
-
-/**
  * @brief Evaluate Predicate. Logs directly on error.
  *
- * [7] Predicate ::= '[' Expr ']'
+ * [9] Predicate ::= '[' Expr ']'
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] exp_idx Position in the expression @p exp.
@@ -6753,7 +6584,7 @@
 
             rc = eval_expr_select(exp, exp_idx, 0, &set2, options);
             if (rc != LY_SUCCESS) {
-                lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+                lyxp_set_free_content(&set2);
                 return rc;
             }
 
@@ -6816,20 +6647,20 @@
         }
 
     } else {
-        set2.type = LYXP_SET_EMPTY;
+        set2.type = LYXP_SET_NODE_SET;
         set_fill_set(&set2, set);
 
         rc = eval_expr_select(exp, exp_idx, 0, &set2, options);
         if (rc != LY_SUCCESS) {
-            lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+            lyxp_set_free_content(&set2);
             return rc;
         }
 
         lyxp_set_cast(&set2, LYXP_SET_BOOLEAN);
         if (!set2.val.bool) {
-            lyxp_set_cast(set, LYXP_SET_EMPTY);
+            lyxp_set_free_content(set);
         }
-        lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+        lyxp_set_free_content(&set2);
     }
 
     /* ']' */
@@ -6842,10 +6673,392 @@
 }
 
 /**
+ * @brief Evaluate Literal. Logs directly on error.
+ *
+ * @param[in] exp Parsed XPath expression.
+ * @param[in] exp_idx Position in the expression @p exp.
+ * @param[in,out] set Context and result set. On NULL the rule is only parsed.
+ */
+static void
+eval_literal(struct lyxp_expr *exp, uint16_t *exp_idx, struct lyxp_set *set)
+{
+    if (set) {
+        if (exp->tok_len[*exp_idx] == 2) {
+            set_fill_string(set, "", 0);
+        } else {
+            set_fill_string(set, &exp->expr[exp->tok_pos[*exp_idx] + 1], exp->tok_len[*exp_idx] - 2);
+        }
+    }
+    LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
+               print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
+    ++(*exp_idx);
+}
+
+/**
+ * @brief Check and parse a predicate following a leaf-list to extract its value.
+ * On success all the used tokens are skipped.
+ *
+ * @param[in] exp Parsed XPath expression.
+ * @param[in] exp_idx Position in the expression @p exp.
+ * @param[out] val Leaf-list value on success.
+ * @param[out] val_len Length of @p val.
+ * @return LY_ERR
+ */
+static LY_ERR
+eval_name_test_parse_leaflist_predicate(struct lyxp_expr *exp, uint16_t *exp_idx, const char **val, uint16_t *val_len)
+{
+    uint16_t cur_idx;
+
+    cur_idx = *exp_idx;
+
+    /* '[' */
+    if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_BRACK1)) {
+        return LY_EINVAL;
+    }
+    ++cur_idx;
+
+    /* '.' */
+    if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_DOT)) {
+        return LY_EINVAL;
+    }
+    ++cur_idx;
+
+    /* '=' */
+    if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_OPERATOR_COMP)
+            || (exp->expr[exp->tok_pos[cur_idx]] != '=')) {
+        return LY_EINVAL;
+    }
+    ++cur_idx;
+
+    /* value */
+    if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_LITERAL)) {
+        return LY_EINVAL;
+    }
+    ++cur_idx;
+
+    /* ']' */
+    if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_BRACK2)) {
+        return LY_EINVAL;
+    }
+    ++cur_idx;
+
+    /* success */
+    *val = &exp->expr[exp->tok_pos[cur_idx - 2] + 1];
+    *val_len = exp->tok_len[cur_idx - 2] - 2;
+    *exp_idx = cur_idx;
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Check and parse a predicate following a list to make sure all the tokens are as expected (all key values defined).
+ * On success all the used tokens are skipped.
+ *
+ * @param[in] exp Parsed XPath expression.
+ * @param[in] exp_idx Position in the expression @p exp.
+ * @param[in] slist Schema node of the list.
+ * @param[out] val Leaf-list value on success.
+ * @param[out] val_len Length of @p val.
+ * @return LY_ERR
+ */
+static LY_ERR
+eval_name_test_parse_list_predicate(struct lyxp_expr *exp, uint16_t *exp_idx, const struct lysc_node *slist,
+                                    const char **keys, uint16_t *keys_len)
+{
+    uint16_t key_count, i, cur_idx;
+    const struct lysc_node *key;
+
+    if (slist->flags & LYS_KEYLESS) {
+        /* invalid */
+        return LY_EINVAL;
+    }
+
+    /* get key count */
+    key_count = 0;
+    for (key = lysc_node_children(slist, 0); key && (key->flags & LYS_KEY); key = key->next) {
+        ++key_count;
+    }
+
+    /* briefly check the predicate for each key */
+    cur_idx = *exp_idx;
+    for (i = 0; i < key_count; ++i) {
+        /* '[' */
+        if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_BRACK1)) {
+            return LY_EINVAL;
+        }
+        ++cur_idx;
+
+        /* key-name */
+        if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_NAMETEST)) {
+            return LY_EINVAL;
+        }
+        ++cur_idx;
+
+        /* '=' */
+        if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_OPERATOR_COMP)
+                || (exp->expr[exp->tok_pos[cur_idx]] != '=')) {
+            return LY_EINVAL;
+        }
+        ++cur_idx;
+
+        /* key-value */
+        if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_LITERAL)) {
+            return LY_EINVAL;
+        }
+        ++cur_idx;
+
+        /* ']' */
+        if ((exp->used == cur_idx) || (exp->tokens[cur_idx] != LYXP_TOKEN_BRACK2)) {
+            return LY_EINVAL;
+        }
+        ++cur_idx;
+    }
+
+    /* success */
+    *keys = &exp->expr[exp->tok_pos[*exp_idx]];
+    *keys_len = (exp->expr + exp->tok_pos[cur_idx - 1] + exp->tok_len[cur_idx - 1]) - *keys;
+    *exp_idx = cur_idx;
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Evaluate NameTest and any following Predicates. Logs directly on error.
+ *
+ * [5] Step ::= '@'? NodeTest Predicate* | '.' | '..'
+ * [6] NodeTest ::= NameTest | NodeType '(' ')'
+ * [7] NameTest ::= '*' | NCName ':' '*' | QName
+ *
+ * @param[in] exp Parsed XPath expression.
+ * @param[in] exp_idx Position in the expression @p exp.
+ * @param[in] attr_axis Whether to search attributes or standard nodes.
+ * @param[in] all_desc Whether to search all the descendants or children only.
+ * @param[in,out] set Context and result set. On NULL the rule is only parsed.
+ * @param[in] options XPath options.
+ * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
+ */
+static LY_ERR
+eval_name_test_with_predicate(struct lyxp_expr *exp, uint16_t *exp_idx, int attr_axis, int all_desc, struct lyxp_set *set,
+                              int options)
+{
+    int i;
+    char *path;
+    const char *ncname = NULL, *key_val = NULL;
+    uint16_t ncname_len, key_val_len, prev_exp_idx;
+    const struct lys_module *moveto_mod;
+    const struct lysc_node *scnode = NULL, *tmp;
+    struct lyd_node *list_inst = NULL;
+    LY_ERR rc = LY_SUCCESS;
+
+    LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
+           print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
+    ++(*exp_idx);
+
+    if (!set) {
+        goto moveto;
+    }
+
+    ncname = &exp->expr[exp->tok_pos[*exp_idx - 1]];
+    ncname_len = exp->tok_len[*exp_idx - 1];
+
+    /* parse (and skip) module name */
+    rc = moveto_resolve_model(&ncname, &ncname_len, set, &moveto_mod);
+    LY_CHECK_GOTO(rc, cleanup);
+
+    if (moveto_mod && !attr_axis && !all_desc && (set->type == LYXP_SET_NODE_SET)) {
+        /* find the matching schema node in some parent in the context */
+        for (i = 0; i < (signed)set->used; ++i) {
+            if (set->val.nodes[i].type == set->root_type) {
+                tmp = lys_find_child(NULL, moveto_mod, ncname, ncname_len, 0, 0);
+            } else if ((set->val.nodes[i].type == LYXP_NODE_ELEM)
+                    && (!scnode || (lysc_data_parent(scnode) != set->val.nodes[i].node->schema))) {
+                /* do not repeat the same search */
+                tmp = lys_find_child(set->val.nodes[i].node->schema, moveto_mod, ncname, ncname_len, 0, 0);
+            }
+
+            /* additional context check */
+            if (tmp && (set->root_type == LYXP_NODE_ROOT_CONFIG) && (tmp->flags & LYS_CONFIG_R)) {
+                tmp = NULL;
+            }
+
+            if (tmp) {
+                if (scnode) {
+                    /* we found a schema node with the same name but at different level, give up, too complicated */
+                    scnode = NULL;
+                    break;
+                } else {
+                    /* remember the found schema node and continue to make sure it can be used */
+                    scnode = tmp;
+                }
+                tmp = NULL;
+            }
+        }
+
+        if (scnode && (scnode->nodetype == LYS_LIST)) {
+            /* make sure all the tokens are right */
+            prev_exp_idx = *exp_idx;
+            if (eval_name_test_parse_list_predicate(exp, exp_idx, scnode, &key_val, &key_val_len)) {
+                scnode = NULL;
+            }
+
+            /* try to create a list instance */
+            if (scnode && lyd_create_list(scnode, key_val, key_val_len, set->format, 0, &list_inst)) {
+                /* for whatever reason the list failed to be created, just use standard name comparison and
+                 * parse predicate normally */
+                *exp_idx = prev_exp_idx;
+                scnode = NULL;
+            }
+        } else if (scnode && (scnode->nodetype == LYS_LEAFLIST)) {
+            /* make sure we know the leaf-list value */
+            if (eval_name_test_parse_leaflist_predicate(exp, exp_idx, &key_val, &key_val_len)) {
+                /* value could not be recognized, use standard name comparison */
+                scnode = NULL;
+            }
+        }
+    }
+
+    if (!scnode) {
+        /* we are not able to match based on a schema node */
+        if (!moveto_mod) {
+            /* '*', all nodes match */
+            ncname = NULL;
+        } else {
+            /* insert name into dictionary for efficient comparison */
+            ncname = lydict_insert(set->ctx, ncname, ncname_len);
+        }
+    }
+
+moveto:
+    /* move to the attribute(s), data node(s), or schema node(s) */
+    if (attr_axis) {
+        if (set && (options & LYXP_SCNODE_ALL)) {
+            set_scnode_clear_ctx(set);
+        } else {
+            if (all_desc) {
+                rc = moveto_attr_alldesc(set, moveto_mod, ncname);
+            } else {
+                rc = moveto_attr(set, moveto_mod, ncname);
+            }
+            LY_CHECK_GOTO(rc, cleanup);
+        }
+    } else {
+        if (set && (options & LYXP_SCNODE_ALL)) {
+            if (all_desc) {
+                rc = moveto_scnode_alldesc(set, moveto_mod, ncname, options);
+            } else {
+                rc = moveto_scnode(set, moveto_mod, ncname, options);
+            }
+            LY_CHECK_GOTO(rc, cleanup);
+
+            for (i = set->used - 1; i > -1; --i) {
+                if (set->val.scnodes[i].in_ctx > 0) {
+                    break;
+                }
+            }
+            if (i == -1) {
+                path = lysc_path(set->ctx_scnode, LYSC_PATH_LOG, NULL, 0);
+                LOGWRN(set->ctx, "Schema node \"%.*s\" not found (%.*s) with context node \"%s\".",
+                       exp->tok_len[*exp_idx], &exp->expr[exp->tok_pos[*exp_idx]],
+                       exp->tok_pos[*exp_idx] + exp->tok_len[*exp_idx], exp->expr, path);
+                free(path);
+            }
+        } else {
+            if (all_desc) {
+                rc = moveto_node_alldesc(set, moveto_mod, ncname);
+            } else {
+                if (scnode) {
+                    /* we can find the nodes using hashes */
+                    rc = moveto_node_hash(set, scnode, list_inst, key_val, key_val_len);
+                } else {
+                    rc = moveto_node(set, moveto_mod, ncname);
+                }
+            }
+            LY_CHECK_GOTO(rc, cleanup);
+        }
+    }
+
+    /* Predicate* */
+    while ((exp->used > *exp_idx) && (exp->tokens[*exp_idx] == LYXP_TOKEN_BRACK1)) {
+        rc = eval_predicate(exp, exp_idx, set, options, 1);
+        LY_CHECK_RET(rc);
+    }
+
+cleanup:
+    lydict_remove(set->ctx, ncname);
+    lyd_free_tree(list_inst);
+    return rc;
+}
+
+/**
+ * @brief Evaluate NodeType and any following Predicates. Logs directly on error.
+ *
+ * [5] Step ::= '@'? NodeTest Predicate* | '.' | '..'
+ * [6] NodeTest ::= NameTest | NodeType '(' ')'
+ * [8] NodeType ::= 'text' | 'node'
+ *
+ * @param[in] exp Parsed XPath expression.
+ * @param[in] exp_idx Position in the expression @p exp.
+ * @param[in] attr_axis Whether to search attributes or standard nodes.
+ * @param[in] all_desc Whether to search all the descendants or children only.
+ * @param[in,out] set Context and result set. On NULL the rule is only parsed.
+ * @param[in] options XPath options.
+ * @return LY_ERR (LY_EINCOMPLETE on unresolved when)
+ */
+static LY_ERR
+eval_node_type_with_predicate(struct lyxp_expr *exp, uint16_t *exp_idx, int attr_axis, int all_desc,
+                              struct lyxp_set *set, int options)
+{
+    LY_ERR rc;
+
+    /* TODO */
+    (void)attr_axis;
+    (void)all_desc;
+
+    if (set) {
+        assert(exp->tok_len[*exp_idx] == 4);
+        if (set->type == LYXP_SET_SCNODE_SET) {
+            set_scnode_clear_ctx(set);
+            /* just for the debug message below */
+            set = NULL;
+        } else {
+            if (!strncmp(&exp->expr[exp->tok_pos[*exp_idx]], "node", 4)) {
+                rc = xpath_node(NULL, 0, set, options);
+            } else {
+                assert(!strncmp(&exp->expr[exp->tok_pos[*exp_idx]], "text", 4));
+                rc = xpath_text(NULL, 0, set, options);
+            }
+            LY_CHECK_RET(rc);
+        }
+    }
+    LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
+           print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
+    ++(*exp_idx);
+
+    /* '(' */
+    assert(exp->tokens[*exp_idx] == LYXP_TOKEN_PAR1);
+    LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
+           print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
+    ++(*exp_idx);
+
+    /* ')' */
+    assert(exp->tokens[*exp_idx] == LYXP_TOKEN_PAR2);
+    LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
+           print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
+    ++(*exp_idx);
+
+    /* Predicate* */
+    while ((exp->used > *exp_idx) && (exp->tokens[*exp_idx] == LYXP_TOKEN_BRACK1)) {
+        rc = eval_predicate(exp, exp_idx, set, options, 1);
+        LY_CHECK_RET(rc);
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
  * @brief Evaluate RelativeLocationPath. Logs directly on error.
  *
  * [4] RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step
  * [5] Step ::= '@'? NodeTest Predicate* | '.' | '..'
+ * [6] NodeTest ::= NameTest | NodeType '(' ')'
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] exp_idx Position in the expression @p exp.
@@ -6874,8 +7087,18 @@
         ++(*exp_idx);
 
 step:
+        /* evaluate abbreviated axis '@'? if any */
+        if (exp->tokens[*exp_idx] == LYXP_TOKEN_AT) {
+            attr_axis = 1;
+
+            LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
+               print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
+            ++(*exp_idx);
+        } else {
+            attr_axis = 0;
+        }
+
         /* Step */
-        attr_axis = 0;
         switch (exp->tokens[*exp_idx]) {
         case LYXP_TOKEN_DOT:
             /* evaluate '.' */
@@ -6903,23 +7126,16 @@
             ++(*exp_idx);
             break;
 
-        case LYXP_TOKEN_AT:
-            /* evaluate '@' */
-            attr_axis = 1;
-            LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"),
-               print_token(exp->tokens[*exp_idx]), exp->tok_pos[*exp_idx]);
-            ++(*exp_idx);
-
-            /* fall through */
         case LYXP_TOKEN_NAMETEST:
-        case LYXP_TOKEN_NODETYPE:
-            rc = eval_node_test(exp, exp_idx, attr_axis, all_desc, set, options);
+            /* evaluate NameTest Predicate* */
+            rc = eval_name_test_with_predicate(exp, exp_idx, attr_axis, all_desc, set, options);
             LY_CHECK_RET(rc);
+            break;
 
-            while ((exp->used > *exp_idx) && (exp->tokens[*exp_idx] == LYXP_TOKEN_BRACK1)) {
-                rc = eval_predicate(exp, exp_idx, set, options, 1);
-                LY_CHECK_RET(rc);
-            }
+        case LYXP_TOKEN_NODETYPE:
+            /* evaluate NodeType Predicate* */
+            rc = eval_node_type_with_predicate(exp, exp_idx, attr_axis, all_desc, set, options);
+            LY_CHECK_RET(rc);
             break;
 
         default:
@@ -6994,7 +7210,7 @@
 /**
  * @brief Evaluate FunctionCall. Logs directly on error.
  *
- * [9] FunctionCall ::= FunctionName '(' ( Expr ( ',' Expr )* )? ')'
+ * [11] FunctionCall ::= FunctionName '(' ( Expr ( ',' Expr )* )? ')'
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] exp_idx Position in the expression @p exp.
@@ -7255,11 +7471,11 @@
 /**
  * @brief Evaluate PathExpr. Logs directly on error.
  *
- * [10] PathExpr ::= LocationPath | PrimaryExpr Predicate*
+ * [12] PathExpr ::= LocationPath | PrimaryExpr Predicate*
  *                 | PrimaryExpr Predicate* '/' RelativeLocationPath
  *                 | PrimaryExpr Predicate* '//' RelativeLocationPath
  * [2] LocationPath ::= RelativeLocationPath | AbsoluteLocationPath
- * [8] PrimaryExpr ::= '(' Expr ')' | Literal | Number | FunctionCall
+ * [10] PrimaryExpr ::= '(' Expr ')' | Literal | Number | FunctionCall
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] exp_idx Position in the expression @p exp.
@@ -7392,7 +7608,7 @@
 /**
  * @brief Evaluate UnionExpr. Logs directly on error.
  *
- * [18] UnionExpr ::= PathExpr | UnionExpr '|' PathExpr
+ * [20] UnionExpr ::= PathExpr | UnionExpr '|' PathExpr
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] exp_idx Position in the expression @p exp.
@@ -7445,15 +7661,15 @@
     }
 
 cleanup:
-    lyxp_set_cast(&orig_set, LYXP_SET_EMPTY);
-    lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+    lyxp_set_free_content(&orig_set);
+    lyxp_set_free_content(&set2);
     return rc;
 }
 
 /**
  * @brief Evaluate UnaryExpr. Logs directly on error.
  *
- * [17] UnaryExpr ::= UnionExpr | '-' UnaryExpr
+ * [19] UnaryExpr ::= UnionExpr | '-' UnaryExpr
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] exp_idx Position in the expression @p exp.
@@ -7498,7 +7714,7 @@
 /**
  * @brief Evaluate MultiplicativeExpr. Logs directly on error.
  *
- * [16] MultiplicativeExpr ::= UnaryExpr
+ * [18] MultiplicativeExpr ::= UnaryExpr
  *                     | MultiplicativeExpr '*' UnaryExpr
  *                     | MultiplicativeExpr 'div' UnaryExpr
  *                     | MultiplicativeExpr 'mod' UnaryExpr
@@ -7559,15 +7775,15 @@
     }
 
 cleanup:
-    lyxp_set_cast(&orig_set, LYXP_SET_EMPTY);
-    lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+    lyxp_set_free_content(&orig_set);
+    lyxp_set_free_content(&set2);
     return rc;
 }
 
 /**
  * @brief Evaluate AdditiveExpr. Logs directly on error.
  *
- * [15] AdditiveExpr ::= MultiplicativeExpr
+ * [17] AdditiveExpr ::= MultiplicativeExpr
  *                     | AdditiveExpr '+' MultiplicativeExpr
  *                     | AdditiveExpr '-' MultiplicativeExpr
  *
@@ -7627,15 +7843,15 @@
     }
 
 cleanup:
-    lyxp_set_cast(&orig_set, LYXP_SET_EMPTY);
-    lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+    lyxp_set_free_content(&orig_set);
+    lyxp_set_free_content(&set2);
     return rc;
 }
 
 /**
  * @brief Evaluate RelationalExpr. Logs directly on error.
  *
- * [14] RelationalExpr ::= AdditiveExpr
+ * [16] RelationalExpr ::= AdditiveExpr
  *                       | RelationalExpr '<' AdditiveExpr
  *                       | RelationalExpr '>' AdditiveExpr
  *                       | RelationalExpr '<=' AdditiveExpr
@@ -7697,15 +7913,15 @@
     }
 
 cleanup:
-    lyxp_set_cast(&orig_set, LYXP_SET_EMPTY);
-    lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+    lyxp_set_free_content(&orig_set);
+    lyxp_set_free_content(&set2);
     return rc;
 }
 
 /**
  * @brief Evaluate EqualityExpr. Logs directly on error.
  *
- * [13] EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr
+ * [15] EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr
  *                     | EqualityExpr '!=' RelationalExpr
  *
  * @param[in] exp Parsed XPath expression.
@@ -7760,38 +7976,21 @@
             lyxp_set_scnode_merge(set, &set2);
             set_scnode_clear_ctx(set);
         } else {
-            /* special handling of evaluations of identityref comparisons, always compare prefixed identites */
-            if ((set->type == LYXP_SET_NODE_SET) && (set->val.nodes[0].node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST))
-                    && (((struct lysc_node_leaf *)set->val.nodes[0].node->schema)->type->basetype == LY_TYPE_IDENT)) {
-                /* left operand is identityref */
-                if ((set2.type == LYXP_SET_STRING) && !strchr(set2.val.str, ':')) {
-                    /* missing prefix in the right operand */
-                    set2.val.str = ly_realloc(set2.val.str, strlen(set->local_mod->name) + 1 + strlen(set2.val.str) + 1);
-                    if (!set2.val.str) {
-                        goto cleanup;
-                    }
-
-                    memmove(set2.val.str + strlen(set->local_mod->name) + 1, set2.val.str, strlen(set2.val.str) + 1);
-                    memcpy(set2.val.str, set->local_mod->name, strlen(set->local_mod->name));
-                    set2.val.str[strlen(set->local_mod->name)] = ':';
-                }
-            }
-
             rc = moveto_op_comp(set, &set2, &exp->expr[exp->tok_pos[this_op]], options);
             LY_CHECK_GOTO(rc, cleanup);
         }
     }
 
 cleanup:
-    lyxp_set_cast(&orig_set, LYXP_SET_EMPTY);
-    lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+    lyxp_set_free_content(&orig_set);
+    lyxp_set_free_content(&set2);
     return rc;
 }
 
 /**
  * @brief Evaluate AndExpr. Logs directly on error.
  *
- * [12] AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr
+ * [14] AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] exp_idx Position in the expression @p exp.
@@ -7853,15 +8052,15 @@
     }
 
 cleanup:
-    lyxp_set_cast(&orig_set, LYXP_SET_EMPTY);
-    lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+    lyxp_set_free_content(&orig_set);
+    lyxp_set_free_content(&set2);
     return rc;
 }
 
 /**
  * @brief Evaluate OrExpr. Logs directly on error.
  *
- * [11] OrExpr ::= AndExpr | OrExpr 'or' AndExpr
+ * [13] OrExpr ::= AndExpr | OrExpr 'or' AndExpr
  *
  * @param[in] exp Parsed XPath expression.
  * @param[in] exp_idx Position in the expression @p exp.
@@ -7925,8 +8124,8 @@
     }
 
 cleanup:
-    lyxp_set_cast(&orig_set, LYXP_SET_EMPTY);
-    lyxp_set_cast(&set2, LYXP_SET_EMPTY);
+    lyxp_set_free_content(&orig_set);
+    lyxp_set_free_content(&set2);
     return rc;
 }
 
@@ -8043,7 +8242,7 @@
     /* prepare set for evaluation */
     exp_idx = 0;
     memset(set, 0, sizeof *set);
-    set->type = LYXP_SET_EMPTY;
+    set->type = LYXP_SET_NODE_SET;
     set_insert_node(set, (struct lyd_node *)ctx_node, 0, ctx_node_type, 0);
     set->ctx = local_mod->ctx;
     set->ctx_node = ctx_node;
@@ -8055,7 +8254,7 @@
     /* evaluate */
     rc = eval_expr_select(exp, &exp_idx, 0, set, options);
     if (rc != LY_SUCCESS) {
-        lyxp_set_cast(set, LYXP_SET_EMPTY);
+        lyxp_set_free_content(set);
     }
 
     return rc;
@@ -8171,16 +8370,15 @@
     }
 
     /* it's not possible to convert anything into a node set */
-    assert((target != LYXP_SET_NODE_SET) && ((set->type != LYXP_SET_SCNODE_SET) || (target == LYXP_SET_EMPTY)));
+    assert(target != LYXP_SET_NODE_SET);
 
     if (set->type == LYXP_SET_SCNODE_SET) {
-        set_free_content(set);
+        lyxp_set_free_content(set);
         return LY_EINVAL;
     }
 
     /* to STRING */
-    if ((target == LYXP_SET_STRING) || ((target == LYXP_SET_NUMBER)
-            && ((set->type == LYXP_SET_NODE_SET) || (set->type == LYXP_SET_EMPTY)))) {
+    if ((target == LYXP_SET_STRING) || ((target == LYXP_SET_NUMBER) && (set->type == LYXP_SET_NODE_SET))) {
         switch (set->type) {
         case LYXP_SET_NUMBER:
             if (isnan(set->val.num)) {
@@ -8223,13 +8421,9 @@
 
             rc = cast_node_set_to_string(set, &str);
             LY_CHECK_RET(rc);
-            set_free_content(set);
+            lyxp_set_free_content(set);
             set->val.str = str;
             break;
-        case LYXP_SET_EMPTY:
-            set->val.str = strdup("");
-            LY_CHECK_ERR_RET(!set->val.str, LOGMEM(set->ctx), LY_EMEM);
-            break;
         default:
             LOGINT_RET(set->ctx);
         }
@@ -8241,7 +8435,7 @@
         switch (set->type) {
         case LYXP_SET_STRING:
             num = cast_string_to_number(set->val.str);
-            set_free_content(set);
+            lyxp_set_free_content(set);
             set->val.num = num;
             break;
         case LYXP_SET_BOOLEAN:
@@ -8269,21 +8463,21 @@
             break;
         case LYXP_SET_STRING:
             if (set->val.str[0]) {
-                set_free_content(set);
+                lyxp_set_free_content(set);
                 set->val.bool = 1;
             } else {
-                set_free_content(set);
+                lyxp_set_free_content(set);
                 set->val.bool = 0;
             }
             break;
         case LYXP_SET_NODE_SET:
-            set_free_content(set);
-
-            assert(set->used);
-            set->val.bool = 1;
-            break;
-        case LYXP_SET_EMPTY:
-            set->val.bool = 0;
+            if (set->used) {
+                lyxp_set_free_content(set);
+                set->val.bool = 1;
+            } else {
+                lyxp_set_free_content(set);
+                set->val.bool = 0;
+            }
             break;
         default:
             LOGINT_RET(set->ctx);
@@ -8291,12 +8485,6 @@
         set->type = LYXP_SET_BOOLEAN;
     }
 
-    /* to EMPTY */
-    if (target == LYXP_SET_EMPTY) {
-        set_free_content(set);
-        set->type = LYXP_SET_EMPTY;
-    }
-
     return LY_SUCCESS;
 }