aliasv2

CUE v0.18.0

Surface: per-file @experiment(...) attribute

Enablement

@experiment(aliasv2)

Lifecycle

preview since v0.15.0 · stable since v0.18.0

Description

AliasV2 enables the use of 'self' identifier to refer to the enclosing struct and enables the postfix alias syntax (~X and ~(K,V)). The file where this experiment is enabled disallows the use of old prefix alias syntax (X=). It is stable as of v0.18.0, so a file at that language version or later must use the postfix syntax.

Specification changes

What this experiment changes in the language specification, section by section. Removed and added text is marked in place.

Define self and postfix aliases

merged 2026-08-25 · v0.18.0

Lexical elements › Operators and punctuationIn the specification

The following character sequences represent operators and punctuation:

+     &&    ==    <     =     (     )
-     ||    !=    >     :     {     }
-     ||    !=    >     :     {     }     ~
*     &     =~    <=    ?     [     ]     ,
/     |     !~    >=    !     _|_   ...   .

Values › StructsIn the specification

6 unchanged blocks

A struct is a set of elements called fields, each of which has a name, called a label, and value.

We say a label is defined for a struct if the struct has a field with the corresponding label. The value for a label f of struct a is denoted a.f. A struct a is an instance of b, or a ⊑ b, if for any label f defined for b, label f is also defined for a and a.f ⊑ b.f. Note that if a is an instance of b it may have fields with labels that are not defined for b.

The (unique) struct with no fields, written {}, has every struct as an instance. It can be considered the type of all structs.

{a: 1} ⊑ {}
{a: 1, b: 1} ⊑ {a: 1}
{a: 1} ⊑ {a: int}
{a: 1, b: 1.0} ⊑ {a: int, b: number}

{} ⋢ {a: 1}
{a: 2} ⋢ {a: 1}
{a: 1} ⋢ {b: 1}

The successful unification of structs a and b is a new struct c which has all fields of both a and b, where the value of a field f in c is a.f & b.f if f is defined in both a and b, or just a.f or b.f if f is in just a or b, respectively. Any references to a or b in their respective field values need to be replaced with references to c. The result of a unification is bottom (_|_) if any of its defined fields evaluates to bottom, recursively.

A struct literal may contain multiple fields with the same label, the result of which is the unification of all those fields.

StructLit       = "{" { Declaration "," } "}" .
Declaration     = Field | Ellipsis | Embedding | LetClause | attribute .
Ellipsis        = "..." [ Expression ] .
Embedding       = Comprehension | AliasExpr .
Field           = Label ":" { Label ":" } AliasExpr { attribute } .
Label           = [ identifier "=" ] LabelExpr .
LabelExpr       = LabelName [ "?" | "!" ] | "[" AliasExpr "]" .
LabelName       = identifier | simple_string_lit | "(" AliasExpr ")" .
Embedding       = Comprehension | Expr .
Field           = Label ":" { Label ":" } Expr { attribute } .
Label           = LabelExpr [ Alias ] .
LabelExpr       = LabelName [ "?" | "!" ] | "[" Expr "]" .
LabelName       = identifier | simple_string_lit | "(" Expr ")" .

attribute       = "@" identifier "(" attr_tokens ")" .
attr_tokens     = { attr_token |
                    "(" attr_tokens ")" |
                    "[" attr_tokens "]" |
                    "{" attr_tokens "}" } .
attr_token      = /* any token except '(', ')', '[', ']', '{', or '}' */
cue
Expression                             Result
{a: int, a: 1}                         {a: 1}
{a: int} & {a: 1}                      {a: 1}
{a: >=1 & <=7} & {a: >=5 & <=9}        {a: >=5 & <=7}
{a: >=1 & <=7, a: >=5 & <=9}           {a: >=5 & <=7}

{a: 1} & {b: 2}                        {a: 1, b: 2}
{a: 1, b: int} & {b: 2}                {a: 1, b: 2}

{a: 1} & {a: 2}                        _|_

Values › Structs › AliasesIn the specification

Aliases name valuesbind identifiers to a field or its name that can be referred to within the scope in which they are declared. The name of an alias must be unique within its scope.

AliasExpr  = [ identifier "=" ] Expression .
Alias  = "~" ( identifier | "(" identifier [ "," ( identifier | "_" ) ] ")" ) .

Aliases can appear in several positions:

The alias is specified using a postfix ~ operator after a label. The single form ~V or ~(V) creates an alias referring to the field, while the dual form ~(K,V) creates two aliases: one for the label and one for the field. The parenthesized single form ~(V) is the canonical form; cue fmt normalizes ~V to ~(V). The first position in the dual form can use _ (blank identifier) to skip binding the label alias.

In front of a Label (X=label: value):

Aliases resolve as follows:

  • binds the identifier to the same value as label would be bound to if it were a valid identifier.

After a label (label~(V): value or label~(K, V): value):

