schema tree FEATURE when in compiled actions and notifs

In YANG 1.1, actions nor notifications do not list when as their substatement. However, the when can be de-facto inherited to the actions/notification by augmenting their parent in data tree. Moreover, in future YANG versions, when substatement is planned even for actions/notifications.

PR  #1229
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 26df0bb..d0ec2d3 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -1408,13 +1408,12 @@
     const char *name;                /**< action/RPC name (mandatory) */
     const char *dsc;                 /**< description */
     const char *ref;                 /**< reference */
-
     struct lysc_ext_instance *exts;  /**< list of the extension instances ([sized array](@ref sizedarrays)) */
     struct lysc_iffeature *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */
 
     struct lysc_action_inout input;  /**< RPC's/action's input */
     struct lysc_action_inout output; /**< RPC's/action's output */
-
+    struct lysc_when **when;         /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */
     void *priv;                      /** private arbitrary user data, not used by libyang */
 
 };
@@ -1433,10 +1432,9 @@
     const char *name;                /**< Notification name (mandatory) */
     const char *dsc;                 /**< description */
     const char *ref;                 /**< reference */
-
     struct lysc_ext_instance *exts;  /**< list of the extension instances ([sized array](@ref sizedarrays)) */
     struct lysc_iffeature *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */
-
+    struct lysc_when **when;         /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */
     void *priv;                      /** private arbitrary user data, not used by libyang */
 };
 
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index 7547114..c85a07a 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -924,15 +924,17 @@
 
 /**
  * @brief Compile information from the when statement
+ *
  * @param[in] ctx Compile context.
- * @param[in] when_p The parsed when statement structure.
- * @param[in] flags Flags of the node with the "when" defiition.
- * @param[in] node Node that inherited the "when" definition, must be connected to parents.
+ * @param[in] when_p Parsed when structure.
+ * @param[in] flags Flags of the parsed node with the when statement.
+ * @param[in] ctx_node Context node for the when statement.
  * @param[out] when Pointer where to store pointer to the created compiled when structure.
  * @return LY_ERR value.
  */
 static LY_ERR
-lys_compile_when(struct lysc_ctx *ctx, struct lysp_when *when_p, uint16_t flags, struct lysc_node *node, struct lysc_when **when)
+lys_compile_when_(struct lysc_ctx *ctx, struct lysp_when *when_p, uint16_t flags, const struct lysc_node *ctx_node,
+        struct lysc_when **when)
 {
     LY_ERR ret = LY_SUCCESS;
 
@@ -941,7 +943,7 @@
     (*when)->refcount = 1;
     LY_CHECK_RET(lyxp_expr_parse(ctx->ctx, when_p->cond, 0, 1, &(*when)->cond));
     (*when)->module = ctx->mod_def;
-    (*when)->context = lysc_xpath_context(node);
+    (*when)->context = (struct lysc_node *)ctx_node;
     DUP_STRING_GOTO(ctx->ctx, when_p->dsc, (*when)->dsc, ret, done);
     DUP_STRING_GOTO(ctx->ctx, when_p->ref, (*when)->ref, ret, done);
     COMPILE_EXTS_GOTO(ctx, when_p->exts, (*when)->exts, (*when), LYEXT_PAR_WHEN, ret, done);
@@ -952,6 +954,65 @@
 }
 
 /**
+ * @brief Compile information from the when statement by either standard compilation or by reusing
+ * another compiled when structure.
+ *
+ * @param[in] ctx Compile context.
+ * @param[in] when_p Parsed when structure.
+ * @param[in] flags Flags of the parsed node with the when statement.
+ * @param[in] ctx_node Context node for the when statement.
+ * @param[in] node Compiled node to which add the compiled when.
+ * @param[in,out] when_c Optional, pointer to the previously compiled @p when_p to be reused. Set to NULL
+ * for the first call.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lys_compile_when(struct lysc_ctx *ctx, struct lysp_when *when_p, uint16_t flags, const struct lysc_node *ctx_node,
+        struct lysc_node *node, struct lysc_when **when_c)
+{
+    struct lysc_when **new_when, ***node_when;
+
+    assert(when_p);
+
+    /* get the when array */
+    if (node->nodetype & LYS_ACTION) {
+        node_when = &((struct lysc_action *)node)->when;
+    } else if (node->nodetype == LYS_NOTIF) {
+        node_when = &((struct lysc_notif *)node)->when;
+    } else {
+        node_when = &node->when;
+    }
+
+    /* create new when pointer */
+    LY_ARRAY_NEW_RET(ctx->ctx, *node_when, new_when, LY_EMEM);
+    if (!when_c || !(*when_c)) {
+        /* compile when */
+        LY_CHECK_RET(lys_compile_when_(ctx, when_p, flags, ctx_node, new_when));
+
+        if (!(ctx->options & LYSC_OPT_GROUPING)) {
+            /* do not check "when" semantics in a grouping */
+            LY_CHECK_RET(ly_set_add(&ctx->xpath, node, 0, NULL));
+        }
+
+        /* remember the compiled when for sharing */
+        if (when_c) {
+            *when_c = *new_when;
+        }
+    } else {
+        /* use the previously compiled when */
+        ++(*when_c)->refcount;
+        *new_when = *when_c;
+
+        if (!(ctx->options & LYSC_OPT_GROUPING)) {
+            /* in this case check "when" again for all children because of dummy node check */
+            LY_CHECK_RET(ly_set_add(&ctx->xpath, node, 0, NULL));
+        }
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
  * @brief Compile information from the must statement
  * @param[in] ctx Compile context.
  * @param[in] must_p The parsed must statement structure.
@@ -4397,7 +4458,9 @@
     LY_ERR ret = LY_SUCCESS;
     struct lysp_node *pnode;
     struct lysc_node *node;
-    struct lysc_when **when, *when_shared;
+    struct lysc_when *when_shared = NULL;
+    struct lysc_action **actions;
+    struct lysc_notif **notifs;
     ly_bool allow_mandatory = 0;
     LY_ARRAY_COUNT_TYPE u;
     struct ly_set child_set = {0};
@@ -4419,7 +4482,6 @@
         allow_mandatory = 1;
     }
 
