This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch edje-vector-intergration
in repository efl.

View the commit online.

commit 67fcd252f89d3629272a4fa45317642a128369a1
Author: [email protected] <[email protected]>
AuthorDate: Thu Apr 30 09:36:42 2026 -0600

    edje: implement VEC004 path-topology mismatch diagnostic (Phase 7.5)
    
    Task 7.5 closes Phase 7, the final compile-time validation phase. VEC004
    detects when same-named shapes have topology-incompatible SVG paths across
    descriptions of the same VECTOR part — an unsupported morph that causes
    runtime step-transitions instead of interpolation.
    
    Topology helpers in edje_cc_out.c:
    - _path_command_count(path, cmds, max) extracts the sequence of SVG
      command letters (MmLlHhVvCcSsQqTtAaZz) from a path string, ignoring
      whitespace, coordinates, and punctuation. Stack buffer at 2048 entries
      safely handles all real-world paths (~100 commands typical).
    - _paths_topology_compatible(a, b) compares command sequences for equality,
      treating 'M' and 'm' as distinct (matches EFL's interpolator behavior).
    
    Walker refactored from _vec009_walk to _vec_walk_pair (and _vec009_check_pair
    to _vec_check_pair) to unify pair-iteration: VEC009 emits on node-type
    mismatch, VEC004 emits on path topology mismatch for SHAPE nodes only. Single
    O(n²) pass covers both diagnostics.
    
    Test helper _compile_with_diag_flags now wraps the previous _compile_with_diag
    with optional -W extra_flags argument (e.g. "-W vector-topology=error");
    backward-compatible wrapper preserves all 77 existing call sites unchanged.
    
    Three new tests:
    1. edje_vg_diag_vec004_topology_mismatch: "M L Z" vs "M L L Z" paths across
       descriptions; expects VEC004 warning with exit 0.
    2. edje_vg_diag_vec004_no_false_positive: same "M L Z" topology, different
       coordinates; no warning, clean compile.
    3. edje_vg_diag_vec004_werror_escalation: same fixture as (1) but with
       -W vector-topology=error flag; exit non-zero with VEC004 in stderr.
    
    All 80 unit tests pass. Phase 7 complete (5 tasks: diagnostic infrastructure,
    parse-time VEC001/003/011/012, VEC014 transform-form conflict, VEC007/009
    cross-description, and now VEC004 path topology).
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 src/bin/edje/edje_cc_out.c               | 116 +++++++++++++++++----
 src/tests/edje/edje_test_vector_states.c | 169 +++++++++++++++++++++++++++++--
 2 files changed, 257 insertions(+), 28 deletions(-)

diff --git a/src/bin/edje/edje_cc_out.c b/src/bin/edje/edje_cc_out.c
index 3945e3d586..37c6ac204a 100644
--- a/src/bin/edje/edje_cc_out.c
+++ b/src/bin/edje/edje_cc_out.c
@@ -3024,38 +3024,109 @@ data_write_color_class_register(Eet_File *ef)
 
 /* =========================================================================
  * Task 7.4: Cross-description vector diagnostics (VEC007, VEC009)
+ * Task 7.5: VEC004 path-topology mismatch
  *
  * VEC007 (INFO)  — Lottie-source description; topology checks skipped.
  * VEC009 (WARN)  — same-named nodes have different types across descriptions.
+ * VEC004 (WARN)  — same-named shapes have topology-incompatible paths.
  * VEC010         — deferred: parent-path mismatch; rare in practice, helper
  *                  to compute parent-path strings is non-trivial. Noted for
  *                  Phase 8 polish.
  * VEC008         — deferred: subsumed by Phase 4.3's VEC002 error.
  * ========================================================================= */
 
+/* --- Task 7.5: path topology helpers -------------------------------------- */
+
+/* Extract the ordered command-letter sequence from an SVG path string into
+ * cmds[0..max-1].  Returns the number of commands written.  Coordinate
+ * digits, signs, dots, and exponent letters are NOT counted — only the
+ * single-letter command tokens from the SVG path grammar are recorded.
+ *
+ * 'e'/'E' are skipped because they appear inside floating-point exponents
+ * (e.g. "1.5e-3") as well as at word boundaries where they would be
+ * ambiguous.  No legitimate SVG path uses the hypothetical 'e' command, so
+ * skipping them causes no false positives. */
+static unsigned int
+_path_command_count(const char *p, char *cmds, unsigned int max)
+{
+   if (!p) return 0;
+   unsigned int n = 0;
+   for (; *p && n < max; p++)
+     {
+        /* Skip whitespace and comma separators. */
+        if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',')
+          continue;
+        /* Record recognised SVG command letters. */
+        if (strchr("MmLlHhVvCcSsQqTtAaZz", *p))
+          cmds[n++] = *p;
+        /* Everything else (digits, '.', '+', '-', exponents) is coordinate
+         * data — skip silently. */
+     }
+   return n;
+}
+
+/* Return EINA_TRUE if path_a and path_b have the same command-letter
+ * sequence (same length, same per-position command).  Coordinate values
+ * are intentionally ignored — they are expected to differ across states
+ * that are meant to morph. */
+static Eina_Bool
+_paths_topology_compatible(const char *path_a, const char *path_b)
+{
+   /* 2048 commands is an ample upper bound; real paths rarely exceed ~100. */
+   char cmds_a[2048], cmds_b[2048];
+   unsigned int n_a = _path_command_count(path_a, cmds_a, sizeof(cmds_a));
+   unsigned int n_b = _path_command_count(path_b, cmds_b, sizeof(cmds_b));
+   if (n_a != n_b) return EINA_FALSE;
+   for (unsigned int i = 0; i < n_a; i++)
+     if (cmds_a[i] != cmds_b[i]) return EINA_FALSE;
+   return EINA_TRUE;
+}
 
 /* Recursive walk over tree A's nodes; for each named node also present in
- * tree B, check whether the types match.  Emit VEC009 on mismatch. */
+ * tree B:
+ *   - emit VEC009 if the node types differ, OR
+ *   - emit VEC004 if both nodes are SHAPEs but their path command sequences
+ *     are topology-incompatible (different length or different command at
+ *     any position). */
 static void
