schema compile BUGFIX if-feature in identity

Disabled identities by if-feature(s) are now also present in the lysc
tree. Checking whether the identity is not disabled by if-feature is
performed dynamically using ::lys_identity_iffeature_value(). Also the
compiled YANG printer now prints all identities, but disabled ones are
marked by comment.
diff --git a/src/plugins_types/identityref.c b/src/plugins_types/identityref.c
index 5837aaa..5a49d46 100644
--- a/src/plugins_types/identityref.c
+++ b/src/plugins_types/identityref.c
@@ -131,7 +131,7 @@
 /**
  * @brief Check that an identityref is derived from the type base.
  *
- * @param[in] ident Identityref.
+ * @param[in] ident Derived identity to which identityref points.
  * @param[in] type Identityref type.
  * @param[in] value String value for logging.
  * @param[in] value_len Length of @p value.
@@ -183,6 +183,44 @@
     return LY_SUCCESS;
 }
 
+/**
+ * @brief Check if @p ident is not disabled.
+ *
+ * Identity is disabled if it is located in an unimplemented model or
+ * it can be disabled by if-feature. Calling this function may invoke
+ * the implementation of another module.
+ *
+ * @param[in] ident Derived identity to which identityref points.
+ * @param[in] value Value of identityref.
+ * @param[in] value_len Length (number of bytes) of the given @p value.
+ * @param[in] options [Type plugin store options](@ref plugintypestoreopts).
+ * @param[in,out] unres Global unres structure for newly implemented modules.
+ * @param[out] err Error information on error.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+identityref_check_ident(const struct lysc_ident *ident, const char *value,
+        size_t value_len, uint32_t options, struct lys_glob_unres *unres, struct ly_err_item **err)
+{
+    LY_ERR ret = LY_SUCCESS;
+
+    if (!ident->module->implemented) {
+        if (options & LYPLG_TYPE_STORE_IMPLEMENT) {
+            ret = lyplg_type_make_implemented(ident->module, NULL, unres);
+        } else {
+            ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL,
+                    "Invalid identityref \"%.*s\" value - identity found in non-implemented module \"%s\".",
+                    (int)value_len, (char *)value, ident->module->name);
+        }
+    } else if (lys_identity_iffeature_value(ident) == LY_ENOT) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL,
+                "Invalid identityref \"%.*s\" value - identity is disabled by if-feature.",
+                (int)value_len, value);
+    }
+
+    return ret;
+}
+
 API LY_ERR
 lyplg_type_store_identityref(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len,
         uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node,
@@ -205,18 +243,9 @@
     ret = identityref_str2ident(value, value_len, format, prefix_data, ctx, ctx_node, &ident, err);
     LY_CHECK_GOTO(ret, cleanup);
 
-    /* handle identity in a non-implemented module */
-    if (!ident->module->implemented) {
-        if (options & LYPLG_TYPE_STORE_IMPLEMENT) {
-            ret = lyplg_type_make_implemented(ident->module, NULL, unres);
-            LY_CHECK_GOTO(ret, cleanup);
-        } else {
-            ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL,
-                    "Invalid identityref \"%.*s\" value - identity found in non-implemented module \"%s\".",
-                    (int)value_len, (char *)value, ident->module->name);
-            goto cleanup;
-        }
-    }
+    /* check if the identity is enabled */
+    ret = identityref_check_ident(ident, value, value_len, options, unres, err);
+    LY_CHECK_GOTO(ret, cleanup);
 
     /* check that the identity is derived form all the bases */
     ret = identityref_check_base(ident, type_ident, value, value_len, err);
diff --git a/src/printer_yang.c b/src/printer_yang.c
index b46c957..8510faa 100644
--- a/src/printer_yang.c
+++ b/src/printer_yang.c
@@ -551,8 +551,12 @@
 
     yprc_extension_instances(ctx, LY_STMT_IDENTITY, 0, ident->exts, &flag, 0);
 
