schema compile CHANGE support for identityref types
diff --git a/src/common.h b/src/common.h
index fd2a719..63077ed 100644
--- a/src/common.h
+++ b/src/common.h
@@ -139,6 +139,7 @@
 #define LY_VCODE_DUPIDENT    LYVE_SYNTAX_YANG, "Duplicate identifier \"%s\" of %s statement."
 #define LY_VCODE_INVAL       LYVE_SYNTAX_YANG, "Invalid value \"%.*s\" of \"%s\"."
 #define LY_VCODE_MISSTMT     LYVE_SYNTAX_YANG, "Missing mandatory keyword \"%s\" as a child of \"%s\"."
+#define LY_VCODE_MISSCHILDSTMT LYVE_SYNTAX_YANG, "Missing %s substatement for %s%s."
 #define LY_VCODE_INORD       LYVE_SYNTAX_YANG, "Invalid keyword \"%s\", it cannot appear after \"%s\"."
 #define LY_VCODE_OOB         LYVE_SYNTAX_YANG, "Value \"%.*s\" is out of \"%s\" bounds."
 #define LY_VCODE_INDEV       LYVE_SYNTAX_YANG, "Deviate \"%s\" does not support keyword \"%s\"."
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 683bb6d..dab3b1b 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -1080,7 +1080,7 @@
     struct lysc_ext_instance *exts;  /**< list of the extension instances ([sized array](@ref sizedarrays)) */
     LY_DATA_TYPE basetype;           /**< Base type of the type */
     uint32_t refcount;               /**< reference counter for type sharing */
-    struct lysc_ident **base;        /**< list of pointers to the base identities ([sized array](@ref sizedarrays)),
+    struct lysc_ident **bases;       /**< list of pointers to the base identities ([sized array](@ref sizedarrays)),
                                           mandatory (at least 1 item) */
 };
 
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index f2b4bf2..63bc3c5 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -494,44 +494,82 @@
 }
 
 static LY_ERR
-lys_compile_identities_derived(struct lysc_ctx *ctx, struct lysp_ident *idents_p, struct lysc_ident *idents)
+lys_compile_identity_bases(struct lysc_ctx *ctx, const char **bases_p,  struct lysc_ident *ident, struct lysc_ident ***bases)
 {
-    unsigned int i, u, v;
+    unsigned int u, v;
     const char *s, *name;
     struct lysc_module *mod;
-    struct lysc_ident **dident;
+    struct lysc_ident **idref;
+
+    assert(ident || bases);
+
+    if (LY_ARRAY_SIZE(bases_p) > 1 && ctx->mod->compiled->version < 2) {
+        LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
+               "Multiple bases in %s are allowed only in YANG 1.1 modules.", ident ? "identity" : "identityref type");
+        return LY_EVALID;
+    }
+
+    for (u = 0; u < LY_ARRAY_SIZE(bases_p); ++u) {
+        s = strchr(bases_p[u], ':');
+        if (s) {
+            /* prefixed identity */
+            name = &s[1];
+            mod = lysc_module_find_prefix(ctx->mod->compiled, bases_p[u], s - bases_p[u]);
+        } else {
+            name = bases_p[u];
+            mod = ctx->mod->compiled;
+        }
+        if (!mod) {
+            if (ident) {
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
+                       "Invalid prefix used for base (%s) of identity \"%s\".", bases_p[u], ident->name);
+            } else {
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
+                       "Invalid prefix used for base (%s) of identityref.", bases_p[u]);
+            }
+            return LY_EVALID;
+        }
+        idref = NULL;
+        if (mod->identities) {
+            for (v = 0; v < LY_ARRAY_SIZE(mod->identities); ++v) {
+                if (!strcmp(name, mod->identities[v].name)) {
+                    if (ident) {
+                        /* we have match! store the backlink */
+                        LY_ARRAY_NEW_RET(ctx->ctx, mod->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->identities[v];
+                    }
+                    break;
+                }
+            }
+        }
+        if (!idref || !(*idref)) {
+            if (ident) {
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
+                       "Unable to find base (%s) of identity \"%s\".", bases_p[u], ident->name);
+            } else {
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
+                       "Unable to find base (%s) of identityref.", bases_p[u]);
+            }
+            return LY_EVALID;
+        }
+    }
+    return LY_SUCCESS;
+}
+
+static LY_ERR
+lys_compile_identities_derived(struct lysc_ctx *ctx, struct lysp_ident *idents_p, struct lysc_ident *idents)
+{
+    unsigned int i;
 
     for (i = 0; i < LY_ARRAY_SIZE(idents_p); ++i) {
         if (!idents_p[i].bases) {
             continue;
         }
-        for (u = 0; u < LY_ARRAY_SIZE(idents_p[i].bases); ++u) {
-            s = strchr(idents_p[i].bases[u], ':');
-            if (s) {
-                /* prefixed identity */
-                name = &s[1];
-                mod = lysc_module_find_prefix(ctx->mod->compiled, idents_p[i].bases[u], s - idents_p[i].bases[u]);
-            } else {
-                name = idents_p[i].bases[u];
-                mod = ctx->mod->compiled;
-            }
-            LY_CHECK_ERR_RET(!mod, LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
-                                          "Invalid prefix used for base (%s) of identity \"%s\".", idents_p[i].bases[u], idents[i].name),
-                             LY_EVALID);
-            if (mod->identities) {
-                for (v = 0; v < LY_ARRAY_SIZE(mod->identities); ++v) {
-                    if (!strcmp(name, mod->identities[v].name)) {
-                        /* we have match! store the backlink */
-                        LY_ARRAY_NEW_RET(ctx->ctx, mod->identities[v].derived, dident, LY_EMEM);
-                        *dident = &idents[i];
-                        break;
-                    }
-                }
-            }
-            LY_CHECK_ERR_RET(!dident, LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
-                                             "Unable to find base (%s) of identity \"%s\".", idents_p[i].bases[u], idents[i].name),
-                             LY_EVALID);
-        }
+        LY_CHECK_RET(lys_compile_identity_bases(ctx, idents_p[i].bases, &idents[i], NULL));
     }
     return LY_SUCCESS;
 }
@@ -1447,7 +1485,7 @@
 }
 
 static LY_ERR
-lys_compile_type_(struct lysc_ctx *ctx, struct lysp_type *type_p, LY_DATA_TYPE basetype, int options, int builtin, const char *tpdfname,
+lys_compile_type_(struct lysc_ctx *ctx, struct lysp_type *type_p, LY_DATA_TYPE basetype, int options, const char *tpdfname,
                   struct lysc_type *base,  struct lysc_type **type)
 {
     LY_ERR ret = LY_SUCCESS;
@@ -1458,11 +1496,13 @@
     struct lysc_type_bits *bits;
     struct lysc_type_enum *enumeration;
     struct lysc_type_dec *dec;
+    struct lysc_type_identityref *idref;
 
     switch (basetype) {
     case LY_TYPE_BINARY:
-        /* RFC 7950 9.8.1, 9.4.4 - length, number of octets it contains */
         bin = (struct lysc_type_bin*)(*type);
+
+        /* RFC 7950 9.8.1, 9.4.4 - length, number of octets it contains */
         if (type_p->length) {
             ret = lys_compile_type_range(ctx, type_p->length, basetype, 1, 0,
                                          base ? ((struct lysc_type_bin*)base)->length : NULL, &bin->length);
@@ -1488,13 +1528,12 @@
             LY_CHECK_RET(ret);
         }
 
-        if (builtin && !type_p->flags) {
+        if (!base && !type_p->flags) {
             /* type derived from bits built-in type must contain at least one bit */
             if (tpdfname) {
-                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Missing bit substatement for bits type \"%s\".",
-                       tpdfname);
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSCHILDSTMT, "bit", "bits type ", tpdfname);
             } else {
-                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Missing bit substatement for bits type.");
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSCHILDSTMT, "bit", "bits type", "");
                 free(*type);
                 *type = NULL;
             }
@@ -1510,13 +1549,12 @@
         dec = (struct lysc_type_dec*)(*type);
 
         /* RFC 7950 9.3.4 - fraction-digits */
-        if (builtin) {
+        if (!base) {
             if (!type_p->fraction_digits) {
                 if (tpdfname) {
-                    LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Missing fraction-digits substatement for decimal64 type \"%s\".",
-                           tpdfname);
+                    LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSCHILDSTMT, "fraction-digits", "decimal64 type ", tpdfname);
                 } else {
-                    LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Missing fraction-digits substatement for decimal64 type.");
+                    LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSCHILDSTMT, "fraction-digits", "decimal64 type", "");
                     free(*type);
                     *type = NULL;
                 }
@@ -1555,8 +1593,9 @@
         }
         break;
     case LY_TYPE_STRING:
-        /* RFC 7950 9.4.4 - length */
         str = (struct lysc_type_str*)(*type);
+
+        /* RFC 7950 9.4.4 - length */
         if (type_p->length) {
             ret = lys_compile_type_range(ctx, type_p->length, basetype, 1, 0,
                                          base ? ((struct lysc_type_str*)base)->length : NULL, &str->length);
@@ -1584,21 +1623,21 @@
         }
         break;
     case LY_TYPE_ENUM:
-        /* RFC 7950 9.6 - enum */
         enumeration = (struct lysc_type_enum*)(*type);
+
+        /* RFC 7950 9.6 - enum */
         if (type_p->enums) {
             ret = lys_compile_type_enums(ctx, type_p->enums, basetype, options,
                                          base ? ((struct lysc_type_enum*)base)->enums : NULL, &enumeration->enums);
             LY_CHECK_RET(ret);
         }
 
-        if (builtin && !type_p->flags) {
+        if (!base && !type_p->flags) {
             /* type derived from enumerations built-in type must contain at least one enum */
             if (tpdfname) {
-                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
-                       "Missing enum substatement for enumeration type \"%s\".", tpdfname);
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSCHILDSTMT, "enum", "enumeration type ", tpdfname);
             } else {
-                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG, "Missing enum substatement for enumeration type.");
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSCHILDSTMT, "enum", "enumeration type", "");
                 free(*type);
                 *type = NULL;
             }
