schema compile FEATURE validate non-instantiated groupings
diff --git a/src/tree_schema.h b/src/tree_schema.h
index a7c5017..b2d619d 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -545,6 +545,7 @@
  *         LYS_DOUBLEQUOTED | | | | | | | | | | | | | | | | | | | | | | |x|
  *                          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  *      11 LYS_SET_MAX      | | | |x|x| | | | | | | | | | | | |x| |x| | | |
+ *         LYS_USED_GRP     | | | | | | | | | | | | | | | |x| | | | | | | |
  *     ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  *
  */
@@ -629,6 +630,8 @@
 #define LYS_YINELEM_TRUE 0x80        /**< yin-element true for extension's argument */
 #define LYS_YINELEM_FALSE 0x100      /**< yin-element false for extension's argument */
 #define LYS_YINELEM_MASK 0x180       /**< mask for yin-element value */
+#define LYS_USED_GRP     0x400       /**< internal flag for validating not-instantiated groupings
+                                          (resp. do not validate again the instantiated groupings). */
 #define LYS_SET_VALUE    0x200       /**< value attribute is set */
 #define LYS_SET_MIN      0x200       /**< min attribute is set */
 #define LYS_SET_MAX      0x400       /**< max attribute is set */
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index 52a102b..1f57db9 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -4443,6 +4443,10 @@
                "Grouping \"%s\" references itself through a uses statement.", grp->name);
         return LY_EVALID;
     }
+    if (!(ctx->options & LYSC_OPT_GROUPING)) {
+        /* remember that the grouping is instantiated to avoid its standalone validation */
+        grp->flags |= LYS_USED_GRP;
+    }
 
     /* switch context's mod_def */
     mod_old = ctx->mod_def;
@@ -4753,6 +4757,45 @@
     return ret;
 }
 
+/**
+ * @brief Validate groupings that were defined but not directly used in the schema itself.
+ *
+ * The grouping does not need to be compiled (and it is compiled here, but the result is forgotten immediately),
+ * but to have the complete result of the schema validity, even such groupings are supposed to be checked.
+ */
+static LY_ERR
+lys_compile_grouping(struct lysc_ctx *ctx, struct lysp_node *node_p, struct lysp_grp *grp)
+{
+    LY_ERR ret;
+    struct lysp_node_uses fake_uses = {
+        .parent = node_p,
+        .nodetype = LYS_USES,
+        .flags = 0, .next = NULL,
+        .name = grp->name,
+        .dsc = NULL, .ref = NULL, .when = NULL, .iffeatures = NULL, .exts = NULL,
+        .refines = NULL, .augments = NULL
+    };
+    struct lysc_node_container fake_container = {
+        .nodetype = LYS_CONTAINER,
+        .flags = node_p ? (node_p->flags & LYS_FLAGS_COMPILED_MASK) : 0,
+        .module = ctx->mod,
+        .sp = NULL, .parent = NULL, .next = NULL,
+        .prev = (struct lysc_node*)&fake_container,
+        .name = "fake",
+        .dsc = NULL, .ref = NULL, .exts = NULL, .iffeatures = NULL, .when = NULL,
+        .child = NULL, .musts = NULL, .actions = NULL, .notifs = NULL
+    };
+
+    if (grp->parent) {
+        LOGWRN(ctx->ctx, "Locally scoped grouping \"%s\" not used.", grp->name);
+    }
+    ret = lys_compile_uses(ctx, &fake_uses, (struct lysc_node*)&fake_container);
+
+    /* cleanup */
+    lysc_node_container_free(ctx->ctx, &fake_container);
+
+    return ret;
+}
 
 /**
  * @brief Compile parsed schema node information.
@@ -5785,6 +5828,7 @@
     struct lysp_module *sp;
     struct lysp_node *node_p;
     struct lysp_augment **augments = NULL;
+    struct lysp_grp *grps;
     struct lys_module *m;
     unsigned int u, v;
     LY_ERR ret = LY_SUCCESS;
@@ -5910,6 +5954,23 @@
         }
     }
 
+    /* validate non-instantiated groupings from the parsed schema,
+     * without it we would accept even the schemas with invalid grouping specification */
+    ctx.options |= LYSC_OPT_GROUPING;
+    LY_ARRAY_FOR(sp->groupings, u) {
+        if (!(sp->groupings[u].flags & LYS_USED_GRP)) {
+            LY_CHECK_GOTO((ret = lys_compile_grouping(&ctx, node_p, &sp->groupings[u])) != LY_SUCCESS, error);
+        }
+    }
+    LY_LIST_FOR(sp->data, node_p) {
+        grps = (struct lysp_grp*)lysp_node_groupings(node_p);
+        LY_ARRAY_FOR(grps, u) {
+            if (!(grps[u].flags & LYS_USED_GRP)) {
+                LY_CHECK_GOTO((ret = lys_compile_grouping(&ctx, node_p, &grps[u])) != LY_SUCCESS, error);
+            }
+        }
+    }
+
     ly_set_erase(&ctx.unres, NULL);
     ly_set_erase(&ctx.groupings, NULL);
     ly_set_erase(&ctx.tpdf_chain, NULL);
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index f66c70f..c3948a0 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -666,7 +666,7 @@
     }
 }
 
-static void
+void
 lysc_node_container_free(struct ly_ctx *ctx, struct lysc_node_container *node)
 {
     struct lysc_node *child, *child_next;
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index 4bafa70..6194649 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -164,6 +164,10 @@
 #define LYSC_OPT_FREE_SP    0x04               /**< Free the input printable schema */
 #define LYSC_OPT_INTERNAL   0x08               /**< Internal compilation caused by dependency */
 #define LYSC_OPT_NOTIFICATION 0x10             /**< Internal option when compiling schema tree of Notification */
+
+#define LYSC_OPT_GROUPING   0x20               /** Compiling (validation) of a non-instantiated grouping.
+                                                   In this case not all the restrictions are checked since they can be valid only
+                                                   in the real placement of the grouping. TODO - what specifically is not done */
 /** @} scflags */
 
 /**
@@ -622,6 +626,17 @@
 void lysc_node_free(struct ly_ctx *ctx, struct lysc_node *node);
 
 /**
+ * @brief Free the compiled container node structure.
+ *
+ * Only the container-specific members are freed, for generic node free function,
+ * use lysc_node_free().
+ *
+ * @param[in] ctx libyang context where the string data resides in a dictionary.
+ * @param[in,out] node Compiled container node structure to be freed.
+ */
+void lysc_node_container_free(struct ly_ctx *ctx, struct lysc_node_container *node);
+
+/**
  * @brief Free the compiled schema structure.
  * @param[in,out] module Compiled schema module structure to free.
  * @param[in] private_destructor Function to remove private data from the compiled schema tree.