vernac / architecture, expressed.
Browse documentation

Enum Value Object Contract

Status: normative contract for the 0.4.0 language revision. MUST and MUST NOT express requirements; implementation verification is tracked separately. MUST and MUST NOT express requirements, not claims about the current compiler.

Purpose and scope

An enum value object represents a named, non-empty, closed set of domain values. It is suitable for statuses, classifications, and kinds whose allowed values are part of the domain model. User-maintained or dynamically extensible catalogs need a different model.

The declaration defines possible values, not permitted state transitions. The responsible domain object enforces transitions and context-dependent invariants. An enum may offer domain-specific queries through author-defined methods.

Related contracts:

Declaration and type identity

namespace org.example.tasks;

value Status = PENDING | IN_PROGRESS | COMPLETED;

An enum MUST declare at least one constant. Declaration order MUST be preserved. Duplicate constants MUST be rejected, using the shared identifier comparison rules. No automatic renaming, numbering, case conversion, or implicit extra constants (such as UNKNOWN) are permitted.

Each declaration MUST generate one public Java enum in <namespace>.domain. The enum MUST be annotated with @NullMarked. It MUST NOT depend on a generated package-info.java for nullness.

Enum type names follow the same namespace, uniqueness, Unicode, and reserved built-in type-name rules as IDs and ordinary value objects. For example, an enum type named String is forbidden because String is an approved built-in type. Per-definition custom Java packages MUST NOT be supported. Legacy package-override syntax and associated enum generation paths must be removed rather than retained as compatibility behavior.

Constant names and separate name categories

UPPER_SNAKE_CASE is the recommended constant naming convention, not a mandatory language restriction. Valid Unicode member identifiers remain supported under the shared naming contract. Java keywords and other invalid Java member names MUST be diagnosed by Vernac. Vernac keywords MUST NOT introduce arbitrary extra restrictions where the shared naming contract permits their use.

Constants are values of their declaring enum. They do not declare types and do not refer to existing types merely because their names match.

The following declaration MUST be accepted:

namespace example;

value MeinTyp(int);
id MeineId;
value MeinEnum = MeinTyp | MeineId | String;

MeinEnum.MeinTyp, MeinEnum.MeineId, and MeinEnum.String are three constants of MeinEnum. Their names do not alter type resolution for MeinTyp, MeineId, or String in Vernac field and method signatures. The reservation of built-in TYPE names does not reserve those names for constants.

Different enums MAY use the same constant names. Constants MUST NOT be added as top-level type symbols or implicitly imported into other definitions. Vernac type imports remain type imports; they are not Java static imports.

Fields/constants and methods occupy different Java name categories. Consequently, constants named name, values, or toString are not, by themselves, collisions with name(), values(), or toString(). Checks MUST consider actual Java legality, not a shared blacklist of all API words.

Embedded Java name shadowing

A permitted constant can obscure a type name in expression context. Inside the example enum, String.valueOf(123) can resolve String as the enum constant rather than java.lang.String. java.lang.String.valueOf(123) disambiguates this example. The same issue can affect static factory calls on domain types.

Documentation MUST distinguish legal declaration names from Java expression shadowing. The generator MUST produce valid Java for permitted constant names, including in generated expressions. Analysis of arbitrary embedded Java bodies remains javac’s responsibility; Vernac does not promise Java-body name resolution.

Generated Java API and value semantics

Vernac MUST preserve the standard Java enum model:

Vernac MUST NOT automatically generate of, create, value, asString, or dbValue helpers for enums. A legal author-defined method may use such a name; removing a generated helper does not globally reserve or ban its name.

Without an author-defined override, toString() follows Java enum behavior. A legal author-defined toString() override is permitted. Neither toString() nor name() is promised as a stable external code across domain refactorings.

Author-defined behavior and API collisions

Authors MAY define public and private methods in a behavior block. See the behavior contract for self, separate implementation classes, scoped Java imports and external static implementations. Method signatures follow the shared resolved-type and nullness rules; no arbitrary Java types become available through this feature.

This contract does not introduce extra instance fields, constructors, constructor arguments per constant, constant-specific class bodies, mutable state, or abstract methods requiring implementations on individual constants. Enum construction validation blocks are not part of this model: every declared constant is a valid member of the set.

Vernac MUST diagnose duplicate or incompatible method declarations before Java generation where their declarations provide enough information. Checks MUST cover:

Methods MUST NOT be rejected solely because they share a name with an inherited method: legal overloads and legal overrides remain permitted. toString() is an example of an allowed override; name() is an example of a forbidden override. A constant named name is still legal.

Diagnostics MUST identify the declaration, conflicting member/signature, and reason. Suggested replacement names MUST NOT introduce known conflicts. Compiler and LSP MUST share the implementation rather than maintaining separate naming rules.

Use as a value object field

Enums MUST be usable as fields in ordinary single- and multi-field value objects. Same-file, same-namespace multi-file, imported, and fully qualified references MUST resolve through the shared Vernac symbol model.

namespace org.example.tasks;

value Status = PENDING | IN_PROGRESS | COMPLETED;
value TaskState(Status);
value TaskView(String title, Status, Status? previousStatus);

The default field name for Status is status; there is no single-field value exception. Explicit field names remain supported. Normal name-collision checks apply when multiple fields derive the same name.

Required enum fields follow ordinary VO required-null rejection. Optional enum fields accept a nullable input and expose Optional<Status> through the getter. Enums participate in the containing VO’s equality, hashing, and string rendering under its normal field rules. There is no implicit String-to-enum conversion.

External representations and persistence

The enum declaration MUST NOT contain database codes, transport codes, serialized aliases, or a special dbValue property. Previous code-assignment syntax and generated dbValue helpers are not part of the supported language or generated API.

An external representation belongs to the relevant persistence or adapter mapping. Different external systems may assign different representations to the same enum. The domain enum MUST NOT acquire one privileged external representation.

Stable storage codes, renaming and schema migration follow the persistence contract. DTO conversion follows the mapping contract. These mappings MUST NOT treat toString () or ordinal () as an implicit external contract. Unsupported conversions require a diagnostic or an explicit Java mapping.

ordinal() MUST NOT be used as the generated persistent representation. The presence of ordinal () on the Java API does not imply a stable domain or storage identifier.

Verification and documentation

Implementation work MUST include compiler, generated-Java, and LSP checks for:

Documentation and a tutorial MUST explain domain use, custom behavior, generated Java usage, equality, optional fields, name shadowing, and the distinction between constant names and external codes. Tooling review MUST cover highlighting, type completion, navigation, and diagnostics. Enum-constant completion/navigation in embedded Java requires explicit verification and must not be claimed solely because type navigation works.

Java reference

The Java name-category and enum behavior rules are grounded in the Java Language Specification, especially sections 6.4, 6.5, and 8.9: