schemagen emits duplicate enum/vector types when the same non-object $defs entry is referenced multiple times #17

Closed
opened 2026-06-21 11:26:00 +00:00 by weaselbot · 0 comments
Member

When a $defs entry that is not an object (e.g. an enum, an array, or an array of enum) is referenced from more than one place, the generator creates a fresh C++ type for every reference instead of reusing the definition.

Relevant code in contrib/schemagen/weaseljson_schemagen.py:

  • Lines 226-232 (build_def): only returns a cached value for entries found in self._building.
  • Line 317 (_build_object): self._building is only populated for object definitions while their fields are being built, so object refs are implicitly cached.
  • Lines 291-297 (enum branch of build_type) and the array branch at lines 304-309: always call self.unique_name(...) and create a new EnumType / TArr, even when defname is the same definition seen before.

Reproduction schema:

{
  "type": "object",
  "properties": {
    "role1": { "$ref": "#/$defs/Role" },
    "role2": { "$ref": "#/$defs/Role" },
    "roles1": { "$ref": "#/$defs/Roles" },
    "roles2": { "$ref": "#/$defs/Roles" }
  },
  "$defs": {
    "Role": { "enum": ["admin", "user"] },
    "Roles": { "type": "array", "items": { "$ref": "#/$defs/Role" } }
  }
}

Actual generated output (abridged):

enum class Role : int { admin, user };
enum class Role2 : int { admin, user };   // duplicate of Role

struct Root {
  std::optional<Role> role1;              // "role1"
  std::optional<Role2> role2;           // "role2"
  std::optional<std::vector<Role>> roles1; // "roles1"
  std::optional<std::vector<Role2>> roles2; // "roles2"
};

Expected output: a single Role enum and a single Roles / std::vector<Role> type, referenced by all four fields.

Impact:

  • Generated headers grow unnecessarily when schemas reuse enum/array definitions.
  • Fields that refer to the same logical enum get different C++ types, so user code cannot assign or compare them directly (r.role1 = r.role2 fails to compile).
  • Arrays-of-enum references also generate duplicate Arr* kinds and matching code in the builder, compounding the bloat.

This only affects non-object $defs entries; object refs already reuse the cached TObj via self._building.

When a `$defs` entry that is not an object (e.g. an enum, an array, or an array of enum) is referenced from more than one place, the generator creates a fresh C++ type for every reference instead of reusing the definition. **Relevant code in `contrib/schemagen/weaseljson_schemagen.py`:** - Lines 226-232 (`build_def`): only returns a cached value for entries found in `self._building`. - Line 317 (`_build_object`): `self._building` is only populated for object definitions while their fields are being built, so object refs are implicitly cached. - Lines 291-297 (enum branch of `build_type`) and the array branch at lines 304-309: always call `self.unique_name(...)` and create a new `EnumType` / `TArr`, even when `defname` is the same definition seen before. **Reproduction schema:** ```json { "type": "object", "properties": { "role1": { "$ref": "#/$defs/Role" }, "role2": { "$ref": "#/$defs/Role" }, "roles1": { "$ref": "#/$defs/Roles" }, "roles2": { "$ref": "#/$defs/Roles" } }, "$defs": { "Role": { "enum": ["admin", "user"] }, "Roles": { "type": "array", "items": { "$ref": "#/$defs/Role" } } } } ``` Actual generated output (abridged): ```cpp enum class Role : int { admin, user }; enum class Role2 : int { admin, user }; // duplicate of Role struct Root { std::optional<Role> role1; // "role1" std::optional<Role2> role2; // "role2" std::optional<std::vector<Role>> roles1; // "roles1" std::optional<std::vector<Role2>> roles2; // "roles2" }; ``` Expected output: a single `Role` enum and a single `Roles` / `std::vector<Role>` type, referenced by all four fields. **Impact:** - Generated headers grow unnecessarily when schemas reuse enum/array definitions. - Fields that refer to the same logical enum get *different* C++ types, so user code cannot assign or compare them directly (`r.role1 = r.role2` fails to compile). - Arrays-of-enum references also generate duplicate `Arr*` kinds and matching code in the builder, compounding the bloat. This only affects non-object `$defs` entries; object refs already reuse the cached `TObj` via `self._building`.
weaselbot was assigned by andrew 2026-06-24 00:46:11 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/weaseljson#17