-    when_shared = NULL;
     LY_LIST_FOR(aug_p->child, pnode) {
         /* check if the subnode can be connected to the found target (e.g. case cannot be inserted into container) */
         if (((pnode->nodetype == LYS_CASE) && (target->nodetype != LYS_CHOICE)) ||
@@ -4452,27 +4514,10 @@
                 goto cleanup;
             }
 
-            /* pass augment's when to all the children TODO this way even action and notif should have "when" (code below) */
             if (aug_p->when) {
-                LY_ARRAY_NEW_GOTO(ctx->ctx, node->when, when, ret, cleanup);
-                if (!when_shared) {
-                    LY_CHECK_GOTO(ret = lys_compile_when(ctx, aug_p->when, aug_p->flags, target, when), cleanup);
-
-                    if (!(ctx->options & LYSC_OPT_GROUPING)) {
-                        /* do not check "when" semantics in a grouping */
-                        LY_CHECK_GOTO(ret = ly_set_add(&ctx->xpath, node, 0, NULL), cleanup);
-                    }
-
-                    when_shared = *when;
-                } else {
-                    ++when_shared->refcount;
-                    (*when) = when_shared;
-
-                    if (!(ctx->options & LYSC_OPT_GROUPING)) {
-                        /* in this case check "when" again for all children because of dummy node check */
-                        LY_CHECK_GOTO(ret = ly_set_add(&ctx->xpath, node, 0, NULL), cleanup);
-                    }
-                }
+                /* pass augment's when to all the children */
+                ret = lys_compile_when(ctx, aug_p->when, aug_p->flags, lysc_xpath_context(target), node, &when_shared);
+                LY_CHECK_GOTO(ret, cleanup);
             }
         }
         ly_set_erase(&child_set, NULL);