@@ -1618,8 +1657,9 @@
     case LY_TYPE_UINT32:
     case LY_TYPE_INT64:
     case LY_TYPE_UINT64:
-        /* RFC 6020 9.2.4 - range */
         num = (struct lysc_type_num*)(*type);
+
+        /* RFC 6020 9.2.4 - range */
         if (type_p->range) {
             ret = lys_compile_type_range(ctx, type_p->range, basetype, 0, 0,
                                          base ? ((struct lysc_type_num*)base)->range : NULL, &num->range);
@@ -1635,6 +1675,46 @@
             *type = calloc(1, sizeof(struct lysc_type_num));
         }
         break;
+    case LY_TYPE_IDENT:
+        idref = (struct lysc_type_identityref*)(*type);
+
+        /* RFC 7950 9.10.2 - base */
+        if (type_p->bases) {
+            if (base) {
+                /* only the directly derived identityrefs can contain base specification */
+                if (tpdfname) {
+                    LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
+                           "Invalid base substatement for type \"%s\" not directly derived from identityref built-in type.",
+                           tpdfname);
+                } else {
+                    LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_SYNTAX_YANG,
+                           "Invalid base substatement for type not directly derived from identityref built-in type.");
+                    free(*type);
+                    *type = NULL;
+                }
+                return LY_EVALID;
+            }
+            ret = lys_compile_identity_bases(ctx, type_p->bases, NULL, &idref->bases);
+            LY_CHECK_RET(ret);
+        }
+
+        if (!base && !type_p->flags) {
+            /* type derived from identityref built-in type must contain at least one base */
+            if (tpdfname) {
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSCHILDSTMT, "base", "identityref type ", tpdfname);
+            } else {
+                LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LY_VCODE_MISSCHILDSTMT, "base", "identityref type", "");
+                free(*type);
+                *type = NULL;
+            }
+            return LY_EVALID;
+        }
+
+        if (tpdfname) {
+            type_p->compiled = *type;
+            *type = calloc(1, sizeof(struct lysc_type_identityref));
+        }
+        break;
     case LY_TYPE_INST:
         /* RFC 7950 9.9.3 - require-instance */
         if (type_p->flags & LYS_SET_REQINST) {
@@ -1790,7 +1870,7 @@
         ++(*type)->refcount;
         (*type)->basetype = basetype;
         prev_type = *type;
-        ret = lys_compile_type_(ctx, &((struct lysp_tpdf*)tctx->tpdf)->type, basetype, options, (u == tpdf_chain.count - 1) ? 1 : 0, tctx->tpdf->name, base, type);
+        ret = lys_compile_type_(ctx, &((struct lysp_tpdf*)tctx->tpdf)->type, basetype, options, tctx->tpdf->name, base, type);
         LY_CHECK_GOTO(ret, cleanup);
         base = prev_type;
     }
@@ -1800,7 +1880,7 @@
         /* get restrictions from the node itself, finalize the type structure */
         (*type)->basetype = basetype;
         ++(*type)->refcount;
-        ret = lys_compile_type_(ctx, &leaf_p->type, basetype, options, base ? 0 : 1, NULL, base, type);
+        ret = lys_compile_type_(ctx, &leaf_p->type, basetype, options, NULL, base, type);
         LY_CHECK_GOTO(ret, cleanup);
     } else {
         /* no specific restriction in leaf's type definition, copy from the base */
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index 92c904a..7e74603 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -549,6 +549,9 @@
     case LY_TYPE_UINT64:
         FREE_MEMBER(ctx, ((struct lysc_type_num*)type)->range, lysc_range_free);
         break;
+    case LY_TYPE_IDENT:
+        LY_ARRAY_FREE(((struct lysc_type_identityref*)type)->bases);
+        break;
     case LY_TYPE_INST:
     case LY_TYPE_BOOL:
     case LY_TYPE_EMPTY:
diff --git a/tests/src/test_tree_schema_compile.c b/tests/src/test_tree_schema_compile.c
index f7ec609..2d87f54 100644
--- a/tests/src/test_tree_schema_compile.c
+++ b/tests/src/test_tree_schema_compile.c
@@ -904,7 +904,7 @@
     assert_non_null(mod = lys_parse_mem(ctx, "module gg {namespace urn:gg;prefix gg;typedef mytype {type enumeration;}"
                                              "leaf l {type mytype {enum one;}}}", LYS_IN_YANG));
     assert_int_equal(LY_EVALID, lys_compile(mod, 0));
-    logbuf_assert("Missing enum substatement for enumeration type \"mytype\".");
+    logbuf_assert("Missing enum substatement for enumeration type mytype.");
 
     assert_non_null(mod = lys_parse_mem(ctx, "module hh {namespace urn:hh;prefix hh; typedef mytype {type enumeration {enum one;}}"
                                         "leaf l {type mytype {enum one;}}}", LYS_IN_YANG));
@@ -1010,7 +1010,7 @@
     assert_non_null(mod = lys_parse_mem(ctx, "module gg {namespace urn:gg;prefix gg;typedef mytype {type bits;}"
                                              "leaf l {type mytype {bit one;}}}", LYS_IN_YANG));
     assert_int_equal(LY_EVALID, lys_compile(mod, 0));
-    logbuf_assert("Missing bit substatement for bits type \"mytype\".");
+    logbuf_assert("Missing bit substatement for bits type mytype.");
 
     assert_non_null(mod = lys_parse_mem(ctx, "module hh {namespace urn:hh;prefix hh; typedef mytype {type bits {bit one;}}"
                                         "leaf l {type mytype {bit one;}}}", LYS_IN_YANG));
@@ -1076,7 +1076,7 @@
 
     assert_non_null(mod = lys_parse_mem(ctx, "module ab {namespace urn:ab;prefix ab; typedef mytype {type decimal64;}leaf l {type mytype;}}", LYS_IN_YANG));
     assert_int_equal(LY_EVALID, lys_compile(mod, 0));
-    logbuf_assert("Missing fraction-digits substatement for decimal64 type \"mytype\".");
+    logbuf_assert("Missing fraction-digits substatement for decimal64 type mytype.");
 
     assert_non_null(mod = lys_parse_mem(ctx, "module bb {namespace urn:bb;prefix bb; leaf l {type decimal64 {fraction-digits 2;"
                                         "range '3.142';}}}", LYS_IN_YANG));
@@ -1144,6 +1144,65 @@
     ly_ctx_destroy(ctx, NULL);
 }
 
