Skip to content

Kotlin codegen: generated *Table / *RepositoryBase reference a cross-package shared field.enum without importing it #246

Description

@dmealing

Summary

When two entities in different packages share one enum by pointing their fields at an abstract
field.enum via extends, the generated data classes emit the cross-package import correctly,
but the generated <Entity>Table and <Entity>RepositoryBase reference the enum by simple
name without importing it. The consuming module fails to compile.

extends itself works — the shared enum type is generated and the field resolves to it. Only the
import emission is inconsistent between generators.

This is the documented reuse mechanism ("Reuse a constraint set across entities with an abstract
field.enum + extends"), so in practice it only works when every consuming entity happens to sit
in the enum's own package.

Version: 7.8.0 (Maven), Kotlin + Exposed generators.

Reproduction

An abstract shared field in its own package:

# metadata/common/meta.record-status.yaml
metadata:
  package: acme::common
  children:
    - field.enum:
        name: RecordStatus
        abstract: true
        values: ["DRAFT", "ACTIVE", "CLOSED"]

Two entities in different packages extend it:

# metadata/orders/meta.order.yaml        (package acme::orders)
- field.enum: { name: "status", required: true, extends: "acme::common::RecordStatus" }

# metadata/billing/meta.invoice.yaml     (package acme::billing)
- field.enum: { name: "status", required: true, extends: "acme::common::RecordStatus" }

Then mvn generate-sources and compile the module.

Observed

acme/common/RecordStatus.kt is generated and the field type resolves to it. The import is emitted
by some generators and not others:

generated file references the enum imports it
orders/Order.kt (data class) yes
billing/Invoice.kt (data class) yes
projection data class over either entity yes
orders/OrderTable.kt yes
billing/InvoiceTable.kt yes
orders/OrderRepositoryBase.kt yes
billing/InvoiceRepositoryBase.kt yes

The table generator does emit cross-package imports for referenced tables, so the import
machinery is present — only the enum is missed:

package acme.orders

import org.jetbrains.exposed.sql.Table
import acme.billing.InvoiceTable        // cross-package TABLE import: present
// no import of acme.common.RecordStatus

object OrderTable : Table("orders") {
    val status = enumerationByName("status", 64, RecordStatus::class)   // unresolved
}

In our tree this produced 214 compile errorsUnresolved reference in each generated table,
cascading into argument-type-mismatch errors at every repository call site that passes the enum.

Expected

Any generated file that references a type resolved through extends should emit its import, matching
what the data-class generator already does.

Impact

There is no metadata-level workaround when the sharing entities live in different packages — the
abstract field can sit in at most one of them. Sharing one enum across packages is therefore not
expressible on 7.8.0, which leaves the vocabulary duplicated per entity and free to drift.

In our case the duplicated copies had already diverged (one had 10 members, the other 12) and the
divergence caused a silent runtime failure: a value legal in one table threw on write to the other,
inside a broad catch, so a whole record was dropped with only a warning logged. Consolidating the
declaration is what would have made that structurally impossible.

Separate observation — possibly its own issue

A child that declares its own values: alongside extends: has them silently ignored. No
warning, no error; the base's list simply wins. Measured on 7.8.0: a child declaring
extends: acme::common::RecordStatus plus values: ["ARCHIVED", "PURGED"] generated the base's
three members only.

If additive extension is intentionally unsupported (one shared enum meaning one shared type is a
reasonable design), then declaring both attributes should be a load error rather than a silent
drop — otherwise the authored metadata and the generated output disagree with nothing to signal it.
The wording of the authoring guidance ("reuse a constraint set across entities with an abstract
field.enum + extends") also reads as though additive extension might work, so a clarifying note
may be worthwhile either way.

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