@@ -4480,32 +4525,60 @@
 
     switch (target->nodetype) {
     case LYS_CONTAINER:
-        COMPILE_OP_ARRAY_GOTO(ctx, aug_p->actions, ((struct lysc_node_container *)target)->actions, target,
-                u, lys_compile_action, 0, ret, cleanup);
-        COMPILE_OP_ARRAY_GOTO(ctx, aug_p->notifs, ((struct lysc_node_container *)target)->notifs, target,
-                u, lys_compile_notif, 0, ret, cleanup);
+        actions = &((struct lysc_node_container *)target)->actions;
+        notifs = &((struct lysc_node_container *)target)->notifs;
         break;
     case LYS_LIST:
-        COMPILE_OP_ARRAY_GOTO(ctx, aug_p->actions, ((struct lysc_node_list *)target)->actions, target,
-                u, lys_compile_action, 0, ret, cleanup);
-        COMPILE_OP_ARRAY_GOTO(ctx, aug_p->notifs, ((struct lysc_node_list *)target)->notifs, target,
-                u, lys_compile_notif, 0, ret, cleanup);
+        actions = &((struct lysc_node_list *)target)->actions;
+        notifs = &((struct lysc_node_list *)target)->notifs;
         break;
     default:
-        if (aug_p->actions) {
+        actions = NULL;
+        notifs = NULL;
+        break;
+    }
+
+    if (aug_p->actions) {
+        if (!actions) {
             LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
                     "Invalid augment of %s node which is not allowed to contain RPC/action node \"%s\".",
                     lys_nodetype2str(target->nodetype), aug_p->actions[0].name);
             ret = LY_EVALID;
             goto cleanup;
         }
-        if (aug_p->notifs) {
+
+        /* compile actions into the target */
+        COMPILE_OP_ARRAY_GOTO(ctx, aug_p->actions, *actions, target, u, lys_compile_action, 0, ret, cleanup);
+
+        if (aug_p->when) {
+            /* inherit when */
+            LY_ARRAY_FOR(*actions, u) {
+                ret = lys_compile_when(ctx, aug_p->when, aug_p->flags, lysc_xpath_context(target),
+                        (struct lysc_node *)&(*actions)[u], &when_shared);
+                LY_CHECK_GOTO(ret, cleanup);
+            }
+        }
+    }
+    if (aug_p->notifs) {
+        if (!notifs) {
             LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
                     "Invalid augment of %s node which is not allowed to contain notification node \"%s\".",
                     lys_nodetype2str(target->nodetype), aug_p->notifs[0].name);
             ret = LY_EVALID;
             goto cleanup;
         }
+
+        /* compile notifications into the target */
+        COMPILE_OP_ARRAY_GOTO(ctx, aug_p->notifs, *notifs, target, u, lys_compile_notif, 0, ret, cleanup);
+
+        if (aug_p->when) {
+            /* inherit when */
+            LY_ARRAY_FOR(*notifs, u) {
+                ret = lys_compile_when(ctx, aug_p->when, aug_p->flags, lysc_xpath_context(target),
+                        (struct lysc_node *)&(*notifs)[u], &when_shared);
+                LY_CHECK_GOTO(ret, cleanup);
+            }
+        }
     }
 
 cleanup:
@@ -4845,7 +4918,7 @@
     uint32_t i, grp_stack_count;
     struct lys_module *grp_mod, *mod_old = ctx->mod_def;
     LY_ERR ret = LY_SUCCESS;
-    struct lysc_when **when, *when_shared;
+    struct lysc_when *when_shared = NULL;
     LY_ARRAY_COUNT_TYPE u;
     struct lysc_notif **notifs = NULL;
     struct lysc_action **actions = NULL;
@@ -4888,33 +4961,12 @@
     }
 
     if (uses_p->when) {
-        /* pass uses's when to all the data children, actions and notifications are ignored */
-        when_shared = NULL;
+        /* pass uses's when to all the data children */
         for (i = 0; i < uses_child_set.count; ++i) {
             child = uses_child_set.snodes[i];
 
-            LY_ARRAY_NEW_GOTO(ctx->ctx, child->when, when, ret, cleanup);
-            if (!when_shared) {
-                ret = lys_compile_when(ctx, uses_p->when, uses_p->flags, parent, when);
-                LY_CHECK_GOTO(ret, cleanup);
-
-                if (!(ctx->options & LYSC_OPT_GROUPING)) {
-                    /* do not check "when" semantics in a grouping */
-                    ret = ly_set_add(&ctx->xpath, child, 0, NULL);
-                    LY_CHECK_GOTO(ret, cleanup);
-                }
-
-                when_shared = *when;
-            } else {
-                ++when_shared->refcount;
-                (*when) = when_shared;
-
-                if (!(ctx->options & LYSC_OPT_GROUPING)) {
-                    /* in this case check "when" again for all children because of dummy node check */
-                    ret = ly_set_add(&ctx->xpath, child, 0, NULL);
-                    LY_CHECK_GOTO(ret, cleanup);
-                }
-            }
+            ret = lys_compile_when(ctx, uses_p->when, uses_p->flags, lysc_xpath_context(parent), child, &when_shared);
+            LY_CHECK_GOTO(ret, cleanup);
         }
     }
 
@@ -4929,6 +4981,15 @@
             goto cleanup;
         }
         COMPILE_OP_ARRAY_GOTO(ctx, grp->actions, *actions, parent, u, lys_compile_action, 0, ret, cleanup);
