Skip to content

Kotlin codegen: a field.enum inheriting values through TWO extends hops (projection → entity → shared) generates no enum at all #259

Description

@dmealing

Summary

A field.enum that reaches its values through two levels of extends generates nothing. Concretely: when a projection field extends an entity field, and that entity field itself extends a shared abstract enum, the projection's generated enum type disappears entirely and every consumer fails to resolve it.

Sibling of #246 (same feature area, different failure). #246 is "the shared enum generates but isn't imported"; this is "a second extends hop and it doesn't generate at all".

Reproduction

Two-level chain — Projection.statusEntity.statusShared — all field.enum:

# common/meta.character-status.yaml
- field.enum:
    name: CharacterStatusValue
    abstract: true
    required: true
    values: ["PRESENT", "ENGAGED", "HIDDEN", "DISGRACED", "FLED", "DEPARTED",
             "CAPTURED", "DEFEATED", "DEAD", "PENDING", "ARCHIVED", "DISMISSED"]

# npc/meta.active-npc.yaml  (entity — hop 1)
- field.enum: { name: "status", extends: "partylore::common::CharacterStatusValue" }

# npc/projections/meta.npc-character-view.yaml  (projection — hop 2, pre-existing)
- field.enum:
    name: status
    extends: "partylore::npc::ActiveNpc.status"

mvn generate-sources reports BUILD SUCCESS.

Actual

  • The shared CharacterStatusValue.kt generates correctly with all 12 values, and ActiveNpcTable / GameCharacterTable reference it (that part works — modulo Kotlin codegen: generated *Table / *RepositoryBase reference a cross-package shared field.enum without importing it #246's missing import).
  • NpcCharacterViewStatus.kt is not generated at all.
  • Every consumer of the projection's status column then fails to compile: Unresolved reference 'NpcCharacterViewStatus', plus knock-on Argument type mismatch: actual type is 'List<partylore.common.CharacterStatusValue>', but 'Iterable<T>' was expected at each call site.

Before the shared-enum change, the same projection field generated NpcCharacterViewStatus correctly from the entity's own inline values. So the regression is specifically the second hop.

Expected

A projection field extending an entity field whose values are themselves inherited should resolve transitively, and generate exactly as it does when the entity declares values inline.

Suggested fix

Likely an own-vs-effective accessor read when collecting enum values during codegen. Your own guidance calls this out (metaobjects-authoring skill, "Abstracts + extends"):

Always read a property or iterate a member set via the resolving/effective accessor … never an own*() accessor — an own-only read silently drops everything inherited via extends.

One hop works because the entity field's own node still carries values. Two hops fails because the middle node has none of its own — it inherits them. So the enum generator very likely reads own-values at the projection layer and finds an empty set, then skips emission.

Two asks beyond the fix itself:

  1. Fail loudly rather than silently. An enum field that resolves to zero values should be a load error (ERR_MISSING_REQUIRED_ATTR-style), not a silent no-emit. @values is documented as required on field.enum; a chain that resolves to none currently violates that invariant quietly. This is what made the failure expensive to diagnose — generate-sources was green and only a full compile revealed it.
  2. Conformance fixture for depth ≥ 2, covering projection→entity→shared. A one-hop fixture passes today and pins nothing about the chain.

Impact

Blocks adopting a shared enum in any project that also uses projections over the same entity — which is the common case, since a projection usually exists precisely to expose that entity's columns. In our tree it blocked converging three drifted copies of one status vocabulary; we shipped identical inline value sets plus a build-time equality test as the workaround, which is strictly worse than the type-level guarantee extends was going to provide.

Environment: metaobjects 7.8.0 (Maven), Kotlin/Exposed codegen, reproduced 2026-08-01.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions