schema compile CHANGE better handling of features

- allow forward reference in feature's if-feature statements
- handling features of not implemented modules - all such features
are permanently disabled, but the structures must be available for
the implemented (compiled) modules which import their module.
diff --git a/src/context.c b/src/context.c
index d95b7b6..ad3abb2 100644
--- a/src/context.c
+++ b/src/context.c
@@ -289,10 +289,10 @@
  * module in the context.
  * @return Module matching the given key, NULL if no such module found.
  */
-static const struct lys_module *
+static struct lys_module *
 ly_ctx_get_module_by_iter(const struct ly_ctx *ctx, const char *key, size_t key_offset, unsigned int *index)
 {
-    const struct lys_module *mod;
+    struct lys_module *mod;
     const char *value;
 
     for (; *index < ctx->list.count; ++(*index)) {
@@ -317,21 +317,20 @@
  * revision module, use ly_ctx_get_module_latest_by().
  * @return Matching module if any.
  */
-static const struct lys_module *
+static struct lys_module *
 ly_ctx_get_module_by(const struct ly_ctx *ctx, const char *key, size_t key_offset, const char *revision)
 {
-    const struct lys_module *mod;
+    struct lys_module *mod;
     unsigned int index = 0;
 
     while ((mod = ly_ctx_get_module_by_iter(ctx, key, key_offset, &index))) {
         if (!revision) {
-            if ((mod->compiled && !mod->compiled->revision) || (!mod->compiled && !mod->parsed->revs)) {
+            if (!mod->revision) {
                 /* found requested module without revision */
                 return mod;
             }
         } else {
-            if ((mod->compiled && mod->compiled->revision && !strcmp(mod->compiled->revision, revision)) ||
-                    (!mod->compiled && mod->parsed->revs && !strcmp(mod->parsed->revs[0].date, revision))) {
+            if (mod->revision && !strcmp(mod->revision, revision)) {
                 /* found requested module of the specific revision */
                 return mod;
             }
@@ -341,14 +340,14 @@
     return NULL;
 }
 
-API const struct lys_module *
+API struct lys_module *
 ly_ctx_get_module_ns(const struct ly_ctx *ctx, const char *ns, const char *revision)
 {
     LY_CHECK_ARG_RET(ctx, ctx, ns, NULL);
     return ly_ctx_get_module_by(ctx, ns, offsetof(struct lys_module, ns), revision);
 }
 
-API const struct lys_module *
+API struct lys_module *
 ly_ctx_get_module(const struct ly_ctx *ctx, const char *name, const char *revision)
 {
     LY_CHECK_ARG_RET(ctx, ctx, name, NULL);
@@ -362,10 +361,10 @@
  * @param[in] key_offset Key's offset in struct lys_module to get value from the context's modules to match with the key.
  * @return Matching module if any.
  */
-static const struct lys_module *
+static struct lys_module *
 ly_ctx_get_module_latest_by(const struct ly_ctx *ctx, const char *key, size_t key_offset)
 {
-    const struct lys_module *mod;
+    struct lys_module *mod;
     unsigned int index = 0;
 
     while ((mod = ly_ctx_get_module_by_iter(ctx, key, key_offset, &index))) {
@@ -377,14 +376,14 @@
     return NULL;
 }
 
-API const struct lys_module *
+API struct lys_module *
 ly_ctx_get_module_latest(const struct ly_ctx *ctx, const char *name)
 {
     LY_CHECK_ARG_RET(ctx, ctx, name, NULL);
     return ly_ctx_get_module_latest_by(ctx, name, offsetof(struct lys_module, name));
 }
 
-const struct lys_module *
+API struct lys_module *
 ly_ctx_get_module_latest_ns(const struct ly_ctx *ctx, const char *ns)
 {
     LY_CHECK_ARG_RET(ctx, ctx, ns, NULL);
@@ -398,10 +397,10 @@
  * @param[in] key_offset Key's offset in struct lys_module to get value from the context's modules to match with the key.
  * @return Matching module if any.
  */
-static const struct lys_module *
+static struct lys_module *
 ly_ctx_get_module_implemented_by(const struct ly_ctx *ctx, const char *key, size_t key_offset)
 {
-    const struct lys_module *mod;
+    struct lys_module *mod;
     unsigned int index = 0;
 
     while ((mod = ly_ctx_get_module_by_iter(ctx, key, key_offset, &index))) {
@@ -413,14 +412,14 @@
     return NULL;
 }
 
-API const struct lys_module *
+API struct lys_module *
 ly_ctx_get_module_implemented(const struct ly_ctx *ctx, const char *name)
 {
     LY_CHECK_ARG_RET(ctx, ctx, name, NULL);
     return ly_ctx_get_module_implemented_by(ctx, name, offsetof(struct lys_module, name));
 }
 
-API const struct lys_module *
+API struct lys_module *
 ly_ctx_get_module_implemented_ns(const struct ly_ctx *ctx, const char *ns)
 {
     LY_CHECK_ARG_RET(ctx, ctx, ns, NULL);
@@ -485,6 +484,40 @@
     }
 }
 
+API LY_ERR
+ly_ctx_module_implement(struct ly_ctx *ctx, struct lys_module *mod)
+{
+    struct lys_module *m;
+
+    LY_CHECK_ARG_RET(ctx, mod, LY_EINVAL);
+
+    if (mod->implemented) {
+        return LY_SUCCESS;
+    }
+
+    /* we have module from the current context */
+    m = ly_ctx_get_module_implemented(ctx, mod->name);
+    if (m) {
+        if (m != mod) {
+            /* check collision with other implemented revision */
+            LOGERR(ctx, LY_EDENIED, "Module \"%s\" is present in the context in other implemented revision (%s).",
+                   mod->name, mod->revision ? mod->revision : "module without revision");
+            return LY_EDENIED;
+        } else {
+            /* mod is already implemented */
+            return LY_SUCCESS;
+        }
+    }
+
+    /* mark the module implemented, check for collision was already done */
+    mod->implemented = 1;
+
+    /* compile the schema */
+    LY_CHECK_RET(lys_compile(mod, 0));
+
+    return LY_SUCCESS;
+}
+
 API void
 ly_ctx_destroy(struct ly_ctx *ctx, void (*private_destructor)(const struct lysc_node *node, void *priv))
 {
diff --git a/src/context.h b/src/context.h
index 8bed61b..e920cb0 100644
--- a/src/context.h
+++ b/src/context.h
@@ -63,6 +63,7 @@
                                         directory, which is by default searched automatically (despite not
                                         recursively). */
 #define LY_CTX_PREFER_SEARCHDIRS 0x20 /**< When searching for schema, prefer searchdirs instead of user callback. */
+
 /**@} contextoptions */
 
 /**
@@ -206,7 +207,7 @@
  * the schema with no revision is returned, if it is present in the context.
  * @return Pointer to the YANG module, NULL if no schema in the context follows the name and revision requirements.
  */
-const struct lys_module *ly_ctx_get_module(const struct ly_ctx *ctx, const char *name, const char *revision);
+struct lys_module *ly_ctx_get_module(const struct ly_ctx *ctx, const char *name, const char *revision);
 
 /**
  * @brief Get the latest revision of the YANG module specified by its name.
@@ -218,7 +219,7 @@
  * @return The latest revision of the specified YANG module in the given context, NULL if no YANG module of the
  * given name is present in the context.
  */
-const struct lys_module *ly_ctx_get_module_latest(const struct ly_ctx *ctx, const char *name);
+struct lys_module *ly_ctx_get_module_latest(const struct ly_ctx *ctx, const char *name);
 
 /**
  * @brief Get the (only) implemented YANG module specified by its name.
@@ -228,7 +229,7 @@
  * @return The only implemented YANG module revision of the given name in the given context. NULL if there is no
  * implemented module of the given name.
  */
-const struct lys_module *ly_ctx_get_module_implemented(const struct ly_ctx *ctx, const char *name);
+struct lys_module *ly_ctx_get_module_implemented(const struct ly_ctx *ctx, const char *name);
 
 /**
  * @brief Get YANG module of the given namespace and revision.
@@ -239,7 +240,7 @@
  * the schema with no revision is returned, if it is present in the context.
  * @return Pointer to the YANG module, NULL if no schema in the context follows the namespace and revision requirements.
  */
-const struct lys_module *ly_ctx_get_module_ns(const struct ly_ctx *ctx, const char *ns, const char *revision);
+struct lys_module *ly_ctx_get_module_ns(const struct ly_ctx *ctx, const char *ns, const char *revision);
 
 /**
  * @brief Get the latest revision of the YANG module specified by its namespace.
@@ -251,7 +252,7 @@
  * @return The latest revision of the specified YANG module in the given context, NULL if no YANG module of the
  * given namespace is present in the context.
  */
-const struct lys_module *ly_ctx_get_module_latest_ns(const struct ly_ctx *ctx, const char *ns);
+struct lys_module *ly_ctx_get_module_latest_ns(const struct ly_ctx *ctx, const char *ns);
 
 /**
  * @brief Get the (only) implemented YANG module specified by its namespace.
@@ -261,7 +262,7 @@
  * @return The only implemented YANG module revision of the given namespace in the given context. NULL if there is no
  * implemented module of the given namespace.
  */
-const struct lys_module *ly_ctx_get_module_implemented_ns(const struct ly_ctx *ctx, const char *ns);
+struct lys_module *ly_ctx_get_module_implemented_ns(const struct ly_ctx *ctx, const char *ns);
 
 /**
  * @brief Reset cached latest revision information of the schemas in the context.
@@ -281,6 +282,17 @@
 void ly_ctx_reset_latests(struct ly_ctx *ctx);
 
 /**
+ * @brief Make the specific module implemented.
+ *
+ * @param[in] ctx libyang context to change.
+ * @param[in] mod Module from the given context to make implemented. It is not an error
+ * to provide already implemented module, it just does nothing.
+ * @return LY_SUCCESS or LY_EDENIED in case the context contains some other revision of the
+ * same module which is already implemented.
+ */
+LY_ERR ly_ctx_module_implement(struct ly_ctx *ctx, struct lys_module *mod);
+
+/**
  * @brief Free all internal structures of the specified context.
  *
  * The function should be used before terminating the application to destroy
diff --git a/src/tree_schema.c b/src/tree_schema.c
index c3a4d2d..6fba4d6 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -241,24 +241,29 @@
  * if-feature statements are again evaluated (disabled if a if-feature statemen
  * evaluates to false).
  *
- * @param[in] mod Compiled module where to set (search for) the feature.
+ * @param[in] mod Module where to set (search for) the feature.
  * @param[in] name Name of the feature to set. Asterisk ('*') can be used to
  * set all the features in the module.
  * @param[in] value Desired value of the feature: 1 (enable) or 0 (disable).
  * @return LY_ERR value.
  */
 static LY_ERR
-lys_feature_change(const struct lysc_module *mod, const char *name, int value)
+lys_feature_change(const struct lys_module *mod, const char *name, int value)
 {
     int all = 0;
     unsigned int u, changed_count, disabled_count;
     struct lysc_feature *f, **df;
     struct lysc_iffeature *iff;
     struct ly_set *changed;
-    struct ly_ctx *ctx = mod->mod->ctx; /* shortcut */
+    struct ly_ctx *ctx = mod->ctx; /* shortcut */
 
-    if (!mod->features) {
-        LOGERR(ctx, LY_EINVAL, "Unable to switch feature since the module \"%s\" has no features.", mod->mod->name);
+    if (!mod->compiled) {
+        LOGERR(ctx, LY_EINVAL, "Module \"%s\" is not implemented so all its features are permanently disabled without a chance to change it.",
+               mod->name);
+        return LY_EINVAL;
+    }
+    if (!mod->compiled->features) {
+        LOGERR(ctx, LY_EINVAL, "Unable to switch feature since the module \"%s\" has no features.", mod->name);
         return LY_EINVAL;
     }
 
@@ -270,8 +275,8 @@
     changed_count = 0;
 
 run:
-    for (disabled_count = u = 0; u < LY_ARRAY_SIZE(mod->features); ++u) {
-        f = &mod->features[u];
+    for (disabled_count = u = 0; u < LY_ARRAY_SIZE(mod->compiled->features); ++u) {
+        f = &mod->compiled->features[u];
         if (all || !strcmp(f->name, name)) {
             if ((value && (f->flags & LYS_FENABLED)) || (!value && !(f->flags & LYS_FENABLED))) {
                 if (all) {
@@ -320,7 +325,7 @@
     }
 
     if (!all && !changed->count) {
-        LOGERR(ctx, LY_EINVAL, "Feature \"%s\" not found in module \"%s\".", name, mod->mod->name);
+        LOGERR(ctx, LY_EINVAL, "Feature \"%s\" not found in module \"%s\".", name, mod->name);
         ly_set_free(changed, NULL);
         return LY_EINVAL;
     }
@@ -329,11 +334,11 @@
         if (changed_count == changed->count) {
             /* no change in last run -> not able to enable all ... */
             /* ... print errors */
-            for (u = 0; disabled_count && u < LY_ARRAY_SIZE(mod->features); ++u) {
-                if (!(mod->features[u].flags & LYS_FENABLED)) {
+            for (u = 0; disabled_count && u < LY_ARRAY_SIZE(mod->compiled->features); ++u) {
+                if (!(mod->compiled->features[u].flags & LYS_FENABLED)) {
                     LOGERR(ctx, LY_EDENIED,
                            "Feature \"%s\" cannot be enabled since it is disabled by its if-feature condition(s).",
-                           mod->features[u].name);
+                           mod->compiled->features[u].name);
                     --disabled_count;
                 }
             }
@@ -384,17 +389,17 @@
 API LY_ERR
 lys_feature_enable(struct lys_module *module, const char *feature)
 {
-    LY_CHECK_ARG_RET(NULL, module, module->compiled, feature, LY_EINVAL);
+    LY_CHECK_ARG_RET(NULL, module, feature, LY_EINVAL);
 
-    return lys_feature_change(module->compiled, feature, 1);
+    return lys_feature_change(module, feature, 1);
 }
 
 API LY_ERR
 lys_feature_disable(struct lys_module *module, const char *feature)
 {
-    LY_CHECK_ARG_RET(NULL, module, module->compiled, feature, LY_EINVAL);
+    LY_CHECK_ARG_RET(NULL, module, feature, LY_EINVAL);
 
-    return lys_feature_change(module->compiled, feature, 0);
+    return lys_feature_change(module, feature, 0);
 }
 
 API int
@@ -560,6 +565,9 @@
 
     /* make sure that the newest revision is at position 0 */
     lysp_sort_revisions(mod->parsed->revs);
+    if (mod->parsed->revs) {
+        mod->revision = lydict_insert(ctx, mod->parsed->revs[0].date, 0);
+    }
 
     if (custom_check) {
         LY_CHECK_GOTO(custom_check(ctx, mod->parsed, NULL, check_data), error);
@@ -575,7 +583,7 @@
     }
 
     /* check for duplicity in the context */
-    mod_dup = (struct lys_module*)ly_ctx_get_module(ctx, mod->name, mod->parsed->revs ? mod->parsed->revs[0].date : NULL);
+    mod_dup = (struct lys_module*)ly_ctx_get_module(ctx, mod->name, mod->revision);
     if (mod_dup) {
         if (mod_dup->parsed) {
             /* error */
@@ -612,15 +620,20 @@
     }
 #endif
 
+    if (!mod->implemented) {
+        /* pre-compile features of the module */
+        LY_CHECK_GOTO(lys_feature_precompile(ctx, mod->parsed->features, &mod->off_features), error);
+    }
+
     /* decide the latest revision */
     latest = (struct lys_module*)ly_ctx_get_module_latest(ctx, mod->name);
     if (latest) {
-        if (mod->parsed->revs) {
-            if ((latest->parsed && !latest->parsed->revs) || (!latest->parsed && !latest->compiled->revision)) {
+        if (mod->revision) {
+            if (!latest->revision) {
                 /* latest has no revision, so mod is anyway newer */
                 mod->latest_revision = latest->latest_revision;
                 latest->latest_revision = 0;
-            } else if (strcmp(mod->parsed->revs[0].date, latest->parsed ? latest->parsed->revs[0].date : latest->compiled->revision) > 0) {
+            } else if (strcmp(mod->revision, latest->revision) > 0) {
                 mod->latest_revision = latest->latest_revision;
                 latest->latest_revision = 0;
             }
@@ -653,6 +666,10 @@
         if (!inc->submodule && lysp_load_submodule(&context, mod->parsed, inc)) {
             goto error_ctx;
         }
+        if (!mod->implemented) {
+            /* pre-compile features of the module */
+            LY_CHECK_GOTO(lys_feature_precompile(ctx, inc->submodule->features, &mod->off_features), error);
+        }
     }
     mod->parsed->parsing = 0;
 
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 3301d74..bcf92d4 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -1394,7 +1394,6 @@
  */
 struct lysc_module {
     struct lys_module *mod;          /**< covering module structure */
-    const char *revision;            /**< the revision of the module */
     struct lysc_import *imports;     /**< list of imported modules ([sized array](@ref sizedarrays)) */
 
     struct lysc_feature *features;   /**< list of feature definitions ([sized array](@ref sizedarrays)) */
@@ -1494,6 +1493,7 @@
 struct lys_module {
     struct ly_ctx *ctx;              /**< libyang context of the module (mandatory) */
     const char *name;                /**< name of the module (mandatory) */
+    const char *revision;            /**< revision of the module (if present) */
     const char *ns;                  /**< namespace of the module (module - mandatory) */
     const char *prefix;              /**< module prefix or submodule belongsto prefix of main module (mandatory) */
     const char *filepath;            /**< path, if the schema was read from a file, NULL in case of reading from memory */
@@ -1503,7 +1503,14 @@
     const char *ref;                 /**< cross-reference for the module */
 
     struct lysp_module *parsed;      /**< Simply parsed (unresolved) YANG schema tree */
-    struct lysc_module *compiled;    /**< Compiled and fully validated YANG schema tree for data parsing */
+    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)).
+                                          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
+                                          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()). */
 
     uint8_t implemented:1;           /**< flag if the module is implemented, not just imported */
     uint8_t latest_revision:2;       /**< flag to mark the latest available revision:
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index ef6fc25..f94cfed 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -62,8 +62,8 @@
 
 #define COMPILE_CHECK_UNIQUENESS(CTX, ARRAY, MEMBER, EXCL, STMT, IDENT) \
     if (ARRAY) { \
-        for (unsigned int u = 0; u < LY_ARRAY_SIZE(ARRAY); ++u) { \
-            if (&(ARRAY)[u] != EXCL && (void*)((ARRAY)[u].MEMBER) == (void*)(IDENT)) { \
+        for (unsigned int u__ = 0; u__ < LY_ARRAY_SIZE(ARRAY); ++u__) { \
+            if (&(ARRAY)[u__] != EXCL && (void*)((ARRAY)[u__].MEMBER) == (void*)(IDENT)) { \
                 LOGVAL((CTX)->ctx, LY_VLOG_STR, (CTX)->path, LY_VCODE_DUPIDENT, IDENT, STMT); \
                 return LY_EVALID; \
             } \
@@ -174,16 +174,31 @@
 #define LYS_IFF_LP 0x04 /* ( */
 #define LYS_IFF_RP 0x08 /* ) */
 
+/**
+ * @brief Find a feature of the given name and referenced in the given module.
+ *
+ * If the compiled schema is available (the schema is implemented), the feature from the compiled schema is
+ * returned. Otherwise, the special array of pre-compiled features is used to search for the feature. Such
+ * features are always disabled (feature from not implemented schema cannot be enabled), but in case the schema
+ * will be made implemented in future (no matter if implicitly via augmenting/deviating it or explicitly via
+ * ly_ctx_module_implement()), the compilation of these feature structure is finished, but the pointers
+ * assigned till that time will be still valid.
+ *
+ * @param[in] mod Module where the feature was referenced (used to resolve prefix of the feature).
+ * @param[in] name Name of the feature including possible prefix.
+ * @param[in] len Length of the string representing the feature identifier in the name variable (mandatory!).
+ * @return Pointer to the feature structure if found, NULL otherwise.
+ */
 static struct lysc_feature *
-lysc_feature_find(struct lysc_module *mod, const char *name, size_t len)
+lys_feature_find(struct lys_module *mod, const char *name, size_t len)
 {
     size_t i;
-    struct lysc_feature *f;
+    struct lysc_feature *f, *flist;
 
     for (i = 0; i < len; ++i) {
         if (name[i] == ':') {
             /* we have a prefixed feature */
-            mod = lysc_module_find_prefix(mod, name, i);
+            mod = lys_module_find_prefix(mod, name, i);
             LY_CHECK_RET(!mod, NULL);
 
             name = &name[i + 1];
@@ -192,8 +207,14 @@
     }
 
     /* we have the correct module, get the feature */
-    LY_ARRAY_FOR(mod->features, i) {
-        f = &mod->features[i];
+    if (mod->implemented) {
+        /* module is implemented so there is already the compiled schema */
+        flist = mod->compiled->features;
+    } else {
+        flist = mod->off_features;
+    }
+    LY_ARRAY_FOR(flist, i) {
+        f = &flist[i];
         if (!strncmp(f->name, name, len) && f->name[len] == '\0') {
             return f;
         }
@@ -382,18 +403,10 @@
             iff_setop(iff->expr, LYS_IFF_F, expr_size--);
 
             /* now get the link to the feature definition */
-            if (!ctx->mod_def->compiled) {
-                /* TODO - permanently switched off feature - link to some static feature and update when the schema gets implemented */
-                LOGINT(ctx->ctx);
-                goto error;
-            } else {
-                f = lysc_feature_find(ctx->mod_def->compiled, &c[i], j - i);
-            }
-            LY_CHECK_ERR_GOTO(!f,
-                              LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
-                                     "Invalid value \"%s\" of if-feature - unable to find feature \"%.*s\".", *value, j - i, &c[i]);
-                              rc = LY_EVALID,
-                              error)
+            f = lys_feature_find(ctx->mod_def, &c[i], j - i);
+            LY_CHECK_ERR_GOTO(!f, LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
+                                         "Invalid value \"%s\" of if-feature - unable to find feature \"%.*s\".", *value, j - i, &c[i]);
+                              rc = LY_EVALID, error)
             iff->features[f_size] = f;
             LY_ARRAY_INCREMENT(iff->features);
             f_size--;
@@ -481,7 +494,7 @@
             }
         }
         if (!mod) {
-            if (lysp_load_module(ctx->ctx, imp->module->name, imp->module->compiled->revision, 0, 1, &mod)) {
+            if (lysp_load_module(ctx->ctx, imp->module->name, imp->module->revision, 0, 1, &mod)) {
                 LOGERR(ctx->ctx, LY_ENOTFOUND, "Unable to reload \"%s\" module to import it into \"%s\", source data not found.",
                        imp->module->name, ctx->mod->name);
                 return LY_ENOTFOUND;
@@ -613,44 +626,81 @@
     return LY_SUCCESS;
 }
 
+LY_ERR
+lys_feature_precompile(struct ly_ctx *ctx, struct lysp_feature *features_p, struct lysc_feature **features)
+{
+    unsigned int offset = 0, u;
+    struct lysc_ctx context = {0};
+
+    assert(ctx);
+    context.ctx = ctx;
+
+    if (!features_p) {
+        return LY_SUCCESS;
+    }
+    if (*features) {
+        offset = LY_ARRAY_SIZE(*features);
+    }
+
+    LY_ARRAY_CREATE_RET(ctx, *features, LY_ARRAY_SIZE(features_p), LY_EMEM);
+    LY_ARRAY_FOR(features_p, u) {
+        LY_ARRAY_INCREMENT(*features);
+        COMPILE_CHECK_UNIQUENESS(&context, *features, name, &(*features)[offset + u], "feature", features_p[u].name);
+        DUP_STRING(ctx, features_p[u].name, (*features)[offset + u].name);
+        DUP_STRING(ctx, features_p[u].dsc, (*features)[offset + u].dsc);
+        DUP_STRING(ctx, features_p[u].ref, (*features)[offset + u].ref);
+        (*features)[offset + u].flags = features_p[u].flags;
+    }
+
+    return LY_SUCCESS;
+}
+
 /**
- * @brief Create compiled feature structure.
+ * @brief Create pre-compiled features array.
+ *
+ * See lys_feature_precompile() for more details.
+ *
  * @param[in] ctx Compile context.
  * @param[in] feature_p Parsed feature definition to compile.
  * @param[in] options Various options to modify compiler behavior, see [compile flags](@ref scflags).
- * @param[in] features List of already compiled features to check name duplicity.
- * @param[in,out] feature Compiled feature structure to fill.
+ * @param[in,out] features List of already (pre)compiled features to find the corresponding precompiled feature structure.
  * @return LY_ERR value.
  */
 static LY_ERR
-lys_compile_feature(struct lysc_ctx *ctx, struct lysp_feature *feature_p, int options, struct lysc_feature *features, struct lysc_feature *feature)
+lys_feature_precompile_finish(struct lysc_ctx *ctx, struct lysp_feature *feature_p, int options, struct lysc_feature *features)
 {
-    unsigned int u, v;
+    unsigned int u, v, x;
+    struct lysc_feature *feature, **df;
     LY_ERR ret = LY_SUCCESS;
-    struct lysc_feature **df;
 
-    COMPILE_CHECK_UNIQUENESS(ctx, features, name, feature, "feature", feature_p->name);
-    DUP_STRING(ctx->ctx, feature_p->name, feature->name);
-    DUP_STRING(ctx->ctx, feature_p->dsc, feature->dsc);
-    DUP_STRING(ctx->ctx, feature_p->ref, feature->ref);
-    feature->flags = feature_p->flags;
+    /* find the preprecompiled feature */
+    LY_ARRAY_FOR(features, x) {
+        if (strcmp(features[x].name, feature_p->name)) {
+            continue;
+        }
+        feature = &features[x];
 
-    COMPILE_ARRAY_GOTO(ctx, feature_p->exts, feature->exts, options, u, lys_compile_ext, ret, done);
-    COMPILE_ARRAY_GOTO(ctx, feature_p->iffeatures, feature->iffeatures, options, u, lys_compile_iffeature, ret, done);
-    if (feature->iffeatures) {
-        for (u = 0; u < LY_ARRAY_SIZE(feature->iffeatures); ++u) {
-            if (feature->iffeatures[u].features) {
-                for (v = 0; v < LY_ARRAY_SIZE(feature->iffeatures[u].features); ++v) {
-                    /* add itself into the dependants list */
-                    LY_ARRAY_NEW_RET(ctx->ctx, feature->iffeatures[u].features[v]->depfeatures, df, LY_EMEM);
-                    *df = feature;
+        /* finish compilation started in lys_feature_precompile() */
+        COMPILE_ARRAY_GOTO(ctx, feature_p->exts, feature->exts, options, u, lys_compile_ext, ret, done);
+        COMPILE_ARRAY_GOTO(ctx, feature_p->iffeatures, feature->iffeatures, options, u, lys_compile_iffeature, ret, done);
+        if (feature->iffeatures) {
+            for (u = 0; u < LY_ARRAY_SIZE(feature->iffeatures); ++u) {
+                if (feature->iffeatures[u].features) {
+                    for (v = 0; v < LY_ARRAY_SIZE(feature->iffeatures[u].features); ++v) {
+                        /* add itself into the dependants list */
+                        LY_ARRAY_NEW_RET(ctx->ctx, feature->iffeatures[u].features[v]->depfeatures, df, LY_EMEM);
+                        *df = feature;
+                    }
+                    /* TODO check for circular dependency */
                 }
-                /* TODO check for circular dependency */
             }
         }
+    done:
+        return ret;
     }
-done:
-    return ret;
+
+    LOGINT(ctx->ctx);
+    return LY_EINT;
 }
 
 /**
@@ -3857,7 +3907,15 @@
     struct lysp_submodule *submod = inc->submodule;
     struct lysc_module *mainmod = ctx->mod->compiled;
 
-    COMPILE_ARRAY_UNIQUE_GOTO(ctx, submod->features, mainmod->features, options, u, lys_compile_feature, ret, error);
+    if (!mainmod->mod->off_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()). */
+        ret = lys_feature_precompile(ctx->ctx, submod->features,
+                                     mainmod->mod->off_features ? &mainmod->mod->off_features : &mainmod->features);
+        LY_CHECK_GOTO(ret, error);
+    }
+
     COMPILE_ARRAY_UNIQUE_GOTO(ctx, submod->identities, mainmod->identities, options, u, lys_compile_identity, ret, error);
 
 error:
@@ -3873,6 +3931,7 @@
     struct lysp_module *sp;
     struct lysp_node *node_p;
     unsigned int u, v;
+    int using_precompiled_features = 0;
     LY_ERR ret = LY_SUCCESS;
 
     LY_CHECK_ARG_RET(NULL, mod, mod->parsed, mod->ctx, LY_EINVAL);
@@ -3892,15 +3951,36 @@
     LY_CHECK_ERR_RET(!mod_c, LOGMEM(mod->ctx), LY_EMEM);
     mod_c->mod = mod;
 
-    if (sp->revs) {
-        DUP_STRING(mod->ctx, sp->revs[0].date, mod_c->revision);
-    }
     COMPILE_ARRAY_GOTO(&ctx, sp->imports, mod_c->imports, options, u, lys_compile_import, ret, error);
     LY_ARRAY_FOR(sp->includes, u) {
         ret = lys_compile_submodule(&ctx, &sp->includes[u], options);
         LY_CHECK_GOTO(ret != LY_SUCCESS, error);
     }
-    COMPILE_ARRAY_UNIQUE_GOTO(&ctx, sp->features, mod_c->features, options, u, lys_compile_feature, ret, error);
+    if (mod->off_features) {
+        /* there is already precompiled array of features */
+        mod_c->features = mod->off_features;
+        mod->off_features = NULL;
+        using_precompiled_features = 1;
+    } 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.ctx, 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. */
+    LY_ARRAY_FOR(sp->features, u) {
+        ret = lys_feature_precompile_finish(&ctx, &sp->features[u], options, mod_c->features);
+        LY_CHECK_GOTO(ret != LY_SUCCESS, error);
+    }
+    LY_ARRAY_FOR(sp->includes, v) {
+        LY_ARRAY_FOR(sp->includes[v].submodule->features, u) {
+            ret = lys_feature_precompile_finish(&ctx, &sp->includes[v].submodule->features[u], options, mod_c->features);
+            LY_CHECK_GOTO(ret != LY_SUCCESS, error);
+        }
+    }
+
     COMPILE_ARRAY_UNIQUE_GOTO(&ctx, sp->identities, mod_c->identities, options, u, lys_compile_identity, ret, error);
     if (sp->identities) {
         LY_CHECK_RET(lys_compile_identities_derived(&ctx, sp->identities, mod_c->identities));
@@ -3972,6 +4052,23 @@
     return LY_SUCCESS;
 
 error:
+    if (using_precompiled_features) {
+        /* keep the off_features list until the complete lys_module is freed */
+        mod->off_features = mod->compiled->features;
+        mod->compiled->features = NULL;
+    }
+    /* in the off_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_FREE(mod->off_features[u].iffeatures);
+        LY_ARRAY_FOR(mod->off_features[u].exts, v) {
+            lysc_ext_instance_free(ctx.ctx, &(mod->off_features[u].exts)[v]);
+        }
+        LY_ARRAY_FREE(mod->off_features[u].exts);
+    }
     ly_set_erase(&ctx.unres, NULL);
     ly_set_erase(&ctx.groupings, NULL);
     lysc_module_free(mod_c, NULL);
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index fa3fff9..ce0dfb8 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -464,14 +464,14 @@
     free(module);
 }
 
-static void
+void
 lysc_ext_instance_free(struct ly_ctx *ctx, struct lysc_ext_instance *ext)
 {
     FREE_STRING(ctx, ext->argument);
     FREE_ARRAY(ctx, ext->exts, lysc_ext_instance_free);
 }
 
-static void
+void
 lysc_iffeature_free(struct ly_ctx *UNUSED(ctx), struct lysc_iffeature *iff)
 {
     LY_ARRAY_FREE(iff->features);
@@ -757,8 +757,6 @@
     LY_CHECK_ARG_RET(NULL, module,);
     ctx = module->mod->ctx;
 
-    FREE_STRING(ctx, module->revision);
-
     FREE_ARRAY(ctx, module->imports, lysc_import_free);
     FREE_ARRAY(ctx, module->features, lysc_feature_free);
     FREE_ARRAY(ctx, module->identities, lysc_ident_free);
@@ -788,9 +786,11 @@
     }
 
     lysc_module_free(module->compiled, private_destructor);
+    FREE_ARRAY(module->ctx, module->off_features, lysc_feature_free);
     lysp_module_free(module->parsed);
 
     FREE_STRING(module->ctx, module->name);
+    FREE_STRING(module->ctx, module->revision);
     FREE_STRING(module->ctx, module->ns);
     FREE_STRING(module->ctx, module->prefix);
     FREE_STRING(module->ctx, module->filepath);
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index 692acaa..20a06dc 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -681,12 +681,17 @@
     LYS_INFORMAT format = LYS_IN_UNKNOWN;
     void (*module_data_free)(void *module_data, void *user_data) = NULL;
     struct lysp_load_module_check_data check_data = {0};
+    struct lys_module *m;
 
-    /* try to get the module from the context */
-    if (revision) {
-        *mod = (struct lys_module*)ly_ctx_get_module(ctx, name, revision);
-    } else {
-        *mod = (struct lys_module*)ly_ctx_get_module_latest(ctx, name);
+    assert(mod);
+
+    if (!*mod) {
+        /* try to get the module from the context */
+        if (revision) {
+            *mod = (struct lys_module*)ly_ctx_get_module(ctx, name, revision);
+        } else {
+            *mod = (struct lys_module*)ly_ctx_get_module_latest(ctx, name);
+        }
     }
 
     if (!(*mod) || (require_parsed && !(*mod)->parsed)) {
@@ -740,12 +745,15 @@
         }
     } else {
         /* we have module from the current context */
-        if (implement && (ly_ctx_get_module_implemented(ctx, name) != *mod)) {
-            /* check collision with other implemented revision */
-            LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE,
-                   "Module \"%s\" is already present in other implemented revision.", name);
-            *mod = NULL;
-            return LY_EDENIED;
+        if (implement) {
+            m = ly_ctx_get_module_implemented(ctx, name);
+            if (m && m != *mod) {
+                /* check collision with other implemented revision */
+                LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE,
+                       "Module \"%s\" is already present in other implemented revision.", name);
+                *mod = NULL;
+                return LY_EDENIED;
+            }
         }
 
         /* circular check */
@@ -828,7 +836,7 @@
     TYPE *imp; \
     if (!strncmp((MOD)->mod->prefix, prefix, len) && (MOD)->mod->prefix[len] == '\0') { \
         /* it is the prefix of the module itself */ \
-        m = ly_ctx_get_module((MOD)->mod->ctx, (MOD)->mod->name, ((struct lysc_module*)(MOD))->revision); \
+        m = ly_ctx_get_module((MOD)->mod->ctx, (MOD)->mod->name, (MOD)->mod->revision); \
     } \
     /* search in imports */ \
     if (!m) { \
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index d1adfa5..b30c123 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -61,12 +61,12 @@
 struct lysc_ctx {
     struct ly_ctx *ctx;
     struct lys_module *mod;
-    struct lys_module *mod_def; /* context module for the definitions of the nodes being currently
-                                   processed - groupings are supposed to be evaluated in place where
-                                   defined, but its content instances are supposed to be placed into
-                                   the target module (mod) */
-    struct ly_set groupings;    /* stack for groupings circular check */
-    struct ly_set unres;        /* to validate leafref's target and xpath of when/must */
+    struct lys_module *mod_def; /**< context module for the definitions of the nodes being currently
+                                     processed - groupings are supposed to be evaluated in place where
+                                     defined, but its content instances are supposed to be placed into
+                                     the target module (mod) */
+    struct ly_set groupings;    /**< stack for groupings circular check */
+    struct ly_set unres;        /**< to validate leafref's target and xpath of when/must */
     uint16_t path_len;
 #define LYSC_CTX_BUFSIZE 4078
     char path[LYSC_CTX_BUFSIZE];
@@ -436,6 +436,27 @@
                             void **result);
 
 /**
+ * @brief Create pre-compiled features array.
+ *
+ * Features are compiled in two steps to allow forward references between them via their if-feature statements.
+ * In case of not implemented schemas, the precompiled list of features is stored in lys_module structure and
+ * the compilation is not finished (if-feature and extensions are missing) and all the features are permanently
+ * disabled without a chance to change it. The list is used as target for any if-feature statement in any
+ * implemented module to get valid data to evaluate its result. The compilation is finished via
+ * lys_feature_precompile_finish() in implemented modules. In case a not implemented module becomes implemented,
+ * the precompiled list is reused to finish the compilation to preserve pointers already used in various compiled
+ * if-feature structures.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] features_p Array if the parsed features definitions to precompile.
+ * @param[in,out] features Pointer to the storage of the (pre)compiled features array where the new features are
+ * supposed to be added. The storage is supposed to be initiated to NULL when the first parsed features are going
+ * to be processed.
+ * @return LY_ERR value.
+ */
+LY_ERR lys_feature_precompile(struct ly_ctx *ctx, struct lysp_feature *features_p, struct lysc_feature **features);
+
+/**
  * @brief Free the parsed submodule structure.
  * @param[in] ctx libyang context where the string data resides in a dictionary.
  * @param[in,out] submod Parsed schema submodule structure to free.
@@ -450,6 +471,22 @@
 void lysc_type_free(struct ly_ctx *ctx, struct lysc_type *type);
 
 /**
+ * @brief Free the compiled if-feature structure.
+ * @param[in] ctx libyang context where the string data resides in a dictionary.
+ * @param[in,out] iff Compiled if-feature structure to be cleaned.
+ * Since the structure is typically part of the sized array, the structure itself is not freed.
+ */
+void lysc_iffeature_free(struct ly_ctx *ctx, struct lysc_iffeature *iff);
+
+/**
+ * @brief Free the compiled extension instance structure.
+ * @param[in] ctx libyang context where the string data resides in a dictionary.
+ * @param[in,out] ext Compiled extension instance structure to be cleaned.
+ * Since the structure is typically part of the sized array, the structure itself is not freed.
+ */
+void lysc_ext_instance_free(struct ly_ctx *ctx, struct lysc_ext_instance *ext);
+
+/**
  * @brief Free the compiled node structure.
  * @param[in] ctx libyang context where the string data resides in a dictionary.
  * @param[in,out] node Compiled node structure to be freed.
diff --git a/tests/src/test_tree_schema_compile.c b/tests/src/test_tree_schema_compile.c
index b3e4917..1288ee3 100644
--- a/tests/src/test_tree_schema_compile.c
+++ b/tests/src/test_tree_schema_compile.c
@@ -286,6 +286,22 @@
     assert_int_equal(1, lys_feature_value(&mod, "f1"));
     assert_int_equal(0, lys_feature_value(&mod, "f2"));
 
+    assert_non_null(modp = lys_parse_mem(ctx.ctx, "module b {namespace urn:b;prefix b;"
+                                         "feature f1 {if-feature f2;}feature f2;}", LYS_IN_YANG));
+    assert_non_null(modp->compiled);
+    assert_non_null(modp->compiled->features);
+    assert_int_equal(2, LY_ARRAY_SIZE(modp->compiled->features));
+    assert_non_null(modp->compiled->features[0].iffeatures);
+    assert_int_equal(1, LY_ARRAY_SIZE(modp->compiled->features[0].iffeatures));
+    assert_non_null(modp->compiled->features[0].iffeatures[0].features);
+    assert_int_equal(1, LY_ARRAY_SIZE(modp->compiled->features[0].iffeatures[0].features));
+    assert_ptr_equal(&modp->compiled->features[1], modp->compiled->features[0].iffeatures[0].features[0]);
+    assert_non_null(modp->compiled->features);
+    assert_int_equal(2, LY_ARRAY_SIZE(modp->compiled->features));
+    assert_non_null(modp->compiled->features[1].depfeatures);
+    assert_int_equal(1, LY_ARRAY_SIZE(modp->compiled->features[1].depfeatures));
+    assert_ptr_equal(&modp->compiled->features[0], modp->compiled->features[1].depfeatures[0]);
+
     /* invalid reference */
     assert_int_equal(LY_EINVAL, lys_feature_enable(&mod, "xxx"));
     logbuf_assert("Feature \"xxx\" not found in module \"a\".");
@@ -333,8 +349,8 @@
     logbuf_assert("Duplicate identifier \"f1\" of feature statement.");
     reset_mod(ctx.ctx, &mod);
 
-    ly_ctx_set_module_imp_clb(ctx.ctx, test_imp_clb, "submodule sb {belongs-to b {prefix b;} feature f1;}");
-    assert_null(lys_parse_mem(ctx.ctx, "module b{namespace urn:b; prefix b; include sb;feature f1;}", LYS_IN_YANG));
+    ly_ctx_set_module_imp_clb(ctx.ctx, test_imp_clb, "submodule sz {belongs-to z {prefix z;} feature f1;}");
+    assert_null(lys_parse_mem(ctx.ctx, "module z{namespace urn:z; prefix z; include sz;feature f1;}", LYS_IN_YANG));
     logbuf_assert("Duplicate identifier \"f1\" of feature statement.");
 
     /* import reference */
@@ -2090,10 +2106,8 @@
 
     assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIRS, &ctx));
 
-    assert_non_null(lys_parse_mem(ctx, "module grp {namespace urn:grp;prefix g; typedef mytype {type string;}"
-                                       "grouping grp {leaf x {type mytype;}}}", LYS_IN_YANG));
-
-
+    ly_ctx_set_module_imp_clb(ctx, test_imp_clb, "module grp {namespace urn:grp;prefix g; typedef mytype {type string;} feature f;"
+                              "grouping grp {leaf x {type mytype;} leaf y {type string; if-feature f;}}}");
     assert_non_null(mod = lys_parse_mem(ctx, "module a {namespace urn:a;prefix a;import grp {prefix g;}"
                                         "grouping grp_a_top {leaf a1 {type int8;}}"
                                         "container a {uses grp_a; uses grp_a_top; uses g:grp; grouping grp_a {leaf a2 {type uint8;}}}}", LYS_IN_YANG));
@@ -2108,6 +2122,22 @@
     assert_non_null((child = child->next));
     assert_string_equal("x", child->name);
     assert_ptr_equal(mod, child->module);
+    assert_non_null((child = child->next));
+    assert_string_equal("y", child->name);
+    assert_non_null(child->iffeatures);
+    assert_int_equal(1, LY_ARRAY_SIZE(child->iffeatures));
+    assert_int_equal(1, LY_ARRAY_SIZE(child->iffeatures[0].features));
+    assert_string_equal("f", child->iffeatures[0].features[0]->name);
+    assert_int_equal(LY_EINVAL, lys_feature_enable(mod->compiled->imports[0].module, "f"));
+    logbuf_assert("Module \"grp\" is not implemented so all its features are permanently disabled without a chance to change it.");
+    assert_int_equal(0, lysc_iffeature_value(&child->iffeatures[0]));
+
+    /* make the imported module implemented and enable the feature */
+    assert_non_null(mod = ly_ctx_get_module(ctx, "grp", NULL));
+    assert_int_equal(LY_SUCCESS, ly_ctx_module_implement(ctx, mod));
+    assert_int_equal(LY_SUCCESS, lys_feature_enable(mod, "f"));
+    assert_string_equal("f", child->iffeatures[0].features[0]->name);
+    assert_int_equal(1, lysc_iffeature_value(&child->iffeatures[0]));
 
     ly_ctx_set_module_imp_clb(ctx, test_imp_clb, "submodule bsub {belongs-to b {prefix b;} grouping grp {leaf b {type string;}}}");
     assert_non_null(mod = lys_parse_mem(ctx, "module b {namespace urn:b;prefix b;include bsub;uses grp;}", LYS_IN_YANG));