schema tree FEATURE pre-compile identities

That way they can be referenced even if not
in an implemented module. Added error when
instantiated identity (or a default identityref)
is found in a non-implemented module.
diff --git a/src/plugins_types.c b/src/plugins_types.c
index 3ebcc0b..ed4162d 100644
--- a/src/plugins_types.c
+++ b/src/plugins_types.c
@@ -1265,7 +1265,7 @@
     char *errmsg = NULL;
     const struct lys_module *mod;
     LY_ARRAY_SIZE_TYPE u;
-    struct lysc_ident *ident;
+    struct lysc_ident *ident, *identities;
 
     if (options & LY_TYPE_OPTS_SECOND_CALL) {
         return LY_SUCCESS;
@@ -1292,17 +1292,27 @@
         asprintf(&errmsg, "Invalid identityref \"%.*s\" value - unable to map prefix to YANG schema.", (int)value_len, value);
         goto error;
     }
-    LY_ARRAY_FOR(mod->compiled->identities, u) {
-        ident = &mod->compiled->identities[u]; /* shortcut */
+    if (mod->compiled) {
+        identities = mod->compiled->identities;
+    } else {
+        identities = mod->dis_identities;
+    }
+    LY_ARRAY_FOR(identities, u) {
+        ident = &identities[u]; /* shortcut */
         if (!ly_strncmp(ident->name, id_name, id_len)) {
             /* we have match */
             break;
         }
     }
-    if (u == LY_ARRAY_SIZE(mod->compiled->identities)) {
+    if (u == LY_ARRAY_SIZE(identities)) {
         /* no match */
         asprintf(&errmsg, "Invalid identityref \"%.*s\" value - identity not found.", (int)value_len, value);
         goto error;
+    } else if (!mod->compiled) {
+        /* non-implemented module */
+        asprintf(&errmsg, "Invalid identityref \"%.*s\" value - identity found in non-implemented module \"%s\".",
+                 (int)value_len, value, mod->name);
+        goto error;
     }
 
     /* check that the identity matches some of the type's base identities */
diff --git a/src/tree_schema.c b/src/tree_schema.c
index d63ab06..4323c3f 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -929,8 +929,9 @@
     }
 
     if (!mod->implemented) {
-        /* pre-compile features of the module */
-        LY_CHECK_GOTO(lys_feature_precompile(NULL, ctx, mod, mod->parsed->features, &mod->off_features), error);
+        /* pre-compile features and identities of the module */
+        LY_CHECK_GOTO(lys_feature_precompile(NULL, ctx, mod, mod->parsed->features, &mod->dis_features), error);
+        LY_CHECK_GOTO(lys_identity_precompile(NULL, ctx, mod, mod->parsed->identities, &mod->dis_identities), error);
     }
 
     if (latest) {
@@ -962,8 +963,9 @@
             goto error_ctx;
         }
         if (!mod->implemented) {
-            /* pre-compile features of the submodule */
-            LY_CHECK_GOTO(lys_feature_precompile(NULL, ctx, mod, inc->submodule->features, &mod->off_features), error);
+            /* pre-compile features and identities of the submodule */
+            LY_CHECK_GOTO(lys_feature_precompile(NULL, ctx, mod, inc->submodule->features, &mod->dis_features), error);
+            LY_CHECK_GOTO(lys_identity_precompile(NULL, ctx, mod, inc->submodule->identities, &mod->dis_identities), error);
         }
     }
     mod->parsed->parsing = 0;
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 6bb16ee..a22dd2f 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -1819,12 +1819,16 @@
     struct lysp_module *parsed;      /**< Simply parsed (unresolved) YANG schema tree */
     struct lysc_module *compiled;    /**< Compiled and fully validated YANG schema tree for data parsing.
                                           Available only for implemented modules. */
