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
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 '}' */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 aslabelwould 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
Vto the field itself (not its value). - binds the identifier
Kto the label.
binds the identifier to the same value aslabelif 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
Vto the field itself. - binds the identifier
Kto the concrete label resulting from evaluatinglabel. - the same semantics applies when binding to an interpolated string label.
binds the identifier to the concrete label resulting from evaluatingexpr.
After a pattern constraint ([expr]~(V): value or [expr]~(K, V): value):
In front of a pattern constraint (X=[expr]: value):
- binds the identifier
Vto the field matched by the pattern within the scope of that field’s value (value). - binds the identifier
Kto the concrete label that matchesexprwithin 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 matchesexprwithin the instances of the field value (value).
@experiment(aliasv2)
// A field alias
result1: X // 4
"not an identifier"~(X): 4Before 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.
let x = expr
a: x + 1
b: x + 2Let 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+308Expressions › 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)