+    ypr_open(ctx->out, &flag);
+    if (lys_identity_iffeature_value(ident) == LY_ENOT) {
+        ly_print_(ctx->out, "%*s/* identity \"%s\" is disabled by if-feature(s) */\n", INDENT, ident->name);
+    }
+
     LY_ARRAY_FOR(ident->derived, u) {
-        ypr_open(ctx->out, &flag);
         if (ctx->module != ident->derived[u]->module) {
             ly_print_(ctx->out, "%*sderived %s:%s;\n", INDENT, ident->derived[u]->module->prefix, ident->derived[u]->name);
         } else {
diff --git a/src/schema_compile.c b/src/schema_compile.c
index 1d6872e..8be266d 100644
--- a/src/schema_compile.c
+++ b/src/schema_compile.c
@@ -271,7 +271,6 @@
     struct lysc_ctx context = {0};
     struct lysc_ident *ident;
     LY_ERR ret = LY_SUCCESS;
-    ly_bool enabled;
 
     assert(ctx_sc || ctx);
 
@@ -290,12 +289,6 @@
 
     lysc_update_path(ctx_sc, NULL, "{identity}");
     LY_ARRAY_FOR(identities_p, u) {
-        /* evaluate if-features */
-        LY_CHECK_RET(lys_eval_iffeatures(ctx, identities_p[u].iffeatures, &enabled));
-        if (!enabled) {
-            continue;
-        }
-
         lysc_update_path(ctx_sc, NULL, identities_p[u].name);
 
         /* add new compiled identity */
@@ -371,14 +364,14 @@
 
 LY_ERR
 lys_compile_identity_bases(struct lysc_ctx *ctx, const struct lysp_module *base_pmod, const char **bases_p,
-        struct lysc_ident *ident, struct lysc_ident ***bases, ly_bool *enabled)
+        struct lysc_ident *ident, struct lysc_ident ***bases)
 {
     LY_ARRAY_COUNT_TYPE u, v;
     const char *s, *name;
     const struct lys_module *mod;
     struct lysc_ident **idref;
 
-    assert((ident && enabled) || bases);
+    assert(ident || bases);
 
     if ((LY_ARRAY_COUNT(bases_p) > 1) && (ctx->pmod->version < LYS_VERSION_1_1)) {
         LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG,
@@ -428,18 +421,7 @@
                 break;
             }
         }
-        if (!idref || !(*idref)) {
-            if (ident || (ctx->compile_opts & LYS_COMPILE_DISABLED)) {
-                /* look into the parsed module to check whether the identity is not merely disabled */
-                LY_ARRAY_FOR(mod->parsed->identities, v) {
-                    if (!strcmp(mod->parsed->identities[v].name, name)) {
-                        if (ident) {
-                            *enabled = 0;
-                        }
-                        return LY_SUCCESS;
-                    }
-                }
-            }
+        if (!idref) {
             if (ident) {
                 LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG,
                         "Unable to find base (%s) of identity \"%s\".", bases_p[u], ident->name);
@@ -451,9 +433,6 @@
         }
     }
 
-    if (ident) {
-        *enabled = 1;
-    }
     return LY_SUCCESS;
 }
 
@@ -461,21 +440,18 @@
  * @brief For the given array of identities, set the backlinks from all their base identities.
  * @param[in] ctx Compile context, not only for logging but also to get the current module to resolve prefixes.
  * @param[in] idents_p Array of identities definitions from the parsed schema structure.
- * @param[in,out] idents Array of referencing identities to which the backlinks are supposed to be set. Any
- * identities with disabled bases are removed.
+ * @param[in,out] idents Array of referencing identities to which the backlinks are supposed to be set.
  * @return LY_ERR value - LY_SUCCESS or LY_EVALID.
  */
 static LY_ERR
 lys_compile_identities_derived(struct lysc_ctx *ctx, struct lysp_ident *idents_p, struct lysc_ident **idents)
 {
     LY_ARRAY_COUNT_TYPE u, v;
-    ly_bool enabled;
 
     lysc_update_path(ctx, NULL, "{identity}");
 
-restart:
     for (u = 0, v = 0; u < LY_ARRAY_COUNT(*idents); ++u) {
-        /* find matching parsed identity, the disabled ones are missing in the compiled array */
+        /* find matching parsed identity */
         while (v < LY_ARRAY_COUNT(idents_p)) {
             if (idents_p[v].name == (*idents)[u].name) {
                 break;
@@ -489,32 +465,8 @@
         }
 
         lysc_update_path(ctx, NULL, (*idents)[u].name);
-        LY_CHECK_RET(lys_compile_identity_bases(ctx, ctx->pmod, idents_p[v].bases, &(*idents)[u], NULL, &enabled));
+        LY_CHECK_RET(lys_compile_identity_bases(ctx, ctx->pmod, idents_p[v].bases, &(*idents)[u], NULL));
         lysc_update_path(ctx, NULL, NULL);
-
-        if (!enabled) {
-            /* remove the identity */
-            lysc_ident_free(ctx->ctx, &(*idents)[u]);
-            LY_ARRAY_DECREMENT(*idents);
-            if (u < LY_ARRAY_COUNT(*idents)) {
-                memmove(&(*idents)[u], &(*idents)[u + 1], (LY_ARRAY_COUNT(*idents) - u) * sizeof **idents);
-            }
-
-            /* revert compilation of all the previous identities */
-            for (v = 0; v < u; ++v) {
-                LY_ARRAY_FREE((*idents)[v].derived);
-                (*idents)[v].derived = NULL;
-            }
-
-            /* free the whole array if there are no identites left */
-            if (!LY_ARRAY_COUNT(*idents)) {
-                LY_ARRAY_FREE(*idents);
-                *idents = NULL;
-            }
-
-            /* restart the whole process without this identity */
-            goto restart;
-        }
     }
 
     lysc_update_path(ctx, NULL, NULL);
diff --git a/src/schema_compile.h b/src/schema_compile.h
index 5d68677..1df79e1 100644
--- a/src/schema_compile.h
+++ b/src/schema_compile.h
@@ -186,11 +186,10 @@
  * @param[in] bases_p Array of names (including prefix if necessary) of base identities.
  * @param[in] ident Referencing identity to work with, NULL for identityref.
  * @param[in] bases Array of bases of identityref to fill in.
- * @param[in] enabled Whether the base is disabled, must be set if @p ident is set.
  * @return LY_ERR value.
  */
 LY_ERR lys_compile_identity_bases(struct lysc_ctx *ctx, const struct lysp_module *base_pmod, const char **bases_p,
-        struct lysc_ident *ident, struct lysc_ident ***bases, ly_bool *enabled);
+        struct lysc_ident *ident, struct lysc_ident ***bases);
 
 /**
  * @brief Perform a complet compilation of identites in a module and all its submodules.
diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c
index 6529a0e..0fe504d 100644
--- a/src/schema_compile_node.c
+++ b/src/schema_compile_node.c
@@ -1636,7 +1636,7 @@
                 }
                 return LY_EVALID;
             }
-            LY_CHECK_RET(lys_compile_identity_bases(ctx, type_p->pmod, type_p->bases, NULL, &idref->bases, NULL));
+            LY_CHECK_RET(lys_compile_identity_bases(ctx, type_p->pmod, type_p->bases, NULL, &idref->bases));
         }
 
         if (!base && !type_p->flags) {
diff --git a/src/schema_features.c b/src/schema_features.c
index e54fc7b..29e6478 100644
--- a/src/schema_features.c
+++ b/src/schema_features.c
@@ -97,6 +97,31 @@
     return LY_ENOT;
 }
 
+API LY_ERR
+lys_identity_iffeature_value(const struct lysc_ident *ident)
+{
+    LY_ARRAY_COUNT_TYPE u;
+    ly_bool enabled;
+    const struct lysp_ident *idents_p;
+
+    assert(ident);
+
+    idents_p = ident->module->parsed->identities;
+    LY_ARRAY_FOR(idents_p, u) {
+        if (idents_p[u].name == ident->name) {
+            break;
+        }
+    }
+    assert(u != LY_ARRAY_COUNT(idents_p));
+
+    LY_CHECK_RET(lys_eval_iffeatures(ident->module->ctx, idents_p[u].iffeatures, &enabled));
+    if (!enabled) {
+        return LY_ENOT;
+    }
+
+    return LY_SUCCESS;
+}
+
 API struct lysp_feature *
 lysp_feature_next(const struct lysp_feature *last, const struct lysp_module *pmod, uint32_t *idx)
 {
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 792b285..be95005 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -2154,6 +2154,19 @@
 LY_ERR lysc_iffeature_value(const struct lysc_iffeature *iff);
 
 /**
+ * @brief Get how the if-feature statement is evaluated for certain identity.
+ *
+ * The function can be called even if the identity does not contain
+ * if-features, in which case ::LY_SUCCESS is returned.
+ *
+ * @param[in] ident Compiled identity statement to evaluate.
+ * @return LY_SUCCESS if the statement evaluates to true,
+ * @return LY_ENOT if it evaluates to false,
+ * @return LY_ERR on error.
+ */
+LY_ERR lys_identity_iffeature_value(const struct lysc_ident *ident);
+
+/**
  * @brief Get the next feature in the module or submodules.
  *
  * @param[in] last Last returned feature.
diff --git a/tests/utests/schema/test_printer_yang.c b/tests/utests/schema/test_printer_yang.c
index 293404c..d40df7f 100644
--- a/tests/utests/schema/test_printer_yang.c
+++ b/tests/utests/schema/test_printer_yang.c
@@ -134,6 +134,19 @@
     compiled = "module c {\n"
             "  namespace \"urn:test:c\";\n"
             "  prefix c;\n"
+            "\n"
+            "  identity i1 {\n"
+            "    /* identity \"i1\" is disabled by if-feature(s) */\n"
+            "    derived i2;\n"
+            "    description\n"
+            "      \"text\";\n"
+            "    reference\n"
+            "      \"text32\";\n"
+            "  }\n"
+            "\n"
+            "  identity i2 {\n"
+            "    status obsolete;\n"
+            "  }\n"
             "}\n";
     UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod);
     assert_int_equal(LY_SUCCESS, lys_print_module(out, mod, LYS_OUT_YANG, 0, 0));
diff --git a/tests/utests/schema/test_tree_schema_compile.c b/tests/utests/schema/test_tree_schema_compile.c
index cb71297..b89b058 100644
--- a/tests/utests/schema/test_tree_schema_compile.c
+++ b/tests/utests/schema/test_tree_schema_compile.c
@@ -1476,6 +1476,7 @@
 test_identity(void **state)
 {
     char *str;
+    const char *feats[2] = {NULL, NULL};
     struct lyd_node *tree;
     const char *data;
 
@@ -1505,6 +1506,7 @@
     assert_true(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id1"));
     data = "<lf xmlns=\"urn:b\" xmlns:ids=\"urn:a\">ids:id1</lf>";
     CHECK_PARSE_LYD_PARAM(data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_EVALID, tree);
+    CHECK_LOG("Invalid identityref \"ids:id1\" value - identity found in non-implemented module \"a\".", "Schema location /b:lf, line number 1.");
     assert_non_null(ly_ctx_get_module(UTEST_LYCTX, "a", NULL));
     assert_false(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id3"));
     data = "<lf xmlns=\"urn:b\" xmlns:ids=\"urn:a\">ids:id3</lf>";
@@ -1532,11 +1534,11 @@
     assert_true(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id1"));
     data = "<lf xmlns=\"urn:b\" xmlns:ids=\"urn:a\">ids:id1</lf>";
     CHECK_PARSE_LYD_PARAM(data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_EVALID, tree);
-    lyd_free_tree(tree);
+    CHECK_LOG("Invalid identityref \"ids:id1\" value - identity found in non-implemented module \"a\".", "Schema location /b:lf, line number 1.");
     assert_true(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id3"));
     data = "<lf xmlns=\"urn:b\" xmlns:ids=\"urn:c\">ids:id3</lf>";
     CHECK_PARSE_LYD_PARAM(data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_EVALID, tree);
-    lyd_free_tree(tree);
+    CHECK_LOG("Invalid identityref \"ids:id3\" value - identity found in non-implemented module \"c\".", "Schema location /b:lf, line number 1.");
     RESET_CTX(UTEST_LYCTX);
 
     /* Unimplemented module expand base identity located in implemented module. */
@@ -1560,7 +1562,7 @@
     assert_true(contains_derived_identity(UTEST_LYCTX, "b", NULL, "baseid", "id1"));
     data = "<lf xmlns=\"urn:b\" xmlns:ids=\"urn:a\">ids:id1</lf>";
     CHECK_PARSE_LYD_PARAM(data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_EVALID, tree);
-    lyd_free_tree(tree);
+    CHECK_LOG("Invalid identityref \"ids:id1\" value - identity found in non-implemented module \"a\".", "Schema location /b:lf, line number 1.");
     RESET_CTX(UTEST_LYCTX);
 
     /* Transitivity of derived identity through unimplemented module. */
@@ -1676,6 +1678,75 @@
     assert_false(contains_derived_identity(UTEST_LYCTX, "a", "2015-05-08", "baseid", "baseref"));
     RESET_CTX(UTEST_LYCTX);
 
+    /* Identity testing with if-features. */
+
+    /* The if-feature has no effect if the module is imported. */
+    str = "module a {yang-version 1.1; namespace urn:a; prefix a;"
+            "feature f;"
+            "identity baseid { if-feature \"f\";}"
+            "}";
+    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, str);
+    str = "module b {namespace urn:b; prefix b; import a { prefix a;}"
+            "identity id1 { base a:baseid;}"
+            "leaf lf { type identityref { base a:baseid;}}"
+            "}";
+    UTEST_ADD_MODULE(str, LYS_IN_YANG, NULL, NULL);
+    assert_true(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id1"));
+    data = "<lf xmlns=\"urn:b\">id1</lf>";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+    lyd_free_tree(tree);
+    RESET_CTX(UTEST_LYCTX);
+
+    /* Even if the identity in the implemented module is disabled,
+     * it can be used as a base.
+     */
+    str = "module a {yang-version 1.1; namespace urn:a; prefix a;"
+            "feature f;"
+            "identity baseid { if-feature \"f\";}"
+            "}";
+    UTEST_ADD_MODULE(str, LYS_IN_YANG, NULL, NULL);
+    str = "module b {namespace urn:b; prefix b; import a { prefix a;}"
+            "identity id1 { base a:baseid;}"
+            "leaf lf { type identityref { base a:baseid;}}"
+            "}";
+    UTEST_ADD_MODULE(str, LYS_IN_YANG, NULL, NULL);
+    assert_true(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id1"));
+    data = "<lf xmlns=\"urn:b\">id1</lf>";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+    lyd_free_tree(tree);
+    RESET_CTX(UTEST_LYCTX);
+
+    /* Identity derivation cannot be instantiated if it is disabled.
+     * Conversely, if the identity is enabled, it can be instantiated.
+     */
+    str = "module a {namespace urn:a; prefix a;"
+            "identity baseid;"
+            "}";
+    UTEST_ADD_MODULE(str, LYS_IN_YANG, NULL, NULL);
+    str = "module b {yang-version 1.1; namespace urn:b; prefix b; import a { prefix a;}"
+            "feature f2;"
+            "feature f3;"
+            "identity id1 { base a:baseid;}"
+            "identity id2 { if-feature \"f2\"; base a:baseid;}"
+            "identity id3 { if-feature \"f3\"; base a:baseid;}"
+            "leaf lf { type identityref { base a:baseid;}}"
+            "}";
+    feats[0] = "f2";
+    UTEST_ADD_MODULE(str, LYS_IN_YANG, feats, NULL);
+    assert_true(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id1"));
+    data = "<lf xmlns=\"urn:b\">id1</lf>";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+    lyd_free_tree(tree);
+    assert_true(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id2"));
+    data = "<lf xmlns=\"urn:b\">id2</lf>";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+    lyd_free_tree(tree);
+    assert_true(contains_derived_identity(UTEST_LYCTX, "a", NULL, "baseid", "id3"));
+    data = "<lf xmlns=\"urn:b\">id3</lf>";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_EVALID, tree);
+    CHECK_LOG_CTX("Invalid identityref \"id3\" value - identity is disabled by if-feature.", "Schema location /b:lf, line number 1.");
+    RESET_CTX(UTEST_LYCTX);
+
 #undef RESET_CTX
 }