-_vec009_walk(const Edje_Vg_Node *na, const Edje_Vg_Tree *tb,
-             const char *part_name,
-             const char *state_a, const char *state_b)
+_vec_walk_pair(const Edje_Vg_Node *na, const Edje_Vg_Tree *tb,
+               const char *part_name,
+               const char *state_a, const char *state_b)
 {
    if (!na) return;
 
    if (na->name)
      {
         Edje_Vg_Node *nb = _edje_vg_tree_node_by_name(tb, na->name);
-        if (nb && nb->type != na->type)
+        if (nb)
           {
-             edje_diag_emit("VEC009", EDJE_DIAG_WARN, NULL, 0,
-                            part_name, NULL,
-                            "node \"%s\" has different types across descriptions "
-                            "\"%s\" (%s) and \"%s\" (%s) — transition will step "
-                            "instead of interpolating",
-                            na->name,
-                            state_a, edje_vg_node_type_str(na->type),
-                            state_b, edje_vg_node_type_str(nb->type));
+             if (nb->type != na->type)
+               {
+                  /* VEC009: same name, different node types. */
+                  edje_diag_emit("VEC009", EDJE_DIAG_WARN, NULL, 0,
+                                 part_name, NULL,
+                                 "node \"%s\" has different types across descriptions "
+                                 "\"%s\" (%s) and \"%s\" (%s) — transition will step "
+                                 "instead of interpolating",
+                                 na->name,
+                                 state_a, edje_vg_node_type_str(na->type),
+                                 state_b, edje_vg_node_type_str(nb->type));
+               }
+             else if (na->type == EDJE_VG_NODE_SHAPE)
+               {
+                  /* VEC004: same name, same SHAPE type, but incompatible path
+                   * topology — runtime will step instead of morphing. */
+                  if (!_paths_topology_compatible(na->shape.path, nb->shape.path))
+                    {
+                       edje_diag_emit("VEC004", EDJE_DIAG_WARN, NULL, 0,
+                                      part_name, NULL,
+                                      "shape \"%s\" has topology-incompatible paths "
+                                      "across descriptions \"%s\" and \"%s\" — "
+                                      "transition will step instead of morphing. "
+                                      "Use -W vector-topology=error to promote.",
+                                      na->name, state_a, state_b);
+                    }
+               }
           }
      }
 
@@ -3064,22 +3135,23 @@ _vec009_walk(const Edje_Vg_Node *na, const Edje_Vg_Tree *tb,
         Eina_List *l;
         Edje_Vg_Node *child;
         EINA_LIST_FOREACH(na->container.children, l, child)
-          _vec009_walk(child, tb, part_name, state_a, state_b);
+          _vec_walk_pair(child, tb, part_name, state_a, state_b);
      }
 }
 
