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 67c737468544bd71d1db1ba174f05ce1893c2f63
Author: [email protected] <[email protected]>
AuthorDate: Thu Apr 30 08:57:46 2026 -0600
edje: enforce VEC014 transform-form mutual exclusion (Phase 7.3)
Closes the Phase 2.5 carryover (project_edje_vg_phase7_vec014): mixing
transform.matrix: and any decomposed form (translate/rotate/scale) on the
same node within a single description's authoring is now a hard VEC014
error per spec §3.2. Inheritance remains allowed—a child description
overriding a parent's matrix with a decomposed component is valid.
Implementation uses a per-node bitset tracker (_vg_xform_seen, keyed by
node pointer via eina_hash_pointer_new) to detect conflicts. Each
transform handler (translate/rotate/scale/matrix) calls _vg_xform_track
with its form bit (VG_XFORM_BIT_DECOMPOSED or VG_XFORM_BIT_MATRIX) after
assignments. The tracker clears at description entry via
eina_hash_free_buckets in ob_collections_group_parts_part_description,
ensuring VEC014 only fires for conflicts within the single description's
own statements.
The has_matrix flag toggling in all four handlers is preserved; it still
serves Phase 4.4's inheritance arbitration and remains harmless within
a single description where VEC014 prevents the mixing.
New tests: edje_vg_diag_vec014_transform_conflict (matrix + rotate in
one description) and edje_vg_diag_vec014_inherit_allowed (parent matrix
+ child rotate across descriptions). All 74/74 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/bin/edje/edje_cc_handlers.c | 48 ++++++++++++++++++++++
src/tests/edje/edje_test_vector_states.c | 68 ++++++++++++++++++++++++++++++++
2 files changed, 116 insertions(+)
diff --git a/src/bin/edje/edje_cc_handlers.c b/src/bin/edje/edje_cc_handlers.c
index adf7b29083..f80a843e04 100644
--- a/src/bin/edje/edje_cc_handlers.c
+++ b/src/bin/edje/edje_cc_handlers.c
@@ -200,6 +200,41 @@ static Edje_Vg_Override *current_vg_override = NULL;
do { if (current_vg_override) \
current_vg_override->field_mask |= EDJE_VG_OVR_##BIT; } while (0)
+/* Phase 7.3 — VEC014 transform-form mutual exclusion. Tracks which form
+ * ({DECOMPOSED, MATRIX}) of transform statement has fired on each node since
+ * the current description was entered. Cleared at description entry.
+ * Hash key: node pointer value (passed as ¤t_vg_node so eina copies the
+ * 8-byte pointer value). Stored value: (uintptr_t bitset + 1) to distinguish
+ * "no entry" (NULL) from "entry with 0 bits set" (impossible but defensive). */
+static Eina_Hash *_vg_xform_seen = NULL;
+
+#define VG_XFORM_BIT_DECOMPOSED 0x1
+#define VG_XFORM_BIT_MATRIX 0x2
+
+static void
+_vg_xform_track(int new_bit)
+{
+ if (!current_vg_node) return;
+ if (!_vg_xform_seen)
+ _vg_xform_seen = eina_hash_pointer_new(NULL);
+
+ uintptr_t encoded = (uintptr_t)eina_hash_find(_vg_xform_seen,
+ ¤t_vg_node);
+ uintptr_t prev = encoded ? (encoded - 1) : 0;
+
+ if (((prev & VG_XFORM_BIT_MATRIX) && (new_bit & VG_XFORM_BIT_DECOMPOSED)) ||
+ ((prev & VG_XFORM_BIT_DECOMPOSED) && (new_bit & VG_XFORM_BIT_MATRIX)))
+ {
+ edje_diag_emit("VEC014", EDJE_DIAG_ERR, file_in, line, NULL, NULL,
+ "transform.matrix and decomposed transform "
+ "(translate/rotate/scale) are mutually exclusive on "
+ "the same node within one description");
+ }
+
+ uintptr_t updated = prev | (uintptr_t)new_bit;
+ eina_hash_set(_vg_xform_seen, ¤t_vg_node, (void *)(updated + 1));
+}
+
/* Record a description → tree linkage in the side table. */
static void
_vg_record_use(Edje_Part_Description_Vector *ed, Edje_Vg_Tree *t)
@@ -247,11 +282,14 @@ _edje_cc_vg_cleanup(void)
if (current_vg_node_stack) eina_array_free(current_vg_node_stack);
if (toplevel_vg_trees) eina_hash_free(toplevel_vg_trees);
if (_vg_desc_to_tree) eina_hash_free(_vg_desc_to_tree);
+ /* Phase 7.3: free the per-description transform-statement tracker. */
+ if (_vg_xform_seen) eina_hash_free(_vg_xform_seen);
current_vg_tree = NULL;
current_vg_node = NULL;
current_vg_node_stack = NULL;
toplevel_vg_trees = NULL;
_vg_desc_to_tree = NULL;
+ _vg_xform_seen = NULL;
}
Eina_List *po_files;
@@ -2393,6 +2431,7 @@ st_vg_node_transform_translate(void)
current_vg_node->xform.ty = parse_float(1);
current_vg_node->xform.has_matrix = 0;
VG_FLAG(TRANSFORM);
+ _vg_xform_track(VG_XFORM_BIT_DECOMPOSED);
}
/**
@@ -2414,6 +2453,7 @@ st_vg_node_transform_rotate(void)
current_vg_node->xform.angle = parse_float(0);
current_vg_node->xform.has_matrix = 0;
VG_FLAG(TRANSFORM);
+ _vg_xform_track(VG_XFORM_BIT_DECOMPOSED);
}
/**
@@ -2436,6 +2476,7 @@ st_vg_node_transform_scale(void)
current_vg_node->xform.sy = parse_float(1);
current_vg_node->xform.has_matrix = 0;
VG_FLAG(TRANSFORM);
+ _vg_xform_track(VG_XFORM_BIT_DECOMPOSED);
}
/**
@@ -2461,6 +2502,7 @@ st_vg_node_transform_matrix(void)
current_vg_node->xform.m[i] = parse_float(i);
current_vg_node->xform.has_matrix = 1;
VG_FLAG(TRANSFORM);
+ _vg_xform_track(VG_XFORM_BIT_MATRIX);
}
/* -----------------------------------------------------------------------
@@ -10050,6 +10092,12 @@ ob_collections_group_parts_part_description(void)
ed->minmul.have = 1;
ed->minmul.w = FROM_DOUBLE(1.0);
ed->minmul.h = FROM_DOUBLE(1.0);
+
+ /* Phase 7.3: clear the per-node transform-statement bitset so that VEC014
+ * only fires for conflicts within this single description's own statements.
+ * Inheritance is exempt: a child overriding a parent's matrix with a
+ * decomposed form is valid and sees a clean tracker here. */
+ if (_vg_xform_seen) eina_hash_free_buckets(_vg_xform_seen);
}
static void
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index dc27bf0ae6..fc25a558a2 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -2016,6 +2016,71 @@ EFL_START_TEST(edje_vg_diag_vec012_missing_fill)
}
EFL_END_TEST
+/* VEC014 — transform.matrix and decomposed form on the same node within one
+ * description: must error (non-zero exit). */
+EFL_START_TEST(edje_vg_diag_vec014_transform_conflict)
+{
+ static const char edc[] =
+ "collections {\n"
+ " vectors { vector { name: \"t\"; viewbox: 0 0 1 1;\n"
+ " shape { name: \"s\";\n"
+ " path: \"M0,0 L1,1\";\n"
+ " fill.color: 255 0 0 255;\n"
+ " transform.matrix: 1 0 0 0 1 0 0 0 1;\n"
+ " transform.rotate: 30;\n"
+ " }\n"
+ " } }\n"
+ " group { name: \"x\"; parts { part { name: \"v\"; type: VECTOR;\n"
+ " description { state: \"default\" 0.0; vector.use: \"t\"; }\n"
+ " } } }\n"
+ "}\n";
+ ck_assert_msg(_compile_with_diag(edc, "VEC014", EINA_TRUE),
+ "VEC014 (transform.matrix + decomposed in same desc) should error");
+}
+EFL_END_TEST
+
+/* VEC014 positive control — parent has matrix, child overrides with decomposed
+ * in a DIFFERENT description. Inheritance is exempt: must compile cleanly. */
+EFL_START_TEST(edje_vg_diag_vec014_inherit_allowed)
+{
+ static const char edc[] =
+ "collections {\n"
+ " vectors { vector { name: \"t\"; viewbox: 0 0 1 1;\n"
+ " shape { name: \"s\";\n"
+ " path: \"M0,0 L1,1\";\n"
+ " fill.color: 255 0 0 255;\n"
+ " transform.matrix: 1 0 0 0 1 0 0 0 1;\n"
+ " }\n"
+ " } }\n"
+ " group { name: \"x\"; parts { part { name: \"v\"; type: VECTOR;\n"
+ " description { state: \"default\" 0.0; vector.use: \"t\"; }\n"
+ " description { state: \"alt\" 0.0;\n"
+ " inherit: \"default\" 0.0;\n"
+ " vector.shape { target: \"s\"; transform.rotate: 45; }\n"
+ " }\n"
+ " } } }\n"
+ "}\n";
+ /* Write EDC and compile; expect zero exit (clean). */
+ char edc_path[PATH_MAX];
+ snprintf(edc_path, sizeof(edc_path), "/tmp/edje_diag_vec014_ok_%d.edc",
+ (int)getpid());
+ FILE *f = fopen(edc_path, "w");
+ fail_if(!f);
+ fputs(edc, f);
+ fclose(f);
+
+ char cmd[PATH_MAX * 2];
+ snprintf(cmd, sizeof(cmd),
+ "EFL_RUN_IN_TREE=1 %s -beta -fd /tmp '%s' /dev/null 2>/dev/null",
+ _EDJE_CC_BIN, edc_path);
+ int ret = system(cmd);
+ unlink(edc_path);
+
+ ck_assert_msg(ret == 0,
+ "VEC014: cross-description matrix+decomposed should compile clean");
+}
+EFL_END_TEST
+
void
edje_test_vector_states(TCase *tc)
{
@@ -2058,4 +2123,7 @@ edje_test_vector_states(TCase *tc)
tcase_add_test(tc, edje_vg_diag_vec003_invalid_path);
tcase_add_test(tc, edje_vg_diag_vec011_undeclared_class);
tcase_add_test(tc, edje_vg_diag_vec012_missing_fill);
+ /* Phase 7.3 VEC014 transform-form mutual exclusion */
+ tcase_add_test(tc, edje_vg_diag_vec014_transform_conflict);
+ tcase_add_test(tc, edje_vg_diag_vec014_inherit_allowed);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.