+
+        if (uses_p->when) {
+            /* inherit when */
+            LY_ARRAY_FOR(*actions, u) {
+                ret = lys_compile_when(ctx, uses_p->when, uses_p->flags, lysc_xpath_context(parent),
+                        (struct lysc_node *)&(*actions)[u], &when_shared);
+                LY_CHECK_GOTO(ret, cleanup);
+            }
+        }
     }
 
     /* compile notifications */
@@ -4942,6 +5003,15 @@
             goto cleanup;
         }
         COMPILE_OP_ARRAY_GOTO(ctx, grp->notifs, *notifs, parent, u, lys_compile_notif, 0, ret, cleanup);
+
+        if (uses_p->when) {
+            /* inherit when */
+            LY_ARRAY_FOR(*notifs, u) {
+                ret = lys_compile_when(ctx, uses_p->when, uses_p->flags, lysc_xpath_context(parent),
+                        (struct lysc_node *)&(*notifs)[u], &when_shared);
+                LY_CHECK_GOTO(ret, cleanup);
+            }
+        }
     }
 
     /* check that all augments were applied */
@@ -6978,7 +7048,6 @@
 {
     LY_ERR ret = LY_SUCCESS;
     struct lysc_node *node = NULL;
-    struct lysc_when **when;
     struct lysp_node *dev_pnode = NULL, *orig_pnode = pnode;
     LY_ARRAY_COUNT_TYPE u;
     ly_bool not_supported;
@@ -7078,13 +7147,8 @@
     DUP_STRING_GOTO(ctx->ctx, pnode->dsc, node->dsc, ret, error);
     DUP_STRING_GOTO(ctx->ctx, pnode->ref, node->ref, ret, error);
     if (pnode->when) {
-        LY_ARRAY_NEW_GOTO(ctx->ctx, node->when, when, ret, error);
-        LY_CHECK_GOTO(ret = lys_compile_when(ctx, pnode->when, pnode->flags, node, when), error);
-
-        if (!(ctx->options & LYSC_OPT_GROUPING)) {
-            /* do not check "when" semantics in a grouping */
-            LY_CHECK_GOTO(ret = ly_set_add(&ctx->xpath, node, 0, NULL), error);
-        }
+        ret = lys_compile_when(ctx, pnode->when, pnode->flags, lysc_xpath_context(node), node, NULL);
+        LY_CHECK_GOTO(ret, error);
     }
     COMPILE_ARRAY_GOTO(ctx, pnode->iffeatures, node->iffeatures, u, lys_compile_iffeature, ret, error);
 
@@ -7605,11 +7669,13 @@
         when = ((struct lysc_node_case *)node)->when;
         break;
     case LYS_NOTIF:
+        when = ((struct lysc_notif *)node)->when;
         musts = ((struct lysc_notif *)node)->musts;
         break;
     case LYS_RPC:
     case LYS_ACTION:
-        /* first process input musts */
+        /* first process when and input musts */
+        when = ((struct lysc_action *)node)->when;
         musts = ((struct lysc_action *)node)->input.musts;
         break;
     default:
@@ -7681,6 +7747,7 @@
     if ((node->nodetype & (LYS_RPC | LYS_ACTION)) && !input_done) {
         /* now check output musts */
         input_done = 1;
+        when = NULL;
         musts = ((struct lysc_action *)node)->output.musts;
         opts = LYXP_SCNODE_OUTPUT;
         goto check_musts;
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index 81693ec..c12253c 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -672,6 +672,7 @@
     FREE_STRING(ctx, action->ref);
     FREE_ARRAY(ctx, action->iffeatures, lysc_iffeature_free);
     FREE_ARRAY(ctx, action->exts, lysc_ext_instance_free);
+    FREE_ARRAY(ctx, action->when, lysc_when_free);
     FREE_ARRAY(ctx, action->input_exts, lysc_ext_instance_free);
     lysc_action_inout_free(ctx, &action->input);
     FREE_ARRAY(ctx, action->output_exts, lysc_ext_instance_free);
@@ -688,6 +689,7 @@
     FREE_STRING(ctx, notif->ref);
     FREE_ARRAY(ctx, notif->iffeatures, lysc_iffeature_free);
     FREE_ARRAY(ctx, notif->exts, lysc_ext_instance_free);
+    FREE_ARRAY(ctx, notif->when, lysc_when_free);
     FREE_ARRAY(ctx, notif->musts, lysc_must_free);
     LY_LIST_FOR_SAFE(notif->data, child_next, child) {
         lysc_node_free(ctx, child);