-    struct lysc_feature *off_features;/**< List of pre-compiled features of the module in non implemented modules ([sized array](@ref sizedarrays)).
+    struct lysc_feature *dis_features;/**< List of pre-compiled features in a non implemented module ([sized array](@ref sizedarrays)).
                                           These features are always disabled and cannot be enabled until the module
-                                          become implemented. The features are present in this form to allow their linkage
+                                          is implemented. The features are present in this form to allow their linkage
                                           from if-feature statements of the compiled schemas and their proper use in case
                                           the module became implemented in future (no matter if implicitly via augment/deviate
                                           or explicitly via ly_ctx_module_implement()). */
+    struct lysc_ident *dis_identities;/**< List of pre-compiled identities in a non-implemented module ([sized array](@ref sizedarrays))
+                                          These identities cannot be instantiated in data (in identityrefs) until
+                                          the module is implemented but can be linked by identities in implemented
+                                          modules. */
     uint8_t implemented;             /**< flag if the module is implemented, not just imported. The module is implemented if
                                           the flag has non-zero value. Specific values are used internally:
                                           1 - implemented module
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index c5e56a5..14dafbc 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -440,7 +440,7 @@
         /* module is implemented so there is already the compiled schema */
         flist = mod->compiled->features;
     } else {
-        flist = mod->off_features;
+        flist = mod->dis_features;
     }
     LY_ARRAY_FOR(flist, u) {
         f = &flist[u];
@@ -948,36 +948,52 @@
     return ret;
 }
 
-/**
- * @brief Compile information from the identity statement
- *
- * The backlinks to the identities derived from this one are supposed to be filled later via lys_compile_identity_bases().
- *
- * @param[in] ctx Compile context.
- * @param[in] ident_p The parsed identity statement structure.
- * @param[in] idents List of so far compiled identities to check the name uniqueness.
- * @param[in,out] ident Prepared (empty) compiled identity structure to fill.
- * @return LY_ERR value.
- */
-static LY_ERR
-lys_compile_identity(struct lysc_ctx *ctx, struct lysp_ident *ident_p, struct lysc_ident *idents, struct lysc_ident *ident)
+LY_ERR
+lys_identity_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lys_module *module,
+                        struct lysp_ident *identities_p, struct lysc_ident **identities)
 {
-    unsigned int u;
+    LY_ARRAY_SIZE_TYPE offset = 0, u, v;
+    struct lysc_ctx context = {0};
     LY_ERR ret = LY_SUCCESS;
 
-    lysc_update_path(ctx, NULL, ident_p->name);
+    assert(ctx_sc || ctx);
 
-    COMPILE_CHECK_UNIQUENESS_ARRAY(ctx, idents, name, ident, "identity", ident_p->name);
-    DUP_STRING(ctx->ctx, ident_p->name, ident->name);
-    DUP_STRING(ctx->ctx, ident_p->dsc, ident->dsc);
-    DUP_STRING(ctx->ctx, ident_p->ref, ident->ref);
-    ident->module = ctx->mod;
-    COMPILE_ARRAY_GOTO(ctx, ident_p->iffeatures, ident->iffeatures, u, lys_compile_iffeature, ret, done);
-    /* backlings (derived) can be added no sooner than when all the identities in the current module are present */
-    COMPILE_EXTS_GOTO(ctx, ident_p->exts, ident->exts, ident, LYEXT_PAR_IDENT, ret, done);
-    ident->flags = ident_p->flags;
+    if (!ctx_sc) {
+        context.ctx = ctx;
+        context.mod = module;
+        context.path_len = 1;
+        context.path[0] = '/';
+        ctx_sc = &context;
+    }
 
-    lysc_update_path(ctx, NULL, NULL);
+    if (!identities_p) {
+        return LY_SUCCESS;
+    }
+    if (*identities) {
+        offset = LY_ARRAY_SIZE(*identities);
+    }
+
+    lysc_update_path(ctx_sc, NULL, "{identity}");
+    LY_ARRAY_CREATE_RET(ctx_sc->ctx, *identities, LY_ARRAY_SIZE(identities_p), LY_EMEM);
+    LY_ARRAY_FOR(identities_p, u) {
+        lysc_update_path(ctx_sc, NULL, identities_p[u].name);
+
+        LY_ARRAY_INCREMENT(*identities);
+        COMPILE_CHECK_UNIQUENESS_ARRAY(ctx_sc, *identities, name, &(*identities)[offset + u], "identity", identities_p[u].name);
+        DUP_STRING(ctx_sc->ctx, identities_p[u].name, (*identities)[offset + u].name);
+        DUP_STRING(ctx_sc->ctx, identities_p[u].dsc, (*identities)[offset + u].dsc);
+        DUP_STRING(ctx_sc->ctx, identities_p[u].ref, (*identities)[offset + u].ref);
+        (*identities)[offset + u].module = ctx_sc->mod;
+        COMPILE_ARRAY_GOTO(ctx_sc, identities_p[u].iffeatures, (*identities)[offset + u].iffeatures, v,
+                           lys_compile_iffeature, ret, done);
+        /* backlinks (derived) can be added no sooner than when all the identities in the current module are present */
+        COMPILE_EXTS_GOTO(ctx_sc, identities_p[u].exts, (*identities)[offset + u].exts, &(*identities)[offset + u],
+                          LYEXT_PAR_IDENT, ret, done);
+        (*identities)[offset + u].flags = identities_p[u].flags;
+
+        lysc_update_path(ctx_sc, NULL, NULL);
+    }
+    lysc_update_path(ctx_sc, NULL, NULL);
 done:
     return ret;
 }