-/* Check one pair of descriptions for VEC009 type mismatches.
+/* Check one pair of descriptions for VEC009 type mismatches and VEC004
+ * path-topology mismatches.
  * tree_a/tree_b are the base trees (overrides not applied — documented
  * simplification: applying overrides at compile time would require
  * duplication; base-tree comparison is the pragmatic v1 approach). */
 static void
-_vec009_check_pair(const Edje_Vg_Tree *tree_a, const Edje_Vg_Tree *tree_b,
-                   const char *part_name,
-                   const char *state_a, const char *state_b)
+_vec_check_pair(const Edje_Vg_Tree *tree_a, const Edje_Vg_Tree *tree_b,
+                const char *part_name,
+                const char *state_a, const char *state_b)
 {
-   /* Walk A→B only: type mismatch is symmetric, so one direction catches
+   /* Walk A→B only: mismatches are symmetric, so one direction catches
     * all pairs where the name exists in both trees. */
-   _vec009_walk(tree_a->root, tree_b, part_name, state_a, state_b);
+   _vec_walk_pair(tree_a->root, tree_b, part_name, state_a, state_b);
 }
 
 /* Validate all VECTOR descriptions in a single part. */
@@ -3122,7 +3194,7 @@ _vec_check_part_descriptions(const Edje_Part_Collection *pc,
           }
      }
 
-   /* VEC009: walk all pairs of tree-based descriptions. */
+   /* VEC009 + VEC004: walk all pairs of tree-based descriptions. */
    if (!edje_file->vector_dir) return;
 
    for (i = 0; i < n; i++)
@@ -3152,7 +3224,7 @@ _vec_check_part_descriptions(const Edje_Part_Collection *pc,
              const Edje_Vg_Tree *tb =
                 &edje_file->vector_dir->trees[db->vg.tree_id];
 
-             _vec009_check_pair(ta, tb, ep->name, state_a, state_b);
+             _vec_check_pair(ta, tb, ep->name, state_a, state_b);
           }
      }
 
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index a313a9e3f2..16696b2c84 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -1881,16 +1881,22 @@ EFL_END_TEST
  *   - if expect_failure is EINA_TRUE, the process exited with a non-zero
  *     status (i.e. an error-level diagnostic was emitted).
  *
