Language reference

Properties and events

Raven uses a property-first model. val and var define the public mutability contract, while storage is an implementation detail.

Property kinds

  • val: publicly read-only after initialization.
  • var: publicly mutable after initialization.
  • Accessors may be omitted when the declaration contract fully defines the intended surface.

val may declare set/init accessors. A set accessor on val must be less accessible than the getter; otherwise Raven reports RAV0910. Within a scope where that setter is accessible, assignment, compound assignment and increment/decrement invoke it. The public val contract remains read-only; setter accessibility is checked separately from that contract. A var without any writable shape reports RAV0911. init remains compatible with public object-initializer assignment.

Storage properties

Storage properties are declarations without computed implementation:

val Name: string
var Count: int = 0

For storage properties, Raven can infer the property type from an initializer when the annotation is omitted:

class Foo {
    val x = 2 // inferred as int
}

Without an initializer, a type annotation is required (RAV0918).

The compiler synthesizes backing storage. A private var without an accessor list is writable storage even without a declaration initializer; it does not require a redundant initializer or setter to satisfy RAV0911.

Initialization is a separate concern. The RAV9006 analyzer accepts an initializer or assignment on every normally completing constructor path. An assignment in only one overload or one conditional branch is insufficient. Throwing paths do not produce an instance; early returns do. Assignments in deferred functions or to another instance do not initialize the current instance. Explicit getter-only computed properties still require a writable shape when declared var.

You can still provide accessors to refine behavior:

val Status: OrderStatus { private set; }
var Score: int {
    get => field
    set => field = max(0, value)
}

Accessor defaults:

Contract Getter Setter
val public none
var public public

When a storage property declares an accessor list that omits get, Raven still provides the getter required by the val or var contract. For example, val Status: OrderStatus { private set; } has a public getter and a private setter. For var, writable access is part of the public contract: explicit set and init accessors must match the property's accessibility. Explicit accessor lists are required when the property surface differs from the default val/var contract.

Computed properties

Computed properties provide implementation directly and do not use synthesized storage unless explicitly needed:

val FullName: string => first + " " + last

The init accessor

init permits assignment only during initialization and preserves val semantics:

val Name: string { init; }

Initialization includes inline initializers, constructors (init(...)), initializer blocks, and object initializers.

field and indexers

Inside storage-property accessors, field references the synthesized backing field. Using field outside an accessor reports RAV0912; using it in a property without backing storage reports RAV0913.

Indexers are a property form using self[...] and follow the same val/var, get/set/init, and accessibility rules:

var self[index: int]: string {
    get => items[index]
    set { items[index] = value }
}

Indexers require element access (items[index]), including imported indexers whose CLI property name is Item or another name. That metadata name is not an ordinary member-access expression: items.Item is invalid. Dot completion omits indexers and offers the element's members after items[index].. Ordinary parameterless properties named Item remain accessible by name. Symbol enumeration still exposes indexer metadata, with IsIndexer true and CanBeReferencedByName false.

This follows the distinction between property and indexer access in C#'s indexer model. The CLI property/accessor representation is unchanged.

Events

Events let a type notify any registered handlers when something happens, without knowing which objects are listening.

Events expose a delegate-like member that supports handler subscription via += and -=. An event declaration specifies the event keyword, a name, and the event handler type:

class Button {
    event Clicked: System.Action;
}

Custom event accessors

Custom events supply add and remove accessors, which receive the implicit value parameter of the handler type:

class Button {
    event Clicked: System.Action {
        add { /* register value */ }
        remove { /* unregister value */ }
    }
}

Auto-implemented events

When an event declaration ends with ;, the compiler synthesizes a hidden backing field and trivial add/remove accessors. Auto-events are the only events that can be invoked directly, and invocation is only permitted inside the declaring type:

class Button {
    event Clicked: System.Action? // Events can be null

    func Raise() -> unit {
        Clicked?();
    }
}

Interface indexer lookup includes inherited interfaces, with generic arguments substituted. A more-derived declaration hides an inherited indexer with the same signature before getter/setter availability is checked: redeclaring a read-only indexer does not expose the hidden setter. Equally applicable indexers from unrelated interfaces are ambiguous; select a specific interface explicitly.

Expression-bodied indexer emission

A getter-only indexer can put its expression directly on the declaration: val self[index: int]: int => index + 40. Class and struct indexers emit an ordinary read-only CLI Item property and getter, using the same expression-body lowering and sequence-point handling as properties. No Runtime Contract option is needed. Regression validation executes both receiver kinds on modern .NET; this is not a .NET Framework or NanoFramework execution claim.