In front of a dynamic field (X=(label): value):

  • binds the identifier V to the field itself (not its value).
  • binds the identifier K to the label.
  • binds the identifier to the same value as label if it were a valid static identifier.

After a dynamic field ((label)~(V): value or (label)~(K, V): value):

In front of a dynamic field expression ((X=expr): value):

  • binds the identifier V to the field itself.
  • binds the identifier K to the concrete label resulting from evaluating label.
  • the same semantics applies when binding to an interpolated string label.
  • binds the identifier to the concrete label resulting from evaluating expr.

After a pattern constraint ([expr]~(V): value or [expr]~(K, V): value):

In front of a pattern constraint (X=[expr]: value):

  • binds the identifier V to the field matched by the pattern within the scope of that field’s value (value).
  • binds the identifier K to the concrete label that matches expr within the instances of the field value (value).
  • binds the identifier to the same field as the matched by the pattern within the instance of the field value (value).

Note that aliases are equivalent to referring to the field by name. Use self to refer to a field’s value directly.

In front of a pattern constraint expression ([X=expr]: value):

foo: {
    let X = self
    // X refers to the value of foo
}
  • binds the identifier to the concrete label that matches expr within the instances of the field value (value).
@experiment(aliasv2)
 
// A field alias
result1: X  // 4
"not an identifier"~(X): 4

Before a value (foo: X=x)

@experiment(aliasv2)
 
// A field alias example
foo~(F): {
    x: F.a
}
bar: foo & {a: 1}  // {a: 1, x: 1}
  • binds the identifier to the value it precedes within the scope of that value.
@experiment(aliasv2)
 
// A label alias with dual form
[string]~(K, V): { name: K, add1: V.value+1 }
foo: { value: 1 } // outputs: foo: { name: "foo", value: 1, add1: 2 }

Before a list element ([ X=value, X+1 ]) (Not yet implemented)

@experiment(aliasv2)
 
// Dynamic field with dual form
("my" + "Field")~(K, V): { label: K, foo: 42 }
value: V.foo
// outputs: {myField: { label: "myField", value: 42 }, value: 42}
  • binds the identifier to the list element it precedes within the scope of the list expression.
// A field alias
foo: X  // 4
X="not an identifier": 4
 
// A value alias
foo: X={x: X.a}
bar: foo & {a: 1}  // {a: 1, x: 1}
 
// A label alias
[Y=string]: { name: Y }
foo: { value: 1 } // outputs: foo: { name: "foo", value: 1 }

Values › Structs › Let declarationsIn the specification

2 unchanged blocks

Let declarations bind an identifier to an expression. The identifier is only visible within the scope in which it is declared. The identifier must be unique within its scope.

cue
let x = expr

a: x + 1
b: x + 2

Let declarations are commonly used with the self keyword to create value aliases:

foo: {
    let V = self
    x: V.a
    y: V.b
}

Declarations and Scopes › Predeclared identifiersIn the specification

CUE predefines a set of types and builtin functions. For each of these there is a corresponding keyword which is the name of the predefined identifier, prefixed with __.

Functions
len close and or
len close and or, for more see builtins

Types
null      The null type and value
bool      All boolean values
int       All integral numbers
float     All decimal floating-point numbers
string    Any valid UTF-8 sequence
bytes     Any valid byte sequence

self      Refers to the inner scope
 
Derived   Value
number    int | float
uint      >=0
uint8     >=0 & <=255
int8      >=-128 & <=127
uint16    >=0 & <=65535
int16     >=-32_768 & <=32_767
rune      >=0 & <=0x10FFFF
uint32    >=0 & <=4_294_967_295
int32     >=-2_147_483_648 & <=2_147_483_647
uint64    >=0 & <=18_446_744_073_709_551_615
int64     >=-9_223_372_036_854_775_808 & <=9_223_372_036_854_775_807
uint128   >=0 & <=340_282_366_920_938_463_463_374_607_431_768_211_455
int128    >=-170_141_183_460_469_231_731_687_303_715_884_105_728 &
           <=170_141_183_460_469_231_731_687_303_715_884_105_727
float32   >=-3.40282346638528859811704183484516925440e+38 &
          <=3.40282346638528859811704183484516925440e+38
float64   >=-1.797693134862315708145274237317043567981e+308 &
          <=1.797693134862315708145274237317043567981e+308

Expressions › References › self (from v0.15.0)new section

The predeclared identifier self and __self refer to the innermost struct or list, or the top level if none exist.

a: {
    b: {
        c: self.d // refers to value of a.b.d (1)
        d: 1
    }
    d: self // refers to value of a (cyclic)
}
e: self // refers to top level (cyclic)
 
// lists
f: [ 1, 2, self[0] ]  // refers to value of f[0]
 
// implicit scope
g: x: self // refers to value of g
 
// naming using let
let X = self
h: a: b: c: X.f[0] // refers to the value of f[0] (1)
 
// predeclared identifier redeclared
i: self: x: y: z: self // refers to value of i.self (self redefined)
Install CUE