+ * extra_flags: optional additional flags passed to edje_cc (e.g. "-W
+ *   vector-topology=error").  Pass NULL for the default invocation.
+ *
  * Returns EINA_TRUE if both conditions hold, EINA_FALSE otherwise.
  * ========================================================================= */
 
 /* Path to the edje_cc binary in the in-tree build directory. */
 #define _EDJE_CC_BIN TESTS_BUILD_DIR "/../../bin/edje/edje_cc"
 
+/* Core implementation — accepts an optional extra_flags string that is
+ * spliced into the edje_cc command line before the input EDC path. */
 static Eina_Bool
-_compile_with_diag(const char *edc_src,
-                   const char *expected_code,
-                   Eina_Bool   expect_failure)
+_compile_with_diag_flags(const char *edc_src,
+                          const char *expected_code,
+                          Eina_Bool   expect_failure,
+                          const char *extra_flags)
 {
    /* Write EDC to a temp file. */
    char edc_path[PATH_MAX];
@@ -1902,11 +1908,12 @@ _compile_with_diag(const char *edc_src,
    fclose(f);
 
    /* Build command: redirect stderr to stdout so popen captures it.
-    * Send the output to /dev/null, capture diagnostics via stderr merge. */
+    * Send the output to /dev/null, capture diagnostics via stderr merge.
+    * extra_flags (if non-NULL) are inserted before the input path. */
    char cmd[PATH_MAX * 2];
    snprintf(cmd, sizeof(cmd),
-            "EFL_RUN_IN_TREE=1 %s -beta -fd /tmp '%s' /dev/null 2>&1",
-            _EDJE_CC_BIN, edc_path);
+            "EFL_RUN_IN_TREE=1 %s -beta -fd /tmp %s '%s' /dev/null 2>&1",
+            _EDJE_CC_BIN, extra_flags ? extra_flags : "", edc_path);
 
    FILE *p = popen(cmd, "r");
    if (!p)
@@ -1933,6 +1940,16 @@ _compile_with_diag(const char *edc_src,
    return EINA_TRUE;
 }
 
+/* Convenience wrapper — no extra flags (backwards-compatible signature used
+ * by all pre-Task-7.5 tests). */
+static Eina_Bool
+_compile_with_diag(const char *edc_src,
+                   const char *expected_code,
+                   Eina_Bool   expect_failure)
+{
+   return _compile_with_diag_flags(edc_src, expected_code, expect_failure, NULL);
+}
+
 /* VEC001 — duplicate node name within a tree: must error (non-zero exit). */
 EFL_START_TEST(edje_vg_diag_vec001_node_name_dup)
 {
@@ -2263,6 +2280,142 @@ EFL_START_TEST(edje_vg_diag_vec009_no_false_positive)
 }
 EFL_END_TEST
 
+/* =========================================================================
+ * Phase 7.5 — VEC004 path-topology diagnostic tests
+ * ========================================================================= */
+
+/* VEC004 trigger: two descriptions where shape "body" has paths with
+ * different command counts ("M L Z" = 3 commands vs "M L L Z" = 4 commands).
+ * VEC004 must fire as a warning; compilation must succeed (exit 0). */
+EFL_START_TEST(edje_vg_diag_vec004_topology_mismatch)
+{
+   static const char edc[] =
+      "collections {\n"
+      "   group { name: \"g\";\n"
+      "      parts {\n"
+      "         part { name: \"vg\"; type: VECTOR;\n"
+      "            description { state: \"default\" 0.0;\n"
+      "               vector {\n"
+      "                  shape { name: \"body\"; path: \"M0,0 L100,100 Z\";\n"
+      "                          fill.color: 200 0 0 255; }\n"
+      "               }\n"
+      "            }\n"
+      "            description { state: \"hover\" 0.0;\n"
+      "               vector {\n"
+      "                  shape { name: \"body\"; path: \"M50,0 L100,50 L50,100 Z\";\n"
+      "                          fill.color: 0 200 0 255; }\n"
+      "               }\n"
+      "            }\n"
+      "         }\n"
+      "      }\n"
+      "   }\n"
+      "}\n";
+
+   ck_assert_msg(_compile_with_diag(edc, "VEC004", EINA_FALSE),
+                 "VEC004 (path topology mismatch across descriptions) should warn "
+                 "(exit 0) when same-named shapes have incompatible command sequences");
+}
+EFL_END_TEST
+
+/* VEC004 positive control: two descriptions where shape "body" has the same
+ * command sequence ("M L Z") but different coordinates.  No VEC004 should
+ * fire; compilation should succeed cleanly. */
+EFL_START_TEST(edje_vg_diag_vec004_no_false_positive)
+{
+   static const char edc[] =
+      "collections {\n"
+      "   group { name: \"g\";\n"
+      "      parts {\n"
+      "         part { name: \"vg\"; type: VECTOR;\n"
+      "            description { state: \"default\" 0.0;\n"
+      "               vector {\n"
+      "                  shape { name: \"body\"; path: \"M0,0 L100,100 Z\";\n"
+      "                          fill.color: 200 0 0 255; }\n"
+      "               }\n"
+      "            }\n"
+      "            description { state: \"hover\" 0.0;\n"
+      "               vector {\n"
+      "                  shape { name: \"body\"; path: \"M50,50 L75,75 Z\";\n"
+      "                          fill.color: 0 200 0 255; }\n"
+      "               }\n"
+      "            }\n"
+      "         }\n"
+      "      }\n"
+      "   }\n"
+      "}\n";
+
+   /* Compile using popen so we can inspect both exit status and output. */
+   char edc_path[PATH_MAX];
+   snprintf(edc_path, sizeof(edc_path), "/tmp/edje_diag_test_%d_pos.edc",
+            (int)getpid());
+   FILE *fout = fopen(edc_path, "w");
+   if (!fout) return;
+   fputs(edc, fout);
+   fclose(fout);
+
+   char cmd[PATH_MAX * 2];
+   snprintf(cmd, sizeof(cmd),
+            "EFL_RUN_IN_TREE=1 " _EDJE_CC_BIN
+            " -beta -fd /tmp '%s' /dev/null 2>&1",
+            edc_path);
+
+   FILE *p = popen(cmd, "r");
+   Eina_Bool spurious = EINA_FALSE;
+   char buf[4096];
+   if (p)
+     {
+        while (fgets(buf, sizeof(buf), p))
+          {
+             if (strstr(buf, "VEC004"))
+               spurious = EINA_TRUE;
+          }
+     }
+   int exit_status = p ? pclose(p) : -1;
+   unlink(edc_path);
+
+   ck_assert_msg(exit_status == 0,
+                 "VEC004 positive control: compilation should succeed");
+   ck_assert_msg(!spurious,
+                 "VEC004 positive control: no VEC004 should fire when paths have "
+                 "the same command sequence (same topology, different coordinates)");
+}
+EFL_END_TEST
+
+/* VEC004 -W escalation: the same mismatched-topology fixture as
+ * edje_vg_diag_vec004_topology_mismatch, but compiled with
+ * "-W vector-topology=error".  The compiler must exit non-zero. */
+EFL_START_TEST(edje_vg_diag_vec004_werror_escalation)
+{
+   static const char edc[] =
+      "collections {\n"
+      "   group { name: \"g\";\n"
+      "      parts {\n"
+      "         part { name: \"vg\"; type: VECTOR;\n"
+      "            description { state: \"default\" 0.0;\n"
+      "               vector {\n"
+      "                  shape { name: \"body\"; path: \"M0,0 L100,100 Z\";\n"
+      "                          fill.color: 200 0 0 255; }\n"
+      "               }\n"
+      "            }\n"
+      "            description { state: \"hover\" 0.0;\n"
+      "               vector {\n"
+      "                  shape { name: \"body\"; path: \"M50,0 L100,50 L50,100 Z\";\n"
+      "                          fill.color: 0 200 0 255; }\n"
+      "               }\n"
+      "            }\n"
+      "         }\n"
+      "      }\n"
+      "   }\n"
+      "}\n";
+
+   ck_assert_msg(
+      _compile_with_diag_flags(edc, "VEC004", EINA_TRUE,
+                               "-W vector-topology=error"),
+      "VEC004 with -W vector-topology=error should promote the warning to an "
+      "error and make edje_cc exit non-zero");
+}
+EFL_END_TEST
+
 void
 edje_test_vector_states(TCase *tc)
 {
@@ -2312,4 +2465,8 @@ edje_test_vector_states(TCase *tc)
    tcase_add_test(tc, edje_vg_diag_vec007_lottie_info);
    tcase_add_test(tc, edje_vg_diag_vec009_type_mismatch);
    tcase_add_test(tc, edje_vg_diag_vec009_no_false_positive);
+   /* Phase 7.5 VEC004 path-topology diagnostics */
+   tcase_add_test(tc, edje_vg_diag_vec004_topology_mismatch);
+   tcase_add_test(tc, edje_vg_diag_vec004_no_false_positive);
+   tcase_add_test(tc, edje_vg_diag_vec004_werror_escalation);
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to