Decorators, accessors, and settings¶
Decorators preserve compiler-visible contracts. Accessors attach behavior to attribute access, and settings provide validated, frozen configuration within a context.
Design documentation
This chapter describes the language design. Examples are illustrative; implementation availability and unresolved rules are explained in Design status.
Decorators¶
Decorators support function and class transformations with compiler-visible signatures, effects, mutation permissions, and exceptions. Typed compile-time application is the working direction. A decorator cannot silently invalidate a purity or exception contract.
Ordinary wrapping decorators apply from the nearest declaration outward. Declaration markers such as accessors and fixture bindings have dedicated rules; they are not arbitrary runtime descriptor composition.
@classmethod and @staticmethod are supported for ordinary methods.
Getters and setters¶
Attribute access can invoke getters and assignment can invoke setters; there is no separate property construct. Instance and class accessors use explicit compiler designations:
@getter
fun api_key() -> Secret[string] { ... }
@classgetter
fun api_key() -> Secret[string] { ... }
Corresponding setter designations are @setter and @classsetter. Exactly one
receiver/accessor designation is permitted. Stacking @classmethod and @getter
is an error in either order. Getter/setter pairs agree on receiver kind and value
type; read and write visibility may differ.
Class accessors receive the class used for access and follow C3. Access through the class is the selected direction, avoiding ambiguous instance lookup. Other decorators can wrap an accessor only with support for its signature and effects.
Separate classproperty and staticproperty decorators are unnecessary. Static
accessors are deferred; a class getter can ignore its receiver.
Lazy values¶
The use case is client.api_key: fetch once, store privately, and reuse afterward.
Accessor syntax does not hide effects or declared exceptions. Ordinary cache
mutation requires mutable access.
A controlled initialize-once cell was proposed to support synchronized lazy storage: one initializer at a time, successful values retained, failures retryable, and recursive initialization detected. Class-level storage must specify whether it is shared by the hierarchy or belongs to each concrete class.
Open: the cell's exact API and whether it can exist in an otherwise frozen object. Externally sourced lazy initialization must not quietly undermine deep freezing, hashability, or purity/caching. This requires an explicit internal mutation contract rather than treating every getter as read-only or pure.
Settings¶
@settings defines typed configuration with construction, validation, deep
freezing, and shared read-only access. Initialization is explicit, may perform I/O,
and may raise declared exceptions. No partially initialized instance is published.
The compiler uses this mechanism to parse its own manifest:
@settings
class Manifest {
package: Package
dependencies: Dependencies
}
The loader parses the supported YAML subset, validates fields and types, applies declared defaults, rejects unknown fields, and freezes the result. Diagnostics include file, field path, and source position.
The singleton is per project/build context, not necessarily process-global. A compiler may handle several projects or manifests concurrently. Tests install separate frozen instances in isolated scopes. Cache contexts must distinguish settings instances; process-wide mutable overrides are not the model.
The bootstrap implementation and separation of schema decoding from contextual storage remain implementation questions.