Status
Accepted
Context
Struct fields previously supported a single “type hint” (effectively one tag) that could be used to validate values at runtime. This created a few limitations:
- Only one constraint could be expressed per field (e.g., you could say “this is a
@str” but not “this is a@strand also something else”). - Structs themselves were not consistently expressible as a first-class type tag in the same way other tagged types are.
- There was no clean way to express “this field must be a struct of this specific schema”.
As the language adds richer typing/tagging capabilities (and as codebases start to rely on more precise struct schemas), we want struct fields to support multiple tags and introduce a schema-aware struct tag.
Decision
1) Replace single struct field hint with a list of tags
Struct field typing metadata changes from a single string hint to a list of tags:
- AST struct fields now carry
Tags(a list) rather than a singleHint. - Struct schema fields store the same list of tags.
This enables multiple type constraints to be declared on a field (e.g., @str @fn fieldName, etc.), and keeps the representation aligned with the general “tag” concept.
2) Parse and preserve multiple tags on struct fields
The parser now collects all consecutive tags that appear before a struct field name, rather than only accepting one.
This allows field declarations like:
@str name@str @bool flags(illustrative example of multiple constraints)
3) Add @struct as a supported type tag (with optional schema specialization)
We introduce special handling and validation for struct typing:
@structmatches:- struct values, and
- struct schema definitions (where relevant)
@struct(User)additionally requires the struct’s schema name to matchUser- The argument may be provided as an identifier or as a string (e.g.,
@struct("User")).
- The argument may be provided as an identifier or as a string (e.g.,
This makes it possible to express “this must be a struct” and “this must be a struct of schema X” directly in field tags.
4) Define validation semantics: all tags must match (intersection)
When validating a struct instance against its schema, the field’s tags are treated with intersection semantics:
- If a field has multiple tags, every tag must match the provided value (unless existing semantics treat
nilas permissive in certain matching paths, as described below).
Special cases:
@fnmatches both function objects and function-group objects (preserving prior behavior).@struct/@struct(User)follows the struct-specific rules above.- Other tags use the existing tag-to-type mapping rules.
5) Validate tags at schema construction time
When building a schema, field tags are validated:
- Unknown tags are rejected.
@structis currently the only type tag that accepts parameters, and it must have at most one argument.
This ensures invalid type hint declarations fail early and with source-positioned errors.
Consequences
Positive
- More expressive schemas: multiple constraints per field are now possible.
- First-class struct tagging:
@structis supported like other type tags. - Schema-specific struct constraints:
@struct(Name)enables stronger validation and clearer intent. - Better alignment with the language’s tagging model: tags are consistently represented as tag objects rather than ad-hoc strings.
Negative
- Breaking internal representation change: anything expecting a single field hint must migrate to
Tags. - More complex validation logic: multiple tags and special cases add branching and require careful test coverage.
- Potential confusion around
nil: some matching paths treatnilas permissive to preserve existing behavior, which can surprise users expecting strict enforcement.
Neutral
- Debug/inspection output for struct fields now reports tags rather than a single hint value.
- Error messages may change slightly (e.g., referencing a tag string vs. a hint string), but remain source-positioned and actionable.