+static void
+test_type_identityref(void **state)
+{
+    *state = test_type_identityref;
+
+    struct ly_ctx *ctx;
+    struct lys_module *mod;
+    struct lysc_type *type;
+
+    assert_int_equal(LY_SUCCESS, ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIRS, &ctx));
+
+    assert_non_null(mod = lys_parse_mem(ctx, "module a {yang-version 1.1;namespace urn:a;prefix a;identity i; identity j; identity k {base i;}"
+                                        "typedef mytype {type identityref {base i;}}"
+                                        "leaf l1 {type mytype;} leaf l2 {type identityref {base k; base j;}}}", LYS_IN_YANG));
+    assert_int_equal(LY_SUCCESS, lys_compile(mod, 0));
+    type = ((struct lysc_node_leaf*)mod->compiled->data)->type;
+    assert_non_null(type);
+    assert_int_equal(LY_TYPE_IDENT, type->basetype);
+    assert_non_null(((struct lysc_type_identityref*)type)->bases);
+    assert_int_equal(1, LY_ARRAY_SIZE(((struct lysc_type_identityref*)type)->bases));
+    assert_string_equal("i", ((struct lysc_type_identityref*)type)->bases[0]->name);
+
+    type = ((struct lysc_node_leaf*)mod->compiled->data->next)->type;
+    assert_non_null(type);
+    assert_int_equal(LY_TYPE_IDENT, type->basetype);
+    assert_non_null(((struct lysc_type_identityref*)type)->bases);
+    assert_int_equal(2, LY_ARRAY_SIZE(((struct lysc_type_identityref*)type)->bases));
+    assert_string_equal("k", ((struct lysc_type_identityref*)type)->bases[0]->name);
+    assert_string_equal("j", ((struct lysc_type_identityref*)type)->bases[1]->name);
+
+    /* invalid cases */
+    assert_non_null(mod = lys_parse_mem(ctx, "module aa {namespace urn:aa;prefix aa; leaf l {type identityref;}}", LYS_IN_YANG));
+    assert_int_equal(LY_EVALID, lys_compile(mod, 0));
+    logbuf_assert("Missing base substatement for identityref type.");
+
+    assert_non_null(mod = lys_parse_mem(ctx, "module bb {namespace urn:bb;prefix bb; typedef mytype {type identityref;}"
+                                        "leaf l {type mytype;}}", LYS_IN_YANG));
+    assert_int_equal(LY_EVALID, lys_compile(mod, 0));
+    logbuf_assert("Missing base substatement for identityref type mytype.");
+
+    assert_non_null(mod = lys_parse_mem(ctx, "module cc {namespace urn:cc;prefix cc; identity i; typedef mytype {type identityref {base i;}}"
+                                        "leaf l {type mytype {base i;}}}", LYS_IN_YANG));
+    assert_int_equal(LY_EVALID, lys_compile(mod, 0));
+    logbuf_assert("Invalid base substatement for type not directly derived from identityref built-in type.");
+
+    assert_non_null(mod = lys_parse_mem(ctx, "module dd {namespace urn:dd;prefix dd; identity i; typedef mytype {type identityref {base i;}}"
+                                        "typedef mytype2 {type mytype {base i;}}leaf l {type mytype2;}}", LYS_IN_YANG));
+    assert_int_equal(LY_EVALID, lys_compile(mod, 0));
+    logbuf_assert("Invalid base substatement for type \"mytype2\" not directly derived from identityref built-in type.");
+
+    assert_non_null(mod = lys_parse_mem(ctx, "module ee {namespace urn:ee;prefix ee; identity i; identity j;"
+                                        "leaf l {type identityref {base i;base j;}}}", LYS_IN_YANG));
+    assert_int_equal(LY_EVALID, lys_compile(mod, 0));
+    logbuf_assert("Multiple bases in identityref type are allowed only in YANG 1.1 modules.");
+
+    *state = NULL;
+    ly_ctx_destroy(ctx, NULL);
+}
+
 int main(void)
 {
     const struct CMUnitTest tests[] = {
@@ -1157,6 +1216,7 @@
         cmocka_unit_test_setup_teardown(test_type_bits, logger_setup, logger_teardown),
         cmocka_unit_test_setup_teardown(test_type_dec64, logger_setup, logger_teardown),
         cmocka_unit_test_setup_teardown(test_type_instanceid, logger_setup, logger_teardown),
+        cmocka_unit_test_setup_teardown(test_type_identityref, logger_setup, logger_teardown),
         cmocka_unit_test_setup_teardown(test_node_container, logger_setup, logger_teardown),
     };