Specification - Spec - MD at Main Val-Lang - Specification
Specification - Spec - MD at Main Val-Lang - Specification
main
specification / spec.md
3 contributors
Introduction
Val is a research language based on the principles of mutable value semantics (MVS)
(Racordon et al. 2022) for safety and efficiency. It is designed to help developers write and
maintain correct programs using powerful abstractions without loss of efficiency.
Lexical conventions
Program text
1. A Val program is written in text format. The text of a program is written using the
Unicode character set and kept in units called source files. A source file is a sequence
of Unicode Extended Grapheme clusters.
2. This document refers to individual Unicode characters with the notation U+n where
n is a hexadecimal value representing a Unicode code point, and refers to the
Unicode general categories to identify groups of Unicode characters.
Lexical translations
1. A sequence of Unicode characters is translated into a sequence of tokens. The
following
2725 rulessloc)
lines (1786 apply during
103 KB translation:
ii. Spaces are ignored unless they appear between the opening and closing
delimiters of a character or string literal. Unicode characters with the
"White_Space" property are recognized as spaces.
iii. New-line delimiters are ignored unless they appear between the opening and
closing delimiters of a character or string literal. Unicode characters with the
"Line_Break" property are recognized as new-line delimiters.
Comments
1. The character sequence // starts a single-line comment, which terminates
immediately before the next new-line delimiter.
2. The character sequences /* and */ are multiline comment opening and closing
delimiters, respectively. A multiline comment opening delimiter starts a comment that
terminates immediately after a matching closing delimiter. Each opening delimiter
must have a matching closing delimiter. Multiline comments may nest and need not
contain any new-line characters. [Note: The character sequences // have no special
meaning in a multiline comment. The character sequences /* and */ have no
special meaning in a single-line comment. The character sequences // and /* have
no special meaning in a string literal. String and character literal delimiters have no
special meaning in a comment.]
Tokens
1. A token is a terminal symbol of the syntactic grammar. It falls into one of five
categories: scalar literals, keywords, identifiers, raw operators, and punctuators.
3. (Example)
The input "a << b" is translated to a sequence of 4 tokens: identifier, raw-operator,
raw-operator, identifier. The first and third tokens are known to be followed by an
inline space.
4. Unless otherwise specified, the token recognized at a given lexical position is the one
having the longest possible sequence of characters.
Scalar literals
Boolean literals
Integer literals
integer-literal ::=
binary-literal
octal-literal
decimal-literal
hexadecimal-literal
2. The sequence of digits of a literal are interpreted as follows, ignoring all occurrences
of _ :
3. (Example)
The integer literal 0o12_34_5__ is interpreted as the integer 5349 in base 10.
4. The default inferred type of an integer literal is the Val standard library Int , which
represents a 64-bit signed integer. If the interpreted value of an integer literal is not in
the range of representable values for its type, the program is ill-formed.
Floating-point literals
3. The default inferred type of an integer literal is the Val standard library Double , which
represents a 64-bit floating point number. If the interpreted value of a floating-point
literal is not in the range of representable values for its type, the program is ill-formed.
Otherwise, the value of a floating-point literal is the interpreted value if representable,
else the larger or smaller representable value nearest the interpreted value, chosen in
an implementation-defined manner.
escape-char ::=
simple-escape
unicode-escape
String literals
simple-quoted-text-item ::=
escape-char
s-char
multiline-quoted-text-item ::=
escape-char
m-char
2. The first new-line delimiter in a multiline string literal is not part of the value of that
literal if it immediately succeeds the opening delimiter. The last new-line delimiter that
is succeeded by a contiguous sequence of inline spaces followed by the closing
delimiter is called the indentation marker. The indentation marker and the succeeding
inline spaces specify the indentation pattern of the literal and are not part of its value.
The pattern is defined as the sequence of inline spaces between the indentation
marker and the closing delimiter. That sequence must be homogeneous. If the literal
has no indentation marker, its indentation pattern is an empty sequence. Each line of a
multiline string literal must begin with the indentation pattern of that literal. That
prefix is not part of the value of the literal.
3. (Example)
let s = """
Hello,
World!
"""
fun main() {
print(s) // " Hello,\n World!"
}
Identifiers
1. Identifiers are case-sensitive sequences of letters and digits. They have the form:
identifier ::=
identifier-token
contextual-keyword
2. The following identifiers are reserved keywords and shall not be used to name new
entities. [Note: A keyword wrapped in backquotes may be used to name entities.]
Any Self Never as as! any async await break catch conformance continue deinit
do else extension
false for fun if import in indirect infix init inout let match namespace nil
operator postfix
prefix property public return set sink some static subscript trait true try
type typealias var
where while yield yielded
Raw operators
1. Raw operators have the form:
General concepts
Preamble
1. An entity is an object, projection, function, subscript, property, trait, type, namespace,
or module.
3. (Example) foo and + are bare names. foo(bar:ham:) is a function name. infix+ is
an operator name. foo.let and foo(bar:ham:).let . are method names.
5. A binding is a name that denotes an object or projection. The value of a binding is the
object denoted by that binding or the value of the projection denoted by that binding.
A binding shall be mutable or immutable. A mutable binding shall be used to modify
its value; an immutable binding shall not. A binding is dead at a given program point if
it denotes an object that has escaped, or if there are no uses of it at that program
point and any other program point reachable from there. A mutable binding may be
resurrected by reassigning it to an object; an immutable binding may not.
3. A lexical scope l1 contains a lexical scope l2 if the region of the program text
covered by l1 includes that covered by l2 . The innermost lexical scope that
contains a lexical scope l1 and that is not l1 is called the parent of l1 .
5. The declaration space of a scope is the set of names introduced in that scope and its
siblings. The declaration space of a declaration is the declaration space of its lexical
scope.
6. (Example)
type A {
fun foo() {}
}
extension A {
fun bar() {}
}
The declarations space of the type declaration includes foo and bar .
7. A declaration may introduce one or more names in the declaration space of the
innermost lexical scope that contains it. A name is said to be conditionally introduced
if it is introduced by an extension or conformance declaration that has a where clause;
otherwise, it is said to be unconditionally introduced. The same name shall not be
unconditionally introduced more than once in a declaration space.
type A {
fun foo() {}
}
extension A {
fun foo() {} // error: invalid redeclaration of 'foo'
}
Name lookup
1. The procedure that identifies the entity denoted by a name is called name lookup. The
rules of name lookup apply uniformly to all names.
2. Name lookup succeeds when it finds a single entity or a set of functions, which is
called an overload set. Overload resolution takes place after name lookup and
identifies a single entity from an overload set using the context in which the name
appears.
3. Access rules are considered only once name lookup and overload resolution have
succeeded.
If qlookup(n, s) = E and:
otherwise:
if s is the lexical scope of a module declaration other than the Val standard
library, ulookup(n, s) = ulookup(n, s') where s' is the module
declaration of the Val standard library; or
If qslookup(n, s) = E and:
otherwise, qslookup(n, s) = {} .
3. (Example)
type A {
var a: Int
fun foo(a: Int) { a.copy() }
fun foo(b: Int) -> Int {
let { a + b }
inout { b += a }
}
}
Qualified name lookup for foo(a:) in the lexical scope s of the declaration of A
starts with a qualified stem name lookup for foo(a:) in s . That search returns a set
with three entities named foo(a:) , foo(b:).let , and foo(b:).inout . Since foo(a:)
is a method name, the two last entities are discarded and the seach concludes with a
singleton.
1. Qualified stem name lookup qslookup(n, s) for a name n in a local or global scope
s is the set containing the entities such that there exists a name in the declaration
space of s whose stem identifier is equal to the stem identifier of n .
In a type scope
1. The names introduced in the declaration space of the declaration of a trait, nominal
product type, or type alias are members of the declared entity.
2. The members of a type are members of its aliases.
3. The members of a trait T are members of all the traits that T refines and all the
types that conform to T .
4. The members of a generic type parameter are members of all the generic type
parameters and associated types that are part of its equivalence class.
2. A name is said to have linkage when it may denote the same entity as a name
introduced by a declaration in another scope.
i. When a name has external linkage, the entity it denotes may be referred to by
names from any scope contained in other module declarations or from other
scopes of the same module declaration.
ii. When a name has module linkage, the entity it denotes can be referred to by
names from any scope in the same module declaration.
iii. When a name has internal linkage, the entity it denotes can be referred to by
names from any scope contained in the scope that contains its declaration.
Memory model
1. The fundamental storage unit in the Val memory model is the byte. A byte is a
contiguous sequence of 8 bits. The memory available to a Val program consists of one
or more sequences of contiguous bytes. Every byte has a unique address.
2. A memory location is a contiguous region of storage that has been allocated during a
program's execution. A memory location has a size. A non-empty memory location
has an address, determined as the address of the first byte in that location.
3. Two memory locations are disjoint if and only if they denote disjoint regions of
storage. A memory location l1 contains another memory location l2 if and only if
the region of storage denoted by l1 contains the region denoted by l2 . Two
memory locations shall be disjoint or one shall contain the other.
5. A root memory location may be bound to a type A . Such a memory location shall
have a size and alignment suitable to store an object of type A and shall not contain
any other memory location. Otherwise, the behavior is undefined. The memory
locations contained in a memory location bound to A are bound according to the
memory layout of A .
ii. A memory location allocated for an object declared by a local binding has
automatic lifetime and is called an automatic memory location.
4. When the end of the lifetime a memory location is reached, all names denoting
objects stored within that location become invalid. Deallocating a memory location
that has already reached the end of its lifetime has undefined behavior.
Objects
1. The constructs in a Val program create, destroy, project, access, and modify objects. An
object is the result of a scalar literal expression, aggregate literal expression, function
call, sink subscript call, sink property call, or it is the value of a unique binding.
[Note: A function is not an object but a lambda is.]
i. o1 is a sub-object of o2 ; or
ii. there exists an object o3 such that o1 is nested within o3 and o3 is nested
within o2 .
6. For every object o , there exists a single root object o , determined as follows:
ii. otherwise, the root object of o is the root object of the object that contains o .
7. An object has non-zero size if and only if its type has non-zero size. An object that has
non-zero size occupies one or more bytes of contiguous storage, including every byte
that is occupied in full or in part by any of its sub-objects.
8. Unless an object is a sub-object of zero size, the address of that object is the address
of the memory location it occupies. Two objects with overlapping lifetimes may have
the same address if one is nested within the other, or if at least one is a sub-object
that has zero size; otherwise, they have distinct addresses and occupy disjoint memory
locations.
Object lifetime
1. The lifetime of an object of type A begins when:
2. The lifetime of an object of type A ends when a bound call to any of its sink methods
has returned or when the memory location that it occupies has reach the end of its
lifetime.
3. The properties ascribed to objects throughout this document apply for a given object
only during its lifetime. [Note: The behavior of an object under construction and
destruction might not be the same as the behavior of an object whose lifetime has
started and not ended.]
4. An object is said to be initializing in the period after its memory location has been
bound and before its lifetime has started, alive throughout its lifetime, deinitializing
during a bound call to a sink method of that object, and dead in the period after its
lifetime has ended and before its storage has been rebound, reused, or deallocated.
5. If, after the lifetime of an object has ended and before the memory location which the
object occupied is reused or deallocated, a new object is stored at the memory
location which the original object occupied, the name of the original object
automatically denotes the new object and, once the lifetime of the new object has
started, can be used to manipulate the new object.
Object alignment
1. Object types have alignment requirements which place restrictions on the addresses
of the memory locations which an object of that type may occupy. An alignment is a
platform-specific integer value representing the number of bytes between successive
addresses at which a given object can be allocated. An object type imposes an
alignment requirement on every object of that type.
2. Alignments are represented as values of the type Int . Valid alignments include only
those values of MemoryLayout<A>.alignnment for any type A plus the value of
Pointer.universal_alignment . Every alignment value shall be a non-negative integral
power of two.
3. Alignments have an order from weaker to stricter alignments. Stricter alignments have
larger alignment values. An address that satisfies an alignment requirement also
satisfies any weaker valid alignment requirement.
i. Two alignments are equal when their numeric values are equal.
ii. Two alignments are different when their numeric values are not equal.
Escapability
1. An binding is sinkable when it denotes an object whose type conforms to the
Sinkable trait. A binding that is not sinkable is unsinkable .
6. A sinkable binding is escapable at a given program point if it has no use after that
program point, and all bindings denoting the same object or sub-objects thereof are
escapable at that program point.
Projections
1. A projection exposes an object yielded by a subscript call or property access.
2. The value of a projection is the object exposed by that projection. A projection has the
type of its value.
5. (Example)
fun main() {
let x = A()
let y = x.zero
// 'y' projects 'x'
// corollary: 'x' is projected by 'y'
}
6. The projection yielded by a let or inout subscript call or property access projects
the arguments bound to the subscript or property's parameters.
7. (Example)
fun main() {
let (x, y) = (2, 3)
let z = min[x, y] // 'z' projects both 'x' and 'y'
print(z)
}
The expression min[x, y] is a call to the let implementation of min , which projects
the values of its arguments.
8. If a projection p projects an object o immutably, o is immutable for the duration of
p 's lifetime. If a projection p projects an object o mutably, o is inaccessible for the
duration of p 's lifetime.
fun f1() {
var x = 42
inout y = x // mutable projection begins here
print(x) // error: 'x' is projected mutably
x += 1 // error: 'x' is projected mutably
print(y) // mutable projection ends afterward
}
fun f2() {
var x = 42
let y = y // immutable projection begins here
print(x) // OK
x += 1 // error: 'x' is projected immutably
print(y) // immutable projection ends afterward
}
Modules
1. A Val program organizes code into modules. A module is a collection of declarations
and import statements defined in one or multiple source files. An individual source file
has the form:
source-file ::=
whitespace* (module-scope-stmt-list whitespace*)?
module-scope-stmt ::=
module-scope-decl
import-decl
module-scope-decl ::=
namespace-decl
trait-decl
type-alias-decl
product-type-decl
extension-decl
conformance-decl
binding-decl
function-decl
subscript-decl
operator-decl
import-decl ::=
'import' identifier
2. A module is called an entry module if it defines a public global function named main
with type () -> Unit . A program shall contain exactly one entry module.
Program execution
1. Executing a program starts a thread of execution in which the main function of its
entry module is invoked. [Note: the arguments passed to the program from the
environment in which it is run are stored in the global variable
Val.Environment.Arguments .]
Sequential execution
1. The immediate sub-expressions of an expression e are:
i. the operands of e ,
iv. if e is a function or subscript call, the expression of each default argument used
in the call.
Types
General
1. The Val language is statically typed. Every entity has a type that is known at compile
time. The type of an entity is immutable.
2. A type has a canonical form. Two types are equivalent if and only if they have the
same canonical form. A type is either structural or nominal. A nominal type has a name
and is defined by a type declaration, unless it is a built-in type. [Note: The types Any
and Never are not considered nominal types.]
5. The object representation of an object is the set of bytes that it occupies in storage.
The value representation of an object of type A is the set of bits that participate in
representing a value of type A . Bits in the object representation that are not part of
the value representation are called padding bits.
Built-in types
1. A built-in type is an instantiable nominal type representing a value on the execution
machine. A built-in type may be referred to only in Val standard library and shall not
conform to any trait.
2. A built-in type has non-zero size. The value representation of a built-in type
determines its value.
3. A built-in integer types denotes a bit pattern and does not specify signedness. There
are six built-in integer types. Five are named Builtin.I{n} where n is 1 , 8 , 16 ,
32 , or 64 and denotes the number of bits in the value representation of the type;
the sixth is named Builtin.Word and has the same value representation as either
Builtin.I32 or Builtin.I64 depending on the execution machine. The object
representation of a built-in integer type is the minimum number of bytes necessary to
store its value representation.
Subtyping relation
1. The types form a lattice, partially ordered by a subtyping relation <: , for which the
least (and only) upper bound is Any and the greatest (and only) lower bound is
Never .
ii. the type of each parameter of A2 is subtype of the type of the corresponding
parameter of A1 ; and
7. If A1 and A2 are existential types, A1 <: A2 if the traits of A2 are coarser than the
traits of A1 and the associated types of A1 are equivalent to the associated types of
A2 .
9. (Example)
(A | B) <: (A | B | C)
[E] ((A2) -> B1) <: ((A1) -> B2) (where A1 <: A2 and B1 <: B2 )
(any T & U where ::T.Element == Int) <: (any T where ::T.Element == Int)
Generic types
Declarations
General
1. A declaration may introduce one or more entities.
Attributes
1. Declaration attributes have the form:
decl-attribute ::=
attribute-name attribute-parameter-list?
attribute-parameter-list ::=
'(' attribute-parameter (',' attribute-parameter)* ')'
attribute-parameter ::=
simple-string
integer-literal
Modifiers
Access modifiers
1. A name is exposed to a lexical scope if it can be referred to from that scope. When a
name is exposed to a lexical scope, it is exposed to all scopes contained in that scope.
An access modifier specifies how a declaration exposes the names it introduces.
Access modifiers have the form:
access-modifier ::=
'public'
2. A declaration is private if it does not have any access modifier. A private declaration
exposes the names that it introduces to the innermost scope that contains it. The static
and non-static members introduced in the lexical scope of a type declaration are
additionally exposed to the lexical scopes of the conformance declarations of the
declared type.
4. (Example)
type A {
var x: Int
public fun foo() -> Int { x.copy() }
}
conformance A: Copyable {
public fun copy() -> Self {
// 'x' is exposed to the conformance declarations of 'A'
A(x: x.copy())
}
}
fun main() {
let A(x: 1)
print(a.foo()) // OK
print(a.x) // error: 'a.x' is not exposed here
}
Member modifiers
1. Member modifiers have the form:
member-modifier ::=
receiver-modifier
static-modifier
static-modifier ::=
'static'
2. A member modifier may appear at most once in a declaration.
3. The static modifier may only apply to a binding, function, subscript, or property
declaration at type scope.
Conformance lists
1. Conformance lists have the form:
conformance-list ::=
':' name-type-expr (',' name-type-expr)*
Generic clauses
1. Generic clauses have the form:
generic-clause ::=
'<' generic-parameter (',' generic-parameter)* where-clause? '>'
generic-parameter ::=
generic-type-parameter
generic-value-parameter
generic-type-parameter ::=
'@type'? identifier '...'? trait-annotation? ('=' type-expr)?
trait-annotation ::=
':' trait-composition
generic-value-parameter ::=
'@value' identifier ':' type-expr ('=' expr)?
3. (Example)
The generic clause <X, Y: Copyable & Equatable> is sugar for <X, Y where Y:
Copyable & Equatable> .
Where clauses
1. Where clauses have the form:
where-clause ::=
'where' where-clause-constraint-list
where-clause-constraint-list ::=
where-clause-constraint (',' where-clause-constraint)*
where-clause-constraint ::=
equality-constraint
conformance-constraint
value-constraint-expr
equality-constraint ::=
name-type-expr '==' type-expr
conformance-constraint ::=
name-type-expr ':' trait-composition
value-constraint-expr ::=
'@value' expr
trait-composition ::=
name-type-expr ('&' name-type-expr)*
i. An equality constraint specifies that the types denoted by either side of == must
be equivalent.
ii. A conformance constraint specifies that the type denoted by the left hand side of
: be conforming to the traits specified in trait-composition.
iii. A size constraint is an expression denoting a predicate over one or more size
parameters. It must be an expression of type Bool and shall only refer to names
introduced in global scopes or size parameters.
Namespace declarations
1. Namespace declarations have the form:
namespace-decl ::=
namespace-head namespace-body
namespace-head ::=
access-modifier? 'namespace' identifier
namespace-body ::=
'{' namespace-member-list? '}'
namespace-member ::=
namespace-decl
trait-decl
type-alias-decl
product-type-decl
extension-decl
conformance-decl
binding-decl
function-decl
subscript-decl
Trait declarations
General
1. A trait is a collection of requirements on a type. [Note: A trait is not a type but it may
form a type if it is part of an existential type.]
trait-head ::=
access-modifier? 'trait' identifier trait-refinement-list?
trait-refinement-list ::=
':' name-type-expr (',' name-type-expr)*
trait-body ::=
'{' (trait-requirement-decl | ';')* '}'
trait-requirement-decl ::=
associated-decl
function-decl
subscript-decl
property-decl
3. (Example)
trait Shape {
static fun name() -> String
fun draw(to: inout Canvas)
}
4. A trait declaration may only appear at global scope. It introduces identifier as a name
denoting the declared trait.
5. The associated type, associated size, function, subscript, and property declarations
that appear in the body of a trait specify the requirements of that trait. Such
declarations may not have access levels. [Note: The access level of a requirement
implementation depends on the visibility of the conformances requiring that
implementation.]
associated-type-decl ::=
associated-type-head associated-type-constraints? ('=' type-expr)?
associated-type-head ::=
'type' identifier
associated-type-constraints ::=
conformance-list
conformance-list? where-clause
associated-value-decl ::=
associated-value-head where-clause? ('=' expr)?
associated-value-head ::=
'value' identifier
3. (Example)
trait Generator {
type Element: Copyable
inout fun next() -> (element: Element, done: Bool)
}
Method requirements
1. A function declaration that appears in the body of a trait declaration is a method
requirement declaration that defines one or more method requirements. A method
requirement is the specification of a method that must be implemented in conforming
types.
2. When a method requirement declaration has a method-bundle-body, each method
implementation in that body denotes a method requirement. When a method
requirement declaration is bodiless, it defines a single method requirement whose
kind depends on the receiver modifier of the method requirement declaration.
4. (Example)
trait State {
property x: Int { let inout }
}
trait Counter {
fun current() -> Int { 0 }
fun offset(by: Int) -> Int { let inout }
}
extension Counter {
fun offset(by value: Int) -> Int {
let { current() + value }
}
}
Subscript requirements
1. A subscript declaration that appears in the body of a trait declaration is a subscript
requirement declaration that defines one or more subscript accessor requirements. A
subscript accessor requirement is the specification of a subscript accessor that must
be implemented in conforming types.
Property requirements
1. A property declaration that appears in the body of a trait declaration is a property
requirement declaration that defines one or more property accessor requirements. A
property accessor requirement is the specification of a property accessor that must be
implemented in conforming types.
Trait refinement
1. A trait T1 is said to refine another trait T2 if it declares conformance to T2 and its
set of requirements includes all requirements of T2 . Conformance of the trait T1 to
T2 shall be declared in the conformance list of the declaration of T1 .
2. Refinement introduces an ordering between the two traits. A trait T1 is finer than a
trait T2 if T1 refines T2 or if there exists a trait T3 such that T1 refines T3 and
T3 is finer than T2 . Trait refinement shall not introduce cycles. If T1 is finer than
T2 , T2 is said to be coarser than T1 .
3. (Example)
trait A: C {} // error: refinement introduces a cycle
trait B: A {}
trait C: B {}
Trait conformance
1. A type A conforms to a trait T1 in a lexical scope if a conformance of A to T1 is
exposed to that scope and if T1 and satisfies all the requirements of T1 , or if A
conforms to a trait T2 such that T2 refines T1 .
2. (Example)
i. the declaration of A ; or
5. The conformance of a type A to a trait T shall not be exposed outside of the lexical
scope of a module m unless at least A or T is declared in m .
6. (Example)
import M
public type A {}
conformance A: M.T {} // OK: conformance is private
General
1. Nominal product type declarations have the form:
product-type-decl ::=
product-type-head product-type-body
product-type-head ::=
access-modifier? 'type' identifier generic-clause? conformance-list?
product-type-body ::=
'{' (product-type-member-decl | ';')* '}'
product-type-member-decl ::=
function-decl
deinit-decl
subscript-decl
property-decl
binding-decl
product-type-decl
type-alias-decl
General
1. Type alias declarations have the form:
type-alias-decl ::=
type-alias-head type-alias-body
type-alias-head ::=
access-modifier? 'typealias' identifier generic-clause?
type-alias-body ::=
'=' type-expr
'=' union-decl
union-decl ::=
product-type-decl ('|' product-type-decl)*
Extension declarations
General
1. Extension declarations have the form:
extension-decl ::=
extension-head extension-body
extension-head ::=
access-modifier? 'extension' type-expr where-clause?
extension-body ::=
'{' (extension-member-decl | ';')* '}'
extension-member-decl ::=
function-decl
subscript-decl
product-type-decl
type-alias-decl
2. An extension declaration may not appear in the lexical scope of a conformance or
extension declaration.
3. A public access modifier may only appear in extension declarations defined in the
lexical scope of a module. An public extension declaration exposes its public members
outside of the module in which it is declared.
Conformance declarations
General
1. Conformance declarations have the form:
conformance-decl ::=
conformance-head conformance-body
conformance-head ::=
access-modifier? 'conformance' type-expr conformance-list where-clause?
conformance-body ::=
'{' (conformance-member-decl | ';')* '}'
conformance-member-decl ::=
function-decl
subscript-decl
product-type-decl
type-alias-decl
4. (Example)
// In 'MyModule.module.val'
public namespace Foo {
public trait T {}
public conformance Int: T {} // error
}
public conformance String: T {} // OK: `String` conforms to `T` in importing
modules.
Binding declarations
General
1. Binding declarations have the form:
binding-decl ::=
binding-head binding-initializer?
binding-head ::=
access-modifier? member-modifier* binding-pattern
binding-initializer ::=
'=' expr
2. (Example)
This binding declaration defines two new immutable bindings: name and age .
3. A binding declaration defines a new binding for each name pattern in binding-
pattern. All new bindings are defined with the same capabilities.
ii. A binding declaration introduced with var or inout defines a mutable binding.
The value of a live mutable binding may be projected mutably or immutably.
A mutable binding can appear on the left side of an assignment, on the right side of an
assignment, as the initializer of a binding, as argument to an inout parameter, or as
argument to an set parameter.
4. The pattern of a binding declaration may not contain any expression patterns.
5. A binding declaration may be defined at module scope, namespace scope, type scope,
or function scope.
ii. A binding declaration at type scope is called a static member binding declaration
if it contains a static modifier. Otherwise, it is called a member binding
declaration. A static member binding declaration introduces one or more global
bindings. A member binding declaration introduces one or more member
bindings.
6. The sink capability may only appear in a local binding declaration introduced with
let or var .
Initialization
1. A static member binding declaration, or global binding declaration must contain an
initializer. Initialization occurs at the first dynamic use of one of the declared bindings.
3. A member binding declaration may not have an initializer. Initialization occurs in the
initializer of the object of which the introduced bindings are members (see Type
initialization).
fun main() {
var fruits = ["apple", "mango", "orange"]
inout first = fruits[0] // mutable projection of 'fruits[0]'
first = "strawberry"
print(first) // projection ends afterward
}
Lifetime
1. Binding lifetimes are not bound to lexical scopes.
2. A binding is said to be alive at a given program point if that program point falls within
one of its lifetimes. Otherwise, it is said to be dead.
3. The lifetime of a global binding begins when its initialization is complete and ends
when the program terminates.
4. The lifetime of a member binging b begins when the initialization of the object o of
which it is a sub-object is complete and ends when o is consumed, or when b is
consumed in a sink method of o .
5. The lifetime of a local binding begins when its initialization is complete and ends after
its last use in an operation, or after any consuming operation.
fun main() {
let count = 1 // lifetime of 'count' begins here
count += 1 // a use of 'count'
print(count) // last use of 'count', lifetime ends afterward
print("done")
}
fun main() {
sink let count = 1 // lifetime of 'count' begins here
sink _ = count // lifetime of 'count' ends here
print(count) // error: 'count' has been consumed
}
8. A dead immediate local mutable binding can be re-initialized, starting a new lifetime.
Function declarations
General
1. Function declarations have the form:
function-decl ::=
memberwise-init-decl
function-head function-signature function-body?
memberwise-init-decl ::=
'memberwise' 'init'
deinit-decl ::=
'deinit' brace-stmt
function-head ::=
access-modifier? member-modifier* function-decl-identifier generic-clause? cap
function-decl-identifier ::=
'init'
'fun' identifier
operator-notation 'fun' operator
function-body ::=
method-bundle-body
brace-stmt
method-bundle-body ::=
'{' method-impl+ '}'
2. (Example)
ii. A function declaration at type scope that contains a static modifier is called a
static method declaration; it is also a global function declaration. A static method
declaration introduces one global function.
iii. A function declaration at type scope that does not contain a static modifier and
is not declared with init or memberwise init is called a method declaration. It
introduces one or more methods.
iv. A function declaration at type scope declared with init is called a initializer
declaration. It introduces one global function.
vi. A function declaration at type scope declared with deinit is called a deinitializer
declaration. It introduces one sink method.
5. The init introducer and the deinit introducer may only appear in a function
declaration at type scope.
6. A method implementation may only appear in a method declaration.
Function signatures
1. Function signatures have the form:
function-signature ::=
'(' parameter-list? ')' receiver-effect? ('->' type-expr)? type-aliases-clause
type-aliases-clause ::=
'where' type-aliases-clause-item (',' type-aliases-clause-item)*
type-aliases-clause-item ::=
'typealias'identifier '=' type-expr
2. The default value of a parameter declaration may not refer to another parameter in a
function signature.
3. The output type of a function signature defines the output type of the containing
declaration. If that type is omitted, the output type of the declaration is interpreted as
() .
Function implementations
1. The brace statement in the body of a global or local function declaration defines its
implementation. A global or local function declaration must have a function
implementation, unless it is static method declaration.
iii. set parameters are dead and mutable, and they must be alive at the end of
every terminating execution path.
4. The output type of a function implementation is the output type of the containing
function declaration, unless it is an explicit inout method implementation (see
Method implementations). A function implementation must have a return statement
on every terminating execution path, unless the output type of the containing
declaration is () . In that case, explicit return statements may be omitted. A function
implementation must return an escapable object whose type is a subtype of the
output type of the containing method declaration.
5. The return keyword may be omitted if the body of the function implementation
consists of a single expression.
Method declarations
1. A bodiless method declaration or a method declaration that contains a bodiless
method implementation defines a method requirement and may only appear in a trait
declaration. A bodiless static method declaration defines a static method requirement
and may only appear in a trait declaration.
2. A method declaration may contain at most one method implementation of each kind.
A method declaration that contains one or more explicit method implementations
may not have a receiver modifier.
inout {
self = m(arg1, ..., argn)
}
6. (Example)
type Vector2 {
var x: Double
var y: Double
method-impl ::=
method-introducer brace-stmt?
Subscript declarations
General
1. Subscript declarations have the form:
subscript-decl ::=
subscript-head subscript-signature subscript-body
subscript-head ::=
access-modifier? member-modifier* 'subscript' identifier? generic-clause? capt
subscript-identifier ::=
'subscript' identifier
subscript-body ::=
'{' subscript-impl+ '}'
2. (Example)
ii. A subscript declaration at type scope that contains a static modifier is called a
static subscript declaration; it is also a global subscript declaration. A static
member subscript declaration introduces a global susbcript.
iii. A subscript declaration at type scope that does not contain a static modifier is
called a member subscript declaration. It introduces a member subscript. A
member subscript declaration may not have a receiver modifier.
Subscript signatures
1. Subscript signatures have the form:
subscript-signature ::=
'(' parameter-list? ')' receiver-effect? ':' 'var'? type-expr
2. The default value of a parameter declaration may not refer to another parameter in a
subscript signature.
3. The output type of a subscript signature defines the output type of the containing
declaration. If that type is prefixed by var , all projections produced by the subscript
are mutable. Otherwise, only the projections produced by the inout implementation
of the subscript are mutable.
4. (Example)
}
Subscript implementations
1. A subscript implementation may be defined implicitly or explicitly. Explicit subscript
implementations have the form:
subscript-impl ::=
subscript-introducer brace-stmt?
4. The passing convention of a yielded parameter depends on the kind of the subscript
implementation: it is a let parameter in a let subscript implementation; or it is a
sink parameter in a sink subscript implementation; or it is an inout parameter in
an inout subscript implementation; or it is an set parameter in an set subscript
implementation.
ii. inout parameters are mutable and escapable. They must be alive at the end of
every terminating execution path.
iii. set parameters are dead and mutable. They must be alive at the end of every
terminating execution path.
6. A let subscript implementation or an inout subscript implementation must have
exactly one a yield statement on every terminating execution path. Given a subscript
declaration with an output type A , a let subscript implementation must yield an
immutable projection of an object whose type is subtype of A , unless the output
signature of the subscript declaration is prefixed by var . In that case it must yield a
mutable projection of an object of type A . An inout subscript implementation must
yield a mutable projection of an object of type A .
Property declarations
General
1. Property declarations have the form:
property-decl ::=
property-head property-annotation subscript-body
property-head ::=
member-modifier* 'property' identifier
property-annotation ::=
':' type-expr
Parameter declarations
1. Parameter declarations have the form:
parameter-list ::=
parameter-decl (',' parameter-decl)*
parameter-decl ::=
(identifier | '_') identifier? (':' parameter-type-expr)? default-value?
default-value ::=
'=' expr
2. If a parameter declaration contains two identifiers, the first is used as the argument
label and the second is used as the parameter name. Otherwise, the same identifier is
used as both the argument label and as the parameter name. If the declaration
contains a wildcard followed by an identifier, it defines a positional parameter. The
identifier is used as the parameter name.
ii. A parameter declared with the sink convention is called a sink parameter.
iii. A parameter declared with the inout convention is called an inout parameter.
iv. A parameter declared with the set convention is called an set parameter.
Operator declarations:
operator-decl ::=
'operator' operator-notation operator (':' precedence-group)?
Capture lists
1. Capture lists have the form:
capture-list ::=
'[' binding-decl (',' binding-decl)* ']'
2. The bindings of a capture list may not have access or member modifiers.
Statements
General
1. Statements of the form:
stmt ::=
brace-stmt
discard-stmt
loop-stmt
jump-stmt
decl-stmt
expr
brace-stmt ::=
'{' stmt-list? '}'
Discard statements
1. Discard statements explicitly discard the result of an expression. They have the form:
discard-stmt ::=
'_' '=' expr
Loop statements
General
1. Loop statements describe iteration. They have the form:
loop-stmt ::=
do-while-stmt
while-stmt
for-stmt
2. A loop statement introduces a lexical scope. The body of a loop is a brace statement
lexically nested inside the loop's scope.
3. A loop has three control entry points: a head, a body, and a tail. Control enters a loop
from its head. The head belongs to the loop scope and the tail belongs to the body's
scope. When a loop statement is executed, control is transferred to its head, entering
the loop's scope. If control reaches the end of a loop's body, it is unconditionally
transferred to its tail. The body's scope and then the loop's scope are exited when
control exits the tail, whether or not it is transferred back to the head.
Do-while statements
1. do-while statements have the form:
do-while-stmt ::=
'do' brace-stmt 'while' expr
2. The condition of a do-while statement belongs to the tail of the loop. It must be an
expression of type Bool , which is evaluated by each continuation test. The test
succeeds if the condition evaluates to true . That value is not consumed.
3. (Example)
fun main() {
var counter = 0
do {
counter += 1
let x = counter
} while x < 3
}
The binding x that occurrs in the condition of the do-while statement is declared in
it its body.
While statements
1. while statements have the form:
while-stmt ::=
'while' while-condition-list brace-stmt
while-condition-list ::=
while-condition-item (',' while-condition-item)*
while-condition-item ::=
binding-pattern '=' expr
expr
2. The condition of a while belongs to the head of the loop. It is a non-empty sequence
of condition items. A condition item that is a binding declaration is considered
satisfied if and only if the value of the initializer matches the pattern and can initialize
its new bindings. A condition item that is an expression must be of type Bool and is
considered satisfied if and only if it evaluates to true . That value is not consumed. If
the condition contains more than a single item, the n+1th item is evaluated if and only
if the nth item is satisfied. The continuation test succeeds if and only if all items are
satisfied.
For statements
1. for statements have the form:
for-stmt ::=
'for' binding-pattern for-range for-filter? brace-stmt
for-range ::=
'in' expr
for-filter ::=
'where' expr
2. (Example)
fun main() {
var things: Array<Any> = [1, "abc", 3, 2]
for inout x: Int in things where x < 3 {
x += 1
}
print(things) // [2, "abc", 3, 3]
}
3. The pattern and filter expression of a for statement belong to the head of the loop.
The range of a for statement belongs to the lexical scope in which that statement is
defined.
4. (Example)
fun main() {
for let x in 0 ..< x { print(x) }
}
This program is ill-typed. The range refers to a binding introduced in the loop binding.
5. The range must be an expression of a type that conforms to the Iterable trait. When
a for statement is executed, an iterator object it is produced by calling
make_iterator on the object evaluated by the loop range. The continuation test
consists of verifying that it.next() does not result in a nil object. The loop iterates
over all elements produced by the iterator, unless it is exited early via a break or a
return statement.
6. The head of a for statement performs a continuation test. If it fails, control exits the
loop. Otherwise, it tests whether the latest object projected by the loop's iterator
matches the pattern of the binding declaration and can initialize its new bindings. If
and only if it does, the loop filter is evaluated. If and only if the filter evaluates to
true , control enters the body of the loop. The value of the filter is not consumed. If
either the pattern of the binding declaration does not match, or the filter is not
satisfied, control is transferred back to the head.
7. (Example)
fun main() {
var things: Array<Any> = [1, "abc", 3, 2]
for inout x: Int in things where x < 3 {
x += 1
}
print(things) // [2, "abc", 3, 3]
}
fun main() {
var things: Array<Any> = [1, "abc", 3, 2]
var iterator = things.make_iterator()
while true {
if inout x: Int = iterator.next(), x < 3 {
x += 1
} else {
break
}
}
print(things) // [2, "abc", 3, 3]
}
Jump statements
General
1. Jump statements unconditionally transfer control. They have the form:
2. break and continue statements are called loop jump statements. A loop jump
statement applies to the innermost loop.
conditional-binding-stmt ::=
binding-pattern 'else' cond-binding-fallback
conditional-binding-fallback ::=
jump-stmt
brace-stmt
expr
Return statements
1. Return statements return an object from a function, terminating the execution path
and transferring control back to the function's caller.
2. The expression in a return statement is called its operand. If the operand is omitted, it
is interpreted as () . A return statement consumes the value of the operand to
initialize an escapable object as result of the call to the containing function.
Yield statements
1. Yield statements project an object out of a subscript, suspending the execution path
and temporarily transferring control to the subscript's caller. Control comes back to
the subscript once after the last use of the yielded projection at the call site, resuming
execution at the statement that directly follows the yield statement.
2. (Example)
fun main() {
let fruits = ["apple", "mango", "orange"]
let f = element(at: 1, in: fruits) // "will yield"
print("foo") // "foo"
print(f) // "mango"
// "did yield"
print("bar") // "bar"
}
3. The expression in a yield statement is called its operand. A yield statement projects
the value of its operand as the result of the call to the containing subscript. The
yielded object is projected immutably in a let subscript implementation and mutably
in an inout subscript implementation. The mutability marker & must prefix a
mutable projection.
Break statements
1. Break statements exit a loop. Control is transferred to the statement immediately
following the loop, if any.
Continue statements
1. Continue statements skip the remainder of a loop body. Control is transferred to the
begin of the loop.
Declaration statements
1. Declaration statements have the form:
decl-stmt ::=
type-alias-decl
product-type-decl
extension-decl
conformance-decl
function-decl
subscript-decl
binding-decl
Value expressions
General
expr ::=
infix-expr-head infix-expr-tail*
infix-expr-head ::=
async-expr
await-expr
prefix-expr
3. If during the evaluation of a value expression, the result is not mathematically defined
or not in the range of representable values for its type, the behavior is undefined.
Consuming expressions
1. A value expression is consuming if and only if its evaluation may end the lifetime of
one or objects not created by the expression's evaluation.
Infix expressions
1. Infix tails have the form:
infix-expr-tail ::=
type-casting-tail
infix-operator-tail
type-casting-tail ::=
type-casting-operator type-expr
infix-operator-tail ::=
infix-operator prefix-expr
infix-operator ::=
operator
'='
'=='
'<'
'>'
'..<'
'...'
Async expressions
1. Async expressions have the form:
async-expr ::=
async-expr-head expr
async-expr-head '->' type-expr brace-stmt
async-expr-head ::=
'async' capture-list?
Notes:
An async expression defined in a scope shall either escape that scope or be consumed by a
consuming method. await e is sugar for e.await() , where await is a consuming
method.
Await expressions
1. Await expressions have the form:
await-expr ::=
'await' expr
Prefix expressions
1. Prefix expressions have the form:
prefix-operator ::=
prefix-operator-head raw-operator*
2. There shall be no whitespace between the operator and the operand of a prefix
expression.
Postfix expressions
2. There shall be no whitespace between the operator and the operand of a suffix
expression.
Primary expressions
General
1. Primary expressions have the form:
primary-expr ::=
scalar-literal
compound-literal
primary-decl-ref
implicit-member-ref
lambda-expr
selection-expr
inout-expr
tuple-expr
'nil'
Scalar literals
1. Scalar literals have the form:
scalar-literal ::=
boolean-literal
integer-literal
floating-point-literal
string-literal
unicode-scalar-literal
Compound literals
General
compound-literal ::=
buffer-literal
map-literal
Buffer literals
1. Buffer literals have the form:
buffer-literal ::=
'[' buffer-component-list? ']'
buffer-component-list ::=
expr (',' expr)* ','?
2. The type of a buffer literal is T[n] , where n is the number of components in the
literal and T is the type of all components. If a buffer literal appears in a typed
context, T may be inferred from that context. Otherwise, the literal may not be
empty, the type of T is inferred from the first component, and all other components
must have the same type. The implementation may issue a warning if the literal has no
components.
3. (Example)
Map literal
map-literal ::=
'[' map-component-list ']'
'[' ':' ']'
map-component-list ::=
map-component (',' map-component)* ','?
map-component ::=
expr ':' expr
4. (Example)
primary-decl-ref ::=
identifier-expr static-argument-list?
static-argument-list ::=
'<' static-argument (',' static-argument)* '>'
static-argument ::=
(identifier ':')? (expr | type-expr)
implicit-member-ref ::=
'.' primary-decl-ref
Identifiers
1. Identifiers have the form:
identifier-expr ::=
entity-identifier impl-identifier?
entity-identifier ::=
identifier
function-entity-identifier
operator-entity-identifier
2. An identifier may not have any whitespace between its constituent tokens.
4. (Example)
type A {
let m: Int
static let n = 0
}
5. An identifier may be suffixed by a method introducer if and only if its entity identifier
refers to a method declaration for which an explicit or synthesized method
implementation for the same method introducer exists.
6. (Example)
type Vector2 {
var x: Double
var y: Double
let f = Vector2.scaled(by:).let // OK
let g = Vector2.scaled(by:).sink // OK
Tuple expressions
1. Tuple expressions have the form:
tuple-expr ::=
'(' tuple-expr-element-list? ')'
tuple-expr-element-list ::=
tuple-expr-element (',' tuple-expr-element)?
tuple-expr-element ::=
(identifier ':')? expr
Compound expressions
General
1. Compound expressions have the form:
compound-expr ::=
value-member-expr
static-value-member-expr
function-call-expr
subscript-call-expr
primary-expr
Member accesses
General
value-member-expr ::=
labeled-member-expr
indexed-member-expr
labeled-member-expr ::=
primary-expr '.' primary-decl-ref
indexed-member-expr ::=
primary-expr '.' member-index
General
static-value-member-expr
type-expr '.' primary-decl-ref
Function calls
General
function-call-expr ::=
function-call-head call-argument-list? ')'
call-argument-list ::=
call-argument (',' call-argument)*
call-argument ::=
(identifier ':')? expr
The opening parenthesis preceding the call argument list must be on the same line as
the callee.
ii. if the callee is a value member expression, the call expression is a method call; or
iii. if the callee is a type member expression referring to a function declaration, the
call expression is a static method call;
3. An initializer all T(a1, ..., an) desugars to var storage: T; T.init(&storage, a1,
..., a2) . A method call x.m(a1, ..., an) (or &x.m(a1, ..., an) ) desugars to
T.m(x, a1, ..., an) (or respectively T.m(&x, a1, ..., an) ) where T is the static
type of x .
Subscript calls
1. Subscript calls have the form:
subscript-call-expr ::=
subscript-call-head call-argument-list? ']'
Lambda expressions
1. Lambda expressions have the form:
lambda-expr ::=
'fun' capture-list? function-signature lambda-body
lambda-body ::=
brace-stmt
2. The parameters of a lambda expressions may not have a default value.
Selection expressions
1. Selection expressions have the form:
selection-expr ::=
conditional-expr
match-expr
Conditional expressions
conditional-expr ::=
'if' conditional-clause brace-stmt conditional-tail?
conditional-clause ::=
conditional-clause-item (',' conditional-clause-item)*
conditional-clause-item ::=
binding-pattern '=' expr
expr
conditional-tail ::=
'else' conditional-expr
'else' brace-stmt
Match expressions
match-expr ::=
'match' expr '{' match-case* '}'
match-case ::=
pattern ('where' expr)? brace-stmt
Operators
Operator notations
1. Operator notations have the form:
2. A cast expression results in an object or projection whose type is the type denoted by
the right operand of the expression.
4. An upcast expression is well-formed if the type of the left operand is statically known
to be subtype of the type denoted by the right operand. The result of an upcast
expression e is an immutable projection of the left operand, unless e is the operand
of a consuming operation and its left operand is sinkable. In that case, the value of the
left operand escapes.
Async operator
Await operator
Type expressions
1. Type expressions have the form:
type-expr ::=
async-type-expr
conformance-lens-type-expr
existential-type-expr
opaque-type-expr
indirect-type-expr
lambda-type-expr
name-type-expr
stored-projection-type-expr
tuple-type-expr
union-type-expr
wildcard-type-expr
'(' type-expr ')'
async-type-expr ::=
'async' type-expr
Conformance lenses
1. Conformance lenses have the form:
conformance-lens-type-expr ::=
type-expr '::' type-identifier
existential-type-expr ::=
'any' trait-composition where-clause?
indirect-type-expr ::=
'indirect' type-expr
lambda-type-expr ::=
lambda-environment? '(' lamda-parameter-list? ')' receiver-effect? '->' type-e
lambda-environment ::=
'thin'
'[' type-expr ']'
lamda-parameter-list ::=
lambda-parameter (',' lambda-parameter)*
lambda-parameter ::=
(identifier ':')? type-expr
name-type-expr ::=
(type-expr '.')? primary-type-decl-ref
primary-type-decl-ref ::=
type-identifier type-argument-list?
type-identifier ::=
identifier
parameter-type-expr ::=
parameter-passing-convention? type-expr
stored-projection-type-expr ::=
'[' stored-projection-capability type-expr ']'
stored-projection-capability ::=
'let'
'inout'
'yielded'
tuple-type-expr ::=
'{' tuple-type-element-list '}'
tuple-type-element-list ::=
tuple-type-element (',' tuple-type-element)?
tuple-type-element ::=
(identifier ':')? type-expr
2. A tuple type is a structural type composed of zero or more ordered operands. An
operand is a type together with an optional label.
union-type-expr ::=
type-expr ('|' type-expr)+
wildcard-type-expr ::=
'_'
Type aliases
Patterns
General
1. Patterns have the form:
pattern ::=
binding-pattern
expr-pattern
tuple-pattern
wildcard-pattern
Binding patterns
1. Binding patterns have the form:
binding-pattern ::=
binding-introducer (tuple-pattern | wildcard-pattern | identifier) binding-ann
binding-introducer ::=
'let'
'var'
'sink'
'inout'
binding-annotation ::=
':' type-expr
Expression patterns
1. Expression patterns have the form:
expr-pattern ::=
expr
Tuple patterns
tuple-pattern ::=
'(' tuple-pattern-element-list ')'
tuple-pattern-element-list ::=
tuple-pattern-element (',' tuple-pattern-element)?
tuple-pattern-element ::=
(identifier ':')? pattern
Wildcard patterns
1. Wildcard patterns have the form:
wildcard-pattern ::=
'_'
Whitespace and comments
```ebnf
whitespace ::=
horizontal-space
newline
horizontal-space ::=
hspace
single-line-comment
block-comment