@@ -1044,17 +1060,18 @@
  *
  * @param[in] ctx Compile context, not only for logging but also to get the current module to resolve prefixes.
  * @param[in] bases_p Array of names (including prefix if necessary) of base identities.
- * @param[in] ident Referencing identity to work with.
+ * @param[in] ident Referencing identity to work with, NULL for identityref.
  * @param[in] bases Array of bases of identityref to fill in.
  * @return LY_ERR value.
  */
 static LY_ERR
-lys_compile_identity_bases(struct lysc_ctx *ctx, struct lys_module *context_module, const char **bases_p,  struct lysc_ident *ident, struct lysc_ident ***bases)
+lys_compile_identity_bases(struct lysc_ctx *ctx, struct lys_module *context_module, const char **bases_p,
+                           struct lysc_ident *ident, struct lysc_ident ***bases)
 {
     LY_ARRAY_SIZE_TYPE u, v;
     const char *s, *name;
     struct lys_module *mod;
-    struct lysc_ident **idref;
+    struct lysc_ident **idref, *identities;
 
     assert(ident || bases);
 
@@ -1064,7 +1081,7 @@
         return LY_EVALID;
     }
 
-    for (u = 0; u < LY_ARRAY_SIZE(bases_p); ++u) {
+    LY_ARRAY_FOR(bases_p, u) {
         s = strchr(bases_p[u], ':');
         if (s) {
             /* prefixed identity */
@@ -1084,27 +1101,31 @@
             }
             return LY_EVALID;
         }
+
         idref = NULL;
-        if (mod->compiled && mod->compiled->identities) {
-            for (v = 0; v < LY_ARRAY_SIZE(mod->compiled->identities); ++v) {
-                if (!strcmp(name, mod->compiled->identities[v].name)) {
-                    if (ident) {
-                        if (ident == &mod->compiled->identities[v]) {
-                            LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
-                                   "Identity \"%s\" is derived from itself.", ident->name);
-                            return LY_EVALID;
-                        }
-                        LY_CHECK_RET(lys_compile_identity_circular_check(ctx, &mod->compiled->identities[v], ident->derived));
-                        /* we have match! store the backlink */
-                        LY_ARRAY_NEW_RET(ctx->ctx, mod->compiled->identities[v].derived, idref, LY_EMEM);
-                        *idref = ident;
-                    } else {
-                        /* we have match! store the found identity */
-                        LY_ARRAY_NEW_RET(ctx->ctx, *bases, idref, LY_EMEM);
-                        *idref = &mod->compiled->identities[v];
+        if (mod->compiled) {
+            identities = mod->compiled->identities;
+        } else {
+            identities = mod->dis_identities;
+        }
+        LY_ARRAY_FOR(identities, v) {
+            if (!strcmp(name, identities[v].name)) {
+                if (ident) {
+                    if (ident == &identities[v]) {
+                        LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
+                               "Identity \"%s\" is derived from itself.", ident->name);
+                        return LY_EVALID;
                     }
-                    break;
+                    LY_CHECK_RET(lys_compile_identity_circular_check(ctx, &identities[v], ident->derived));
+                    /* we have match! store the backlink */
+                    LY_ARRAY_NEW_RET(ctx->ctx, identities[v].derived, idref, LY_EMEM);
+                    *idref = ident;
+                } else {
+                    /* we have match! store the found identity */
+                    LY_ARRAY_NEW_RET(ctx->ctx, *bases, idref, LY_EMEM);
+                    *idref = &identities[v];
                 }
+                break;
             }
         }
         if (!idref || !(*idref)) {
@@ -1133,6 +1154,7 @@
 {
     LY_ARRAY_SIZE_TYPE u;
 
+    lysc_update_path(ctx, NULL, "{identity}");
     for (u = 0; u < LY_ARRAY_SIZE(idents_p); ++u) {
         if (!idents_p[u].bases) {
             continue;
@@ -1141,11 +1163,13 @@
         LY_CHECK_RET(lys_compile_identity_bases(ctx, idents[u].module, idents_p[u].bases, &idents[u], NULL));
         lysc_update_path(ctx, NULL, NULL);
     }
+    lysc_update_path(ctx, NULL, NULL);
     return LY_SUCCESS;
 }
 
 LY_ERR
-lys_feature_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lys_module *module, struct lysp_feature *features_p, struct lysc_feature **features)
+lys_feature_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lys_module *module,
+                       struct lysp_feature *features_p, struct lysc_feature **features)
 {
     LY_ARRAY_SIZE_TYPE offset = 0, u;
     struct lysc_ctx context = {0};
@@ -1305,35 +1329,35 @@
  * @brief Revert compiled list of features back to the precompiled state.
  *
  * Function is needed in case the compilation failed and the schema is expected to revert back to the non-compiled status.
- * The features are supposed to be stored again as off_features in ::lys_module structure.
+ * The features are supposed to be stored again as dis_features in ::lys_module structure.
  *
  * @param[in] ctx Compilation context.
  * @param[in] mod The module structure still holding the compiled (but possibly not finished, only the list of compiled features is taken) schema
- * and supposed to hold the off_features list.
+ * and supposed to hold the dis_features list.
  */
 static void
 lys_feature_precompile_revert(struct lysc_ctx *ctx, struct lys_module *mod)
 {
     LY_ARRAY_SIZE_TYPE u, v;
 
-    /* keep the off_features list until the complete lys_module is freed */
-    mod->off_features = mod->compiled->features;
+    /* keep the dis_features list until the complete lys_module is freed */
+    mod->dis_features = mod->compiled->features;
     mod->compiled->features = NULL;
 
-    /* in the off_features list, remove all the parts (from finished compiling process)
+    /* in the dis_features list, remove all the parts (from finished compiling process)
      * which may points into the data being freed here */
-    LY_ARRAY_FOR(mod->off_features, u) {
-        LY_ARRAY_FOR(mod->off_features[u].iffeatures, v) {
-            lysc_iffeature_free(ctx->ctx, &mod->off_features[u].iffeatures[v]);
+    LY_ARRAY_FOR(mod->dis_features, u) {
+        LY_ARRAY_FOR(mod->dis_features[u].iffeatures, v) {
+            lysc_iffeature_free(ctx->ctx, &mod->dis_features[u].iffeatures[v]);
         }
-        LY_ARRAY_FREE(mod->off_features[u].iffeatures);
-        mod->off_features[u].iffeatures = NULL;
+        LY_ARRAY_FREE(mod->dis_features[u].iffeatures);
+        mod->dis_features[u].iffeatures = NULL;
 
-        LY_ARRAY_FOR(mod->off_features[u].exts, v) {
-            lysc_ext_instance_free(ctx->ctx, &(mod->off_features[u].exts)[v]);
+        LY_ARRAY_FOR(mod->dis_features[u].exts, v) {
+            lysc_ext_instance_free(ctx->ctx, &(mod->dis_features[u].exts)[v]);
         }
-        LY_ARRAY_FREE(mod->off_features[u].exts);
-        mod->off_features[u].exts = NULL;
+        LY_ARRAY_FREE(mod->dis_features[u].exts);
+        mod->dis_features[u].exts = NULL;
     }
 }
 
@@ -6469,7 +6493,7 @@
     struct lysc_module *mainmod = ctx->mod->compiled;
     struct lysp_node *node_p;
 
-    if (!mainmod->mod->off_features) {
+    if (!mainmod->mod->dis_features) {
         /* features are compiled directly into the compiled module structure,
          * but it must be done in two steps to allow forward references (via if-feature) between the features themselves.
          * The features compilation is finished in the main module (lys_compile()). */
@@ -6477,9 +6501,10 @@
         LY_CHECK_GOTO(ret, error);
     }
 
-    lysc_update_path(ctx, NULL, "{identity}");
-    COMPILE_ARRAY_UNIQUE_GOTO(ctx, submod->identities, mainmod->identities, u, lys_compile_identity, ret, error);
-    lysc_update_path(ctx, NULL, NULL);
+    if (!mainmod->mod->dis_identities) {
+        ret = lys_identity_precompile(ctx, NULL, NULL, submod->identities, &mainmod->identities);
+        LY_CHECK_GOTO(ret, error);
+    }
 
     /* data nodes */
     LY_LIST_FOR(submod->data, node_p) {
@@ -7078,16 +7103,17 @@
     }
 
     /* features */
-    if ((*mod)->off_features) {
+    if ((*mod)->dis_features) {
         /* there is already precompiled array of features */
-        mod_c->features = (*mod)->off_features;
-        (*mod)->off_features = NULL;
+        mod_c->features = (*mod)->dis_features;
+        (*mod)->dis_features = NULL;
     } else {
         /* features are compiled directly into the compiled module structure,
          * but it must be done in two steps to allow forward references (via if-feature) between the features themselves */
         ret = lys_feature_precompile(&ctx, NULL, NULL, sp->features, &mod_c->features);
         LY_CHECK_GOTO(ret, error);
     }
+
     /* finish feature compilation, not only for the main module, but also for the submodules.
      * Due to possible forward references, it must be done when all the features (including submodules)
      * are present. */
@@ -7106,12 +7132,26 @@
     }
     lysc_update_path(&ctx, NULL, NULL);
 
-    /* identities */
-    lysc_update_path(&ctx, NULL, "{identity}");
-    COMPILE_ARRAY_UNIQUE_GOTO(&ctx, sp->identities, mod_c->identities, u, lys_compile_identity, ret, error);
+    /* identities, work similarly to features with the precompilation */
+    if ((*mod)->dis_identities) {
+        mod_c->identities = (*mod)->dis_identities;
+        (*mod)->dis_identities = NULL;
+    } else {
+        ret = lys_identity_precompile(&ctx, NULL, NULL, sp->identities, &mod_c->identities);
+        LY_CHECK_GOTO(ret, error);
+    }
     if (sp->identities) {
         LY_CHECK_GOTO(ret = lys_compile_identities_derived(&ctx, sp->identities, mod_c->identities), error);
     }
+    lysc_update_path(&ctx, NULL, "{submodule}");
+    LY_ARRAY_FOR(sp->includes, v) {
+        if (sp->includes[v].submodule->identities) {
+            lysc_update_path(&ctx, NULL, sp->includes[v].name);
+            ret = lys_compile_identities_derived(&ctx, sp->includes[v].submodule->identities, mod_c->identities);
+            LY_CHECK_GOTO(ret, error);
+            lysc_update_path(&ctx, NULL, NULL);
+        }
+    }
     lysc_update_path(&ctx, NULL, NULL);
 
     /* data nodes */
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index 58ac788..c931b4a 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -872,7 +872,8 @@
     }
 
     lysc_module_free(module->compiled, private_destructor);
-    FREE_ARRAY(module->ctx, module->off_features, lysc_feature_free);
+    FREE_ARRAY(module->ctx, module->dis_features, lysc_feature_free);
+    FREE_ARRAY(module->ctx, module->dis_identities, lysc_ident_free);
     lysp_module_free(module->parsed);
 
     FREE_STRING(module->ctx, module->name);
@@ -887,6 +888,7 @@
 
     free(module);
 }
+
 API void
 lysc_extension_instance_free(struct ly_ctx *ctx, struct lysc_ext_substmt *substmts)
 {
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index ed07a25..6dba9f2 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -588,6 +588,23 @@
                             struct lys_parser_ctx *main_ctx, const char *main_name, int required, void **result);
 
 /**
+ * @brief Compile information from the identity statement
+ *
+ * The backlinks to the identities derived from this one are supposed to be filled later via lys_compile_identity_bases().
+ *
+ * @param[in] ctx_sc Compile context - alternative to the combination of @p ctx and @p module.
+ * @param[in] ctx libyang context.
+ * @param[in] module Module of the features.
+ * @param[in] identities_p Array of the parsed identity definitions to precompile.
+ * @param[in,out] identities Pointer to the storage of the (pre)compiled identities array where the new identities are
+ * supposed to be added. The storage is supposed to be initiated to NULL when the first parsed identities are going
+ * to be processed.
+ * @return LY_ERR value.
+ */
+LY_ERR lys_identity_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lys_module *module,
+                               struct lysp_ident *identities_p, struct lysc_ident **identities);
+
+/**
  * @brief Create pre-compiled features array.
  *
  * Features are compiled in two steps to allow forward references between them via their if-feature statements.
diff --git a/tests/utests/schema/test_schema_stmts.c b/tests/utests/schema/test_schema_stmts.c
index a588e42..7e93afd 100644
--- a/tests/utests/schema/test_schema_stmts.c
+++ b/tests/utests/schema/test_schema_stmts.c
@@ -127,6 +127,21 @@
     TEST_SCHEMA_ERR(ctx, 0, 0,"inv", "identity i1 {base i2;}identity i2 {base i3;}identity i3 {base i1;}",
                     "Identity \"i1\" is indirectly derived from itself. /inv:{identity='i3'}");
 
+    /* base in non-implemented module */
+    ly_ctx_set_module_imp_clb(ctx, test_imp_clb,
+                              "module base {namespace \"urn\"; prefix b; identity i1; identity i2 {base i1;}}");
+    TEST_SCHEMA_OK(ctx, 0, 0, "ident", "import base {prefix b;} identity ii {base b:i1;}", mod);
+
+    /* default value from non-implemented module */
+    TEST_SCHEMA_ERR(ctx, 0, 0, "ident2", "import base {prefix b;} leaf l {type identityref {base b:i1;} default b:i2;}",
+                    "Invalid leaf's default value \"b:i2\" which does not fit the type (Invalid identityref \"b:i2\" value"
+                    " - identity found in non-implemented module \"base\".). /ident2:l");
+
+    /* default value in typedef from non-implemented module */
+    TEST_SCHEMA_ERR(ctx, 0, 0, "ident2", "import base {prefix b;} typedef t1 {type identityref {base b:i1;} default b:i2;}"
+                    "leaf l {type t1;}", "Invalid type's default value \"b:i2\" which does not fit the type (Invalid"
+                    " identityref \"b:i2\" value - identity found in non-implemented module \"base\".). /ident2:l");
+
     /*
      * printing
      */
diff --git a/tools/lint/commands.c b/tools/lint/commands.c
index a456e47..65200cf 100644
--- a/tools/lint/commands.c
+++ b/tools/lint/commands.c
@@ -1284,7 +1284,7 @@
         if (module->compiled) {
             features = module->compiled->features;
         } else {
-            features = module->off_features;
+            features = module->dis_features;
         }
 
         /* get the max len */