schema compile FEATURE validate non-instantiated groupings
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);