Ballerina Language Specification, Swan Lake preview 2020-06-18

Primary contributors:

(Other contributors are listed in Appendix D.)

Copyright © 2018, 2019, 2020 WSO2

Licensed under the Creative Commons Attribution-NoDerivatives 4.0 International license

Language and document status

This is a draft document, previewing the next release of the specification, code-named Swan Lake.

Features marked as having Preview status are less stable than the rest of the language. We expect the final design of these features to be close enough to the current design that it will be straightforward for code that makes uses the current design to be updated to the final design.

Comments on this document are welcome and should be made by creating an issue in https://github.com/ballerina-platform/ballerina-spec, which is the GitHub repository where this specification is maintained. The design of the language may also be discussed in the ballerina-dev@googlegroups.com mailing list.

Table of contents

1. Introduction

Ballerina is a statically typed, concurrent programming language, focusing on network interaction and structured data. It is intended to be the core of a language-centric middleware platform. It has all the general-purpose functionality expected of a modern programming language, but it also has several unusual aspects that make it particularly suitable for its intended purpose.

First, it provides language constructs specifically for consuming and providing network services. Future versions of Ballerina will add language constructs for other middleware functionality such as event stream processing, distributed transactions and reliable messaging; this is described in more detail in Appendix C.

Second, its abstractions and syntax for concurrency and network interaction have been designed so that there is a close correspondence with sequence diagrams. This enables a bidirectional mapping for any Ballerina function between its textual representation in the syntax described in this specification and its graphical representation as a sequence diagram, such that the sequence diagram fully shows the aspects of the behavior of that function that relate to concurrency and network interaction.

Third, it has a type system that is more flexible and allows for looser coupling than traditional statically typed languages. The type system is structural: instead of requiring the program to explicitly say which types are compatible with each other, compatibility of types and values is determined automatically based on their structure; this is particularly useful when combining data from multiple, independently-designed systems. In addition, the type system provides union types and open records. This flexibility allows the type system to be used as a schema for the data that is exchanged in distributed applications. Ballerina's data types are designed to work particularly well with JSON; any JSON value has a direct, natural representation as a Ballerina value. Ballerina also provides support for XML and tabular data.

Ballerina is not a research language. It is intended to be a pragmatic language suitable for mass-market commercial adoption. It tries to feel familiar to programmers who are used to popular, modern C-family languages, notably Java, C# and JavaScript. It also gets ideas and inspiration from many other existing programming languages including TypeScript, Go, Rust, D, Kotlin, Swift, Python and Perl.

The Ballerina language has been designed in conjunction with the Ballerina platform, which provides comprehensive support for a module-based software development model, including versioning, dependency management, testing, documentation, building and sharing. Modules are organized into repositories; there is a globally-shared, central repository, but repositories can also be local.

The Ballerina language includes a small library, the lang library, which provides fundamental operations on the data types defined by the language; the lang library is defined by this specification. The Ballerina platform includes an extensive standard library, which includes not only the usual low-level, general-purpose functionality, but also support for a wide variety of network protocols, interface standards, data formats and authentication/authorization standards, which make writing secure, resilient distributed applications significantly easier than with other languages. The standard library is not specified in this document.

2. Notation

Productions are written in the form:

symbol := rhs

where symbol is the name of a nonterminal, and rhs is as follows:

The rhs of a symbol that starts with a lower-case letter implicitly allows white space and comments, as defined by the production TokenWhiteSpace, between the terminals and nonterminals that it references.

3. Program structure

A Ballerina program is divided into modules. A module has a source form and a binary form. The module is the unit of compilation; a Ballerina compiler translates the source form of a module into its binary form. A module may reference other modules. When a compiler translates a source module into a binary module, it needs access only to the binary form of other modules referenced from the source module.

A binary module can only be referenced if it is placed in a module store. There are two kinds of module store: a repository and a project. A module stored in a repository can be referenced from any other module. A module stored in a project can only be referenced from other modules stored in the same project.

A repository organizes binary modules into a 3-level hierarchy:

  1. organization;
  2. module name;
  3. version.

Organizations are identified by Unicode strings, and are unique within a repository. Any organization name starting with the string ballerina is reserved for use by the Ballerina platform. A module name is a Unicode string and is unique within a repository organization. A particular module name can have one or more versions each associated with a separate binary module. Versions are semantic, as described in the SemVer specification.

A project stores modules using a simpler single-level hierarchy, in which the module is associated directly with the module name.

A binary module is a sequence of octets. Its format is specified in the Ballerina platform.

An abstract source module consists of:

An abstract source module can be stored in a variety of concrete forms. For example, the Ballerina platform describes a method for storing an abstract source module in a filesystem, where the source parts are files with a .bal extension stored in a directory, the module name comes from the name of that directory, and the version and organization name comes from a configuration file Ballerina.toml in that directory.

4. Lexical structure

The grammar in this document specifies how a sequence of Unicode code points is interpreted as part of the source of a Ballerina module. A Ballerina module part is a sequence of octets (8-bit bytes); this sequence of octets is interpreted as the UTF-8 encoding of a sequence of code points and must comply with the requirements of RFC 3629.

After the sequence of octets is decoded from UTF-8, the following two transformations must be performed before it is parsed using the grammar in this document:

The sequence of code points must not contain any of the following disallowed code points:

Note that the grammar notation ^X does not allow the above disallowed code points.

identifier := UnquotedIdentifier | QuotedIdentifier
UnquotedIdentifier :=
   IdentifierInitialChar IdentifierFollowingChar*
QuotedIdentifier := ' QuotedIdentifierChar+
QuotedIdentifierChar :=
  IdentifierFollowingChar
  | QuotedIdentifierEscape
  | StringNumericEscape
IdentifierInitialChar :=  AsciiLetter | _ | UnicodeIdentifierChar
IdentifierFollowingChar := IdentifierInitialChar | Digit
QuotedIdentifierEscape := \ ^ ( AsciiLetter | 0x9 | 0xA | 0xD | UnicodePatternWhiteSpaceChar )
AsciiLetter := A .. Z | a .. z
UnicodeIdentifierChar := ^ ( AsciiChar | UnicodeNonIdentifierChar )
AsciiChar := 0x0 .. 0x7F
UnicodeNonIdentifierChar :=
   UnicodePrivateUseChar
   | UnicodePatternWhiteSpaceChar
   | UnicodePatternSyntaxChar
UnicodePrivateUseChar :=
   0xE000 .. 0xF8FF
   | 0xF0000 .. 0xFFFFD
   | 0x100000 .. 0x10FFFD
UnicodePatternWhiteSpaceChar := 0x200E | 0x200F | 0x2028 | 0x2029
UnicodePatternSyntaxChar := character with Unicode property Pattern_Syntax=True
Digit := 0 .. 9

Note that the set of characters allowed in identifiers follows the requirements of Unicode TR31 for immutable identifiers; the set of characters is immutable in the sense that it does not change between Unicode versions.

The QuotedIdentifier syntax allows an arbitrary non-empty string to be treated as an identifier. In particular, a reserved keyword K can be used as an identifier by preceding it with a single quote i.e. 'K.

TokenWhiteSpace := (Comment | WhiteSpaceChar)*
Comment := // AnyCharButNewline*
AnyCharButNewline := ^ 0xA
WhiteSpaceChar := 0x9 | 0xA | 0xD | 0x20

TokenWhiteSpace is implicitly allowed on the right hand side of productions for non-terminals whose names start with a lower-case letter.

5. Values, types and variables

5.1 Overview

5.1.1 Type system fundamentals

Ballerina programs operate on a rich universe of values. This universe of values is partitioned into a number of basic types; every value belongs to exactly one basic type.

Values are of four kinds, each corresponding to a kind of basic type:

  • simple values, like booleans and floating point numbers, which are not constructed from other values;
  • structured values, like mappings and lists, which contain other values;
  • sequence values, which combine aspects of simple values and structured values;
  • behavioral values, like functions, which allow parts of Ballerina programs to be handled in a uniform way with other values

There is a fundamental distinction between values that have a storage identity and values that do not. A value that has storage identity has an identity that comes from the location where the value is stored. All structural and behavioural values have a storage identity, whereas all simple values do not. Storage identity for sequence values is more complicated and will be explained in the section on sequence values.

Values can be stored in variables or as members of structures. When a value has no storage identity, it can be stored directly in the variable or structure. However, when a value has storage identity, what is stored in the variable or member is a reference to the location where the value is stored rather than the value itself. Storage identity allows values in Ballerina to represent not just trees but graphs.

Ballerina provides the ability to test whether two values have the same storage identity, but does not expose the specific storage location of a value. For values with storage identity, there is the concept of creating a new value: this means creating a value that has a storage identity that is different from any existing value. For values with storage identity, there is also the concept of copying: it means to create a value that is the same, except for having a new storage identity. The concept of having storage identity is similar to the concept of a reference type in some other programming languages, but also accomodates the concept of a sequence value.

Storage identity is tied to mutability. Mutation is only well-defined for values with storage identity. When a value stored in some storage location is mutated, the change will be visible through all variables referring to the value in that location. But not all values with storage identity can be mutated: a value may not support mutation even though it has a storage identity.

Ballerina programs use types to categorize values both at compile-time and runtime. Types deal with an abstraction of values that does not consider storage identity. This abstraction is called a shape. A type denotes a set of shapes. Subtyping in Ballerina is semantic: a type S is a subtype of type T if the set of shapes denoted by S is a subset of the set of shapes denoted by T. Every value has a corresponding shape. A shape is specific to a basic type: if two values have different basic types, then they have different shapes. The shape of the values contained in a structured value are part of the shape of the structured value. Since shapes do not deal with storage identity, they represent trees rather graphs. For simple values, there is no difference between a shape and a value, with the exception of floating point values where the shape does not consider representation details that do not affect the mathematical value being represented. There are two important relations between a value and a type:

  • a value looks like a type at a particular point in the execution of a program if its shape at that point is a member of the type;
  • a value belongs to a type if it looks like the type, and it will necessarily continue to look like the type no matter how the value is mutated.

When a Ballerina program declares a variable to have a compile-time type, this means that the Ballerina compiler together with the runtime system will ensure that the variable will only ever contain a value that belongs to the type. Ballerina also provides mechanisms that take a value that looks like a type and use it to create a value that belongs to a type.

If a value cannot be mutated, looking like a type and belonging to a type are the same thing.

Every value has a read-only bit. If the read-only bit is on, it means that the value is not mutable. A value's read-only bit is fixed when the value is constructed, and cannot be changed thereafter. Immutability is deep: the values contained in a structured value that has its read-only bit on will all have their read-only bits on. Some basic types are inherently immutable: the read-only bit is always on for a value that belongs to an inherently immutable basic type. All simple types are inherently immutable. Some basic types are selectively immutable: a type is selectively immutable if it is possible to construct both values of the type that have the read-only bit on and values that do not have the read-only bit on. All structured types are selectively immutable. Finally, some basic types are inherently mutable: the read-only bit is never on for a value belonging to an inherently mutable basic type.

A shape is divided into two aspects: the primary aspect and the read-only aspect. A value's read-only bit is a part of the read-only aspect of the value's shape. The read-only bit of values contained in a structured value is part of the read-only aspect of those values and of the read-only aspect of the structured value. Everything about a shape except the read-only bits constitutes the primary aspect of the shape.

A value is plain data if it is a simple value, a sequence value or a structured value that does not contain a behavioral value at any depth. More precisely, a value is defined to be plain data if it is

  • a simple value,
  • a sequence value, or
  • a structured value, all of whose members are also plain data.

Plain data values can in general contain cycles of references, but in some contexts are restricted to be acyclic. Plain data values, including values with cycles, can be compared for equality.

5.1.2 Type descriptors

Ballerina provides a rich variety of type descriptors, which programs use to describe types. For example, there is a type descriptor for each simple basic type; there is a type descriptor that describes a type as a union of two types; there is a type descriptor that uses a single value to describe a type that contains a single shape. This means that values can look like and belong to arbitrarily many types, even though they look like or belong to exactly one basic type.

The following table summarizes the type descriptors provided by Ballerina.

Kind Name Set of values denoted by type descriptor
basic, simple nil ()
boolean true, false
int 64-bit signed integers
float 64-bit IEEE 754-2008 binary floating point numbers
decimal decimal floating point numbers
basic, sequence string a sequence of Unicode scalar values
XML a sequence of zero or more elements, processing instructions, comments or text items
basic, structured array an ordered list of values, optionally with a specific length, where a single type is specified for all members of the list
tuple an ordered list of values, where a type is specified separately for each member of the list
map a mapping from keys, which are strings, to values; specifies mappings in terms of a single type to which all keys are mapped
record a mapping from keys, which are strings, to values; specifies maps in terms of names of fields (required keys) and value for each field
table a ordered collection of mappings, where a mapping is uniquely identified within the table by a key derived from the mapping
basic, behavioral error an indication that there has been an error, with a string identifying the reason for the error, and a mapping giving additional details about the error
function a function with 0 or more specified parameter types and a single return type
future a value to be returned by a function execution
object a combination of named fields and named methods
service a collection of named methods, including resource methods
typedesc a type descriptor
handle reference to externally managed storage
stream a sequence of values that can be generated lazily
other singleton a single value described by a literal
readonly any value whose read-only bit is on
any any value other than an error
never no value
optional a value that is either () or belongs to a type
union a value that belongs to at least one of a number of types
intersection a value that belongs to all of a number of types
distinct
anydata plain data (a simple value, sequence value or structured value that does not contain behavioral members at any depth)
json the union of (), int, float, decimal, string, and maps and arrays whose values are, recursively, json
byte int in the range 0 to 255 inclusive

Type descriptors other than readonly describes types using only the primary aspect of shape: whether a value belongs to the type is not affected by the read-only aspect of the value's shape. The readonly type uses only the read-only aspect: whether a value belongs to the readonly type depends only on the read-only aspect of the value's shape.

In addition to describing a type, a type descriptor may also include information used to construct a value of the type, as well as metadata. Whereas the type described by a type descriptor is known at compile time, this additional information may need to be resolved at runtime. The typedesc basic type represents a type descriptor that has been resolved.

type-descriptor :=
   simple-type-descriptor
   | sequence-type-descriptor
   | structured-type-descriptor
   | behavioral-type-descriptor
   | other-type-descriptor

For simplicity, the type-descriptor grammar is ambiguous. The following table shows the various types of type descriptor in decreasing order of precedence, together with associativity.

Operator Associativity
T[]
T1 & T2 left
T1 | T2 left
function(args) returns T right

5.1.3 Type-ids

Ballerina has a feature, called distinct types, which provides functionality similar to that provided by nominal types, but which works within Ballerina's structural type system. Distinct types are similar to the branded types found in some other structurally typed languages, such as Modula-3.

The semantics of distinct types are based on type-ids. These are similar to the brands used by branded types. A distinct type is created by a distinct-type-descriptor. Each occurrence of a distinct-type-descriptor in a source module has a distinct type-id, which uniquely identifies it within a Ballerina program. A type-id has three parts:

  1. a module id, which identifies the module within which the distinct-type-descriptor occurs; this consists of an organization, a module name, and a version number;
  2. a local id, which identifies the occurrence of the distinct-type-descriptor within the module; this takes one of two forms:
    • named - if a distinct-type-descriptor is the only distinct-type-descriptor occurring within a module-type-defn, then the local id is the name of the type defined by the module-type-defn
    • anonymous - otherwise, the local id is a compiler-generated integer;
  3. a boolean flag saying whether the type-id is public; this flag is on if and only if the local id part is named and is the name of a module-type-defn that is public.

Distinct types can be used with only the object or error basic types. An object value or error value has a set of type-ids. These type-ids are fixed at the time of construction and are immutable thereafter. A value's set of type-ids may be empty. The type-ids of a value are part of the value's shape and so can affect when an object belongs to a type. The set of type-ids of an object or error value are divided into primary type-ids and secondary type-ids: the secondary type-ids could be inferred from the primary type-ids using the program's source.

An object or error value is always constructed using a specific type descriptor. A type descriptor for objects and errors thus performs a dual role: it denotes a type and it defines a mechanism to construct a value of the type. A type descriptor is definite if it induces a specific set of type-ids. The set of type-ids of an object or error value are those induced by the type-descriptor used to construct it; such a type descriptor must therefore be definite. A type descriptor that denotes a type that does not allow object or error values induces an empty set of type-ids and so is vacuously definite. For other type descriptors, the section that specifies that type descriptor will say when it is definite, the set of type-ids that it induces when it is, and which of those are primary.

5.1.4 Iterability

Values of some basic types are iterable. An iterable value supports an iteration operation, which treats the iterable value as consisting of a sequence of zero or more simpler values, which are in some sense a part of the iterable value; the iteration operation provides the values in the sequence, one after another. The sequence of values that an iteration operation on a value provides is the iteration sequence of the value. Each iterable basic type defines the iteration sequence for a value of that basic type. There is also a value associated with the completion of the iteration operation, which is nil if the iteration completed successfully and an error otherwise. The iteration operation thus determines two associated types for an iterable type: the value type, which is the type of the values in the iteration sequence, and the completion type, which is the type of the iteration completion value.

The following tables summarizes the iterable basic types.

Basic type Iteration sequence Type descriptor Value type Completion type
string length 1 substrings string string:Char ()
xml singleton xml values xml<T> T ()
list members in order T[] T ()
mapping members map<T> T ()
table members in order table<T> T ()
stream items stream<T,C> T C

5.2 Simple values

A simple value belongs to exactly one of the following basic types:

The type descriptor for each simple basic type contains all the values of the basic type.

All simple basic types are inherently immutable.

simple-type-descriptor :=
   nil-type-descriptor
   | boolean-type-descriptor
   | int-type-descriptor
   | floating-point-type-descriptor

5.2.1 Nil

nil-type-descriptor :=  ( )
nil-literal :=  ( ) | null

The nil type contains a single value, called nil, which is used to represent the absence of any other value. The nil value is written (). The nil value can also be written null, for compatibility with JSON; the use of null should be restricted to JSON-related contexts.

The nil type is special, in that it is the only basic type that consists of a single value. The type descriptor for the nil type is not written using a keyword, but is instead written () like the value.

5.2.2 Boolean

boolean-type-descriptor := boolean
boolean-literal := true | false

The boolean type consists of the values true and false.

5.2.3 Int

int-type-descriptor := int
int-literal := DecimalNumber | HexIntLiteral
DecimalNumber := 0 | NonZeroDigit Digit*
HexIntLiteral := HexIndicator HexNumber
HexNumber := HexDigit+
HexIndicator := 0x | 0X
HexDigit := Digit | a .. f | A .. F
NonZeroDigit := 1 .. 9

The int type consists of integers between -9,223,372,036,854,775,808 and 9,223,372,036,854,775,807 (i.e. signed integers than can fit into 64 bits using a two's complement representation).

The byte type is a subtype of int. The lang.int lang library module are also provides built-in subtypes for signed and unsigned integers representable in 8, 16 and 32 bits.

5.2.4 Floating point types

floating-point-type-descriptor := float | decimal
floating-point-literal :=
   DecimalFloatingPointNumber | HexFloatingPointLiteral
DecimalFloatingPointNumber :=
   DecimalNumber Exponent [FloatingPointTypeSuffix]
   | DottedDecimalNumber [Exponent] [FloatingPointTypeSuffix]
   | DecimalNumber FloatingPointTypeSuffix
DottedDecimalNumber :=
   DecimalNumber . Digit*
   | . Digit+
Exponent := ExponentIndicator [Sign] Digit+
ExponentIndicator := e | E
HexFloatingPointLiteral := HexIndicator HexFloatingPointNumber
HexFloatingPointNumber :=
   HexNumber HexExponent
   | DottedHexNumber [HexExponent]
DottedHexNumber :=
   HexDigit+ . HexDigit*
   | . HexDigit+
HexExponent := HexExponentIndicator [Sign] Digit+
HexExponentIndicator := p | P
Sign := + | -
FloatingPointTypeSuffix := DecimalTypeSuffix | FloatTypeSuffix
DecimalTypeSuffix := d | D
FloatTypeSuffix :=  f | F
5.2.4.1 Float

The float type corresponds to IEEE 754-2008 64-bit binary (radix 2) floating point numbers. A float value can be represented by either a DecimalFloatingPointNumber with an optional FloatTypeSuffix, or by a HexFloatingPointLiteral.

The multiple bit patterns that IEEE 754 treats as NaN are considered to be the same value in Ballerina. Positive and negative zero of a floating point basic type are distinct values, following IEEE 754, but are defined to have the same shape, so that they will usually be treated as being equal.

IEEE-defined operations on float values must be performed using a rounding-direction attribute of roundTiesToEven (which is the default IEEE rounding direction, sometimes called round to nearest). All float values, including the intermediate results of expressions, must use the value space defined for the float type; implementations must not use extended precision for intermediate results. This ensures that all implementations will produce identical results. (This is the same as what is required by strictfp in Java.)

5.2.4.2 Decimal

The decimal type corresponds to a subset of IEEE 754-2008 128-bit decimal (radix 10) floating point numbers. Any decimal value can be represented by a DecimalFloatingPointNumber with an optional DecimalTypeSuffix.

A decimal value is a triple (s, c, e) where

  • s is sign, either 0 or -1
  • c is the coefficient, an unsigned integer that can be exactly represented in 34 decimal digits
  • e is the exponent, a signed integer

representing the mathematical value -1s × c × 10e. The range for the exponent e is implementation dependent, but must be at least the range supported by the IEEE 754-2008 decimal128 format (which is -6176 to 6111 inclusive).

The decimal type corresponds to the ANSI X3.X274 subset of IEEE 754-2008, which has the following simplifications:

  • +0 and -0 are not distinguished; if the coefficent is zero, then the sign is also constrained to be zero;
  • NaN, infinities and subnormals are not supported; operations that would result in one of these values according to the normal rules of IEEE 754-2008 instead result in a panic.

Operations on the decimal type use the roundTiesToEven rounding mode, like the float type.

The shape of a decimal value is its mathematical value. Thus two decimal values have the same shape if they represent the same mathematical value, even if they do so using different exponents.

5.3 Sequence values

sequence-type-descriptor :=
   string-type-descriptor
   | xml-type-descriptor

A sequence value belongs to one of the following two basic types:

A sequence value consists of an ordered sequence of zero or more constituent items, where the constituent items belong to the same basic type as the sequence value itself. The length of a sequence value is the number of its constituent items. Each constituent of a sequence value has an integer index ≥ 0 and < length. A sequence value is a singleton if its length is 1. For each sequence basic type, there is an empty value, which has length 0. As with other basic types, the sequence basic types are disjoint with themselves and with other basic types. Thus the empty value for string is distinct from the empty value for xml, and these are both distinct from nil.

The values belonging to a sequence basic type B can be defined in terms of its singleton values and a concatenation operation, by the following rules:

The concatenation of any value v belonging to B with the empty sequence of B in either order is v.

Note that for a sequence consisting of a single item v is the same thing as v. A single item is a sequence. The type of the constituent items of a sequence of basic type B is thus a subtype of B. This is a fundamental difference between sequences and lists.

Only singleton values of a sequence type can have storage identity. When a constituent of a sequence value has storage identity, what is stored in the sequence value is a reference to the location where the constituent value is stored rather than the constituent value itself.

A sequence value is iterable: the iteration sequence consists of the singleton items of the sequence value in order and the iteration completion value is always nil.

5.3.1 Strings

string-type-descriptor := string
string-literal := DoubleQuotedStringLiteral
DoubleQuotedStringLiteral := " (StringChar | StringEscape)* "
StringChar := ^ ( 0xA | 0xD | \ | " )
StringEscape := StringSingleEscape | StringNumericEscape
StringSingleEscape := \t | \n | \r | \\ | \"
StringNumericEscape := \u{ CodePoint }
CodePoint := HexDigit+

A string is an sequence of zero or more Unicode characters. More precisely, it is a sequence whose singleton values represent Unicode scalar values, where a Unicode scalar value is any code point in the Unicode range of 0x0 to 0x10FFFF inclusive, other than surrogate code points, which are 0xD800 to 0xDFFF inclusive. Note that a string may include Unicode noncharacters, such as 0xFFFE and 0xFFFF.

In a StringNumericEscape, CodePoint must valid Unicode code point; more precisely, it must be a hexadecimal numeral denoting an integer n where 0 ≤ n < 0xD800 or 0xDFFF < n ≤ 0x10FFFF.

String values do not have storage identity and so the string basic type is inherently immutable.

There is a built-in subtype string:Char for single character strings.

5.3.2 XML

An xml value is a sequence representing parsed XML, such as occurs in the content of an XML element. The singleton values are of the following types:

  • element
  • processing instruction
  • comment
  • text

The element, processing instruction and comment singletons correspond directly to information items in the XML Information Set. A text singleton corresponds to one or more character information items. When an xml value is constructed, consecutive text singletons are merged, so that an xml value never contains consecutive text singletons. There are built-in subtypes xml:Element, xml:ProcessingInstruction, xml:Comment and xml:Text corresponding to the above singletons; xml:Text also allows the empty xml value.

xml-type-descriptor := xml [type-parameter]
type-parameter := < type-descriptor >

A shape belongs to type xml if its basic type is xml. A type parameter of an xml-type-descriptor must be a subtype of xml. A shape belongs to type xml<T> if all of its constituent items belong to T. So, for example, xml<xml:Element> is the type for xml values containing only elements. Note that xml<xml<T>> is the same as xml<T> and that xml<xml:Text> is the same as xml:Text.

The name of an element is represented by a string. The attributes of an element are represented by a value of type map<string>. The children of an element is represented by a value of type xml.

Singleton element, processing instruction and comment values have storage identity. Other xml values do not.

The xml:Text type is inherently immutable. This implies that both text singletons and empty xml values always have their read-only bits on. The xml:Element, xml:ProcessingInstruction and xml:Comment types are selectively immutable. The read-only bit of a xml value with length greater than one is on if and only if the read-only bit of all its constituent items is on. Immutability of xml values is deep: if the read-only bit of an xml:Element is on, then the read-only bits of the mapping representing its attributes and of the xml value representing its children are also on.

Note that although the mutable constituents of mutable xml value can be mutated, the number and the storage identity of the constituents of a xml value are fixed when the value is constructed. The storage identity of the attributes map of an element are also fixed when the element is constructed.

5.3.2.1 XML namespaces

The name of an element or attribute, which in the XML Information Set is represented by a combination of the [namespace name] and [local name] properties of an element information item (EII) or attribute information item (AII), is represented by a single expanded name string. If the [namespace name] property has no value, then the expanded name consists of just the value of the [local name] property; otherwise, the expanded name is of the form:

   {namespace-uri}local-name

where namespace-uri and local-name are the values of the [namespace name] and [local name] properties respectively.

The attributes map for an element includes not only an entry for each AII in the [attributes] property of the EII, but also an entry for each attribute in the [namespace attributes] property. The key of the entry is the string representing the name of the attribute, constructed from the AII item as described in the previous paragraph. The name of every namespace attribute will thus start with the string {http://www.w3.org/2000/xmlns/}.

The attributes map can also contain entries representing namespace attributes synthesized from the [in-scope namespaces] property. There will be a synthesized namespace attribute for every prefix other than xml that occurs as a prefix of the EII or of an AII in the element's [attributes] property and for which there is no declaration in the [namespace attributes] property. No namespace attribute will be synthesized for the default namespace. (The synthesized namespace attributes ensure that namespace prefixes will not be lost if the element is extracted into a new context.)

An xml value can be converted to an XML information set for serialization. This is done in the context of a set of namespace declarations that are in-scope from the xml value's parent element, if any. The process of converting an xml element singleton into an EII has the following stages.

  1. The [namespace name] and [local name] properties of the EII are determined from the element's expanded name.
  2. An AII is constructed for each entry in the element's attribute map, with the [namespace name] and [local name] properties of the AII determined from the entry's key, which is the attribute's expanded name. Each AII is added to either the [attributes] or [namespace attributes] property of the EII depending on the AII's [namespace name] property. The [prefix] property of each AII in the [namespace attributes] property can also be set at this point.
  3. The [namespace attributes] property of the EII is pruned by removing an AII with [normalized value] N if the AII is not a default namespace declaration, and the in-scope namespace declarations include a declaration with namespace name N, and either the EII or one of the AIIs has a [namespace name] property equal to N. (In this case, the entry for the namespace attribute would get synthesized when the information set is converted to an xml value.)
  4. For each AII in the EII's [attributes] property that has a [namespace name] property, a [prefix] property is assigned. If the namespace name is http://www.w3.org/XML/1998/namespace, then use a prefix of xml. If there is already a namespace declaration in the [namespace attributes] that declares a prefix with that namespace name, then that prefix is used. Otherwise if there is a namespace declaration in the in-scope namespaces that declares a prefix with that namespace and it is not redeclared or undeclared by the [namespace attributes], then that prefix is used. Otherwise generate a prefix and add an AII to the [namespace attributes] to declare it.
  5. If the EII has no [namespace name] property, but a default namespace declaration is in scope, then an xmlns="" AII is added to the [namespace attributes] property to undeclare the default namespace.
  6. If the EII has a [namespace name] property N, then we need to ensure that there is an applicable namespace declaration:
    1. if one of the [namespace attributes] declares N as the default namespace, then nothing needs to be done;
    2. similarly, if an in-scope namespace declaration declares N as the default namespace, and the [namespace attributes] do not undeclare it, then nothing needs to be done;
    3. otherwise, try to find, in the same way as for an AII, a prefix P which is already declared as N; if there is one, set the [prefix] property of the EII to P;
    4. otherwise, if the [namespace attributes] property does not contain a default namespace declaration or undeclaration, generate a default namespace declaration for N and add it to the [namespace attributes] property;
    5. otherwise, generate a new prefix P, set the [prefix] property of the EII to P, and add an AII to the [namespace attributes] to declare it.
  7. Generate the [in-scope namespaces] property for this EII, using the parent's in-scope namespaces and the [namespace attributes].
  8. Convert the children of the xml element, including the element children, to a list of information items, in the context of this EII's in-scope namespaces.

5.4 Structured values

Structured values are containers for other values, which are called their members. There are three basic types of structured value: list, mapping and table.

Structured values are usually mutable. Mutating a structured value changes which values it contains. Structured values can also be constructed as immutable. Immutability is deep: immutable structured values cannot contain mutable structured values; if the read-only bit of a structured value is on, then the read-only bit of each of its members is on.

The shape of the members of a structured value contributes to the shape of the structured value. A structured type descriptor describe the shape of the structured value in terms of the shapes of its members. Mutating a member of a structured value can cause the shape of the structured value to change. A structured value has an inherent type, which is a type descriptor which is part of the structured value's runtime value. At runtime, the structured value prevents any mutation that might lead to the structured value having a shape that is not a member of its inherent type. Thus a structured value belongs to a type if and only if its inherent type is a subtype of that type.

The inherent type of an immutable structured value is a singleton type with the structured value's shape as its single member. Thus, an immutable structured value belongs to a type if and only if the type contains the shape of the value.

Every structured value has a length, which is the number of its members. All structured values are iterable: the iteration sequence consists of the members of the structure and the completion type is always nil.

A structured value provides random access to its members using a key that uniquely identifies each member within the structure. A key can be out-of-line, meaning it is independent of the member, or in-line, meaning it is part of the member. The member type for a key type K in a structured type T consists of all shapes v such that there is a shape in T with key in K and shape v. A type K is an optional key type for T if there is a shape v in T and a key k in K such that v does not have a member k; a type that is not an optional key type is a required key type.

structured-type-descriptor :=
   list-type-descriptor
   | mapping-type-descriptor
   | table-type-descriptor

The following table summarizes the type descriptors for structured types.

Structured type list mapping table
Key source out-of-line out-of-line in-line
Key type integer string anydata
Type descriptor with uniform member type array map table
Type descriptor with separate member types tuple record -

5.4.1 Lists

A list value is a container that keeps its members in an ordered list. The number of members of the list is called the length of the list. The key for a member of a list is the integer index representing its position in the list, with the index of the first member being 0. For a list of length n, the indices of the members of the list, from first to last, are 0,1,...,n - 1. The shape of a list value is an ordered list of the shapes of its members.

A list is iterable: the iteration sequence consists of the members of the list in order and the iteration completion value is always nil.

The type of list values can be described by two kinds of type descriptors.

list-type-descriptor :=
   array-type-descriptor | tuple-type-descriptor

The inherent type of a list value must be a list-type-descriptor. The inherent type of a list value determines a type Ti for a member with index i. The runtime system will enforce a constraint that a value written to index i will belong to type Ti. Note that the constraint is not merely that the value looks like Ti.

Both kinds of type descriptor are covariant in the types of their members.

5.4.1.1 Array types

An array type-descriptor describes a type of list value by specifying the type that the value for all members must belong to, and optionally, a length.

array-type-descriptor := member-type-descriptor [ [ array-length ] ]
member-type-descriptor := type-descriptor
array-length :=
   int-literal
   | constant-reference-expr
   | inferred-array-length
inferred-array-length := *

A type T[] contains a list shape if all members of the list shape are in T. A type T[n] contains a list shape if in addition the length of the list shape is n.

A constant-reference-expr in an array-length must evaluate to a non-negative integer. An array length of * means that the length of the array is to be inferred from the context; this is allowed only within a type descriptor occurring in a context that is specified to be inferable; its meaning is the same as if the length was specified explicitly.

Note also that T[n] is a subtype of T[], and that if S is a subtype of T, then S[] is a subtype of T[]; this is a consequence of the definition of subtyping in terms of subset inclusion of the corresponding sets of shapes.

The type of the values in the iteration sequence of a value belonging T[] is T.

5.4.1.2 Tuple types

A tuple type descriptor describes a type of list value by specifying a separate type for each member of the list.

tuple-type-descriptor :=
   [ tuple-member-type-descriptors ]
tuple-member-type-descriptors :=
   member-type-descriptor (, member-type-descriptor)* [, tuple-rest-descriptor]
   | [ tuple-rest-descriptor ]
tuple-rest-descriptor := type-descriptor ...

A tuple type descriptor T with m member type descriptors contains a list shape L of length n if and only if:

  • m is less than or equal to n
  • the i-th member type descriptor of T contains the i-th member of L for each i from 1 to m;
  • if n is greater than m, then T has a tuple-rest-descriptor R..., and R contains the j-th member of L for each j from m + 1 to n.

Note that a tuple type where all the member-type-descriptors are the same and there is no tuple-rest-descriptor is equivalent to an array-type-descriptor with a length.

5.4.2 Mappings

A mapping value is a container where each member has a key, which is a string, that uniquely identifies within the mapping. We use the term field to mean the member together its key; the name of the field is the key, and the value of the field is that value of the member; no two fields in a mapping value can have the same name.

The shape of a mapping value is an unordered collection of field shapes one for each field. The field shape for a field f has a name, which is the same as the name of f, and a shape, which is the shape of the value of f.

Each field also has a read-only bit. This is in addition to the read-only bit of the mapping value. If a mapping value's field has its read-only bit on, then that field cannot be assigned to nor removed. If the mapping value's read-only bit is on, then the read-only bit of every field is also on. A field's read-only bit is fixed when the mapping value is constructed, and cannot be changed thereafter. If a field's read-only bit is on, the read-only bit of the value of the field is also on. The read-only bit of a field is part of the read-only aspect of the mapping value's shape.

A mapping is iterable: the iteration sequence consists of the members of the mapping and the iteration completion value is always nil. The order of the iteration sequence is implementation-dependent, but implementations are encouraged to preserve and use the order in which the fields were added.

The type of mapping values can be described by two kinds of type descriptors.

mapping-type-descriptor :=
   map-type-descriptor | record-type-descriptor

The inherent type of a mapping value must be a mapping-type-descriptor. The inherent type of a mapping value determines a type Tf for the value of the field with name f. The runtime system will enforce a constraint that a value written to field f will belong to type Tf. Note that the constraint is not merely that the value looks like Tf.

Both kinds of type descriptor are covariant in the types of their members.

5.4.2.1 Map types

A map type-descriptor describes a type of mapping value by specifying the type that the value for all fields must belong to.

map-type-descriptor := map type-parameter

A type map<T> contains a mapping shape m if every field shape in m has a value shape that is in T.

The type of the values in the iteration sequence of a value belonging map<T> is T.

If a type descriptor T has lax static typing, then the type map<T> also has lax static typing.

5.4.2.2 Record types

A record type descriptor describes a type of mapping value by specifying a type separately for the value of each field.

record-type-descriptor :=
   inclusive-record-type-descriptor | exclusive-record-type-descriptor
inclusive-record-type-descriptor :=
   record { field-descriptor* }
exclusive-record-type-descriptor :=
   record {| field-descriptor* [record-rest-descriptor] |}
field-descriptor :=
   individual-field-descriptor | record-type-inclusion
individual-field-descriptor :=
   metadata [readonly] type-descriptor field-name [? | default-value] ;
field-name := identifier
default-value := = expression
record-type-inclusion := * type-reference ;
record-rest-descriptor := type-descriptor ... ;

Each individual-field-descriptor specifies an additional constraint that a mapping value shape must satisfy for it to be a member of the described type. The constraint depends on whether ? is present:

  • if ? is not present, then the constraint is that the mapping value shape must have a field shape with the specified field-name and with a value shape that is a member of the specified type-descriptor; this is called a required field;
  • if ? is present, then the constraint is that if the mapping value shape has a field shape with the specified field-name, then its value shape must be a member of the specified type-descriptor; this is called an optional field.

If an individual-field-descriptor specifies readonly, then there is also a constraint that the field has its read-only bit set. Furthermore, the type of the field is the intersection of readonly and the type specified by the type-descriptor.

The order of the individual-field-descriptors within a record-type-descriptor is not significant. Note that the delimited identifier syntax allows the field name to be any non-empty string.

An exclusive-record-type-descriptor, which uses the {| and |} delimiters, allows exclusively the fields described. More precisely, for a mapping value shape and a record-type-descriptor, let the extra field shapes be the field shapes of the mapping value shapes whose names are not the same as field-name of any individual-field-descriptor; a mapping value shape is a member of the type described by an exclusive-record-type-descriptor only if either:

  • there are no extra fields shapes, or
  • there is a record-rest-descriptor T..., and the value shape of every extra field shape is a member of T.

An inclusive-record-type-descriptor, which uses the { and } delimiters, allows any mapping value that includes the fields described, provided that the values of all other fields are plain data. A type descriptor record { F }; is thus equivalent to record {| F; anydata...; |}, where anydata is defined below as the type descriptor for plain data.

A record type descriptor that either is an inclusive-record-type-descriptor or is an exclusive-record-type-descriptor with a record-rest-descriptor is called open; a record type descriptor that is not open is called closed.

If a record type descriptor is closed and every individual-type-descriptor specifies readonly, then it describes a type that is a subtype of readonly: a shape belongs to the type only if its read-only bit is set.

A default-value specifies a default value for the field, which is used when the record type descriptor is used to construct a mapping value but no value is specified explicitly for the field. The type descriptor contains a 0-argument function closure for each default value. The closure is created from the expression when the type descriptor is resolved. The closure is evaluated to create a field value each time the default is used in the construction of a mapping value. The default value does not affect the type described by the type descriptor.

A record-type-inclusion includes fields from a named record type. The type-reference must reference a type described by a record-type-descriptor. The field-descriptors and any record-rest-descriptor are included the type being defined; the meaning is the same as if they had been specified explicitly. For default values, the closure rather than the expression is copied in. An individual-field-descriptor in a record-type-descriptor can override an individual-field-descriptor of the same name in an included record-type-descriptor, provided the type declared for the field in the overriding field descriptor is a subtype of the type declared in the overridden field descriptor. It is an error for an record-type-descriptor to directly or indirectly include itself. A record-rest-descriptor in the including type overrides any record-rest-descriptor in the included type. For the purposes of resolving a record-type-reference, a including or included type that is an inclusive-record-type-descriptor is treated as if it were the equivalent exclusive-record-type-descriptor with an explicit record-rest-descriptor.

5.4.3 Tables

A table is a structural value, whose members are mapping values. A table provides access to its members using a key that comes from the read-only fields of the member. It keeps its members in order, but does not provide random access to a member using its position in this order.

Every table value has, in addition to its members, a key sequence, which is used to provide keyed access to its members. The key sequence is an ordered sequence of field names. The key sequence of a table is fixed when a table is constructed and cannot be changed thereafter. For each field name in the key sequence, every member of the table must have a read-only field with that name and the value of the field must be acyclic plain data. A table's key sequence determines a key value for each member of the table: if the key sequence consists of a single field name f, then the key value of a member is that value of field f of that member. If the key sequence consists of multiple field names f1,f2,...,fn where n is ≥ 2, then the key value for a member r is a tuple with members v1,v2,...,vn where vi is the value of field fi of r. A table constrains its membership so that a key value uniquely identifies a member within the table. More precisely, for every two rows ri and rj in a table with i not equal to j, the key value for ri must not be equal to the key value for rj. Key values are compared for equality using the DeepEquals abstract operation. This constraint is enforced by the table both when the table is constructed and when the table is mutated. As a special case, a table's key sequence may be empty; this represents a keyless table, whose members are not uniquely identified by a key.

The shape of a table value is a triple consisting of

  • an ordered list containing for each table member, the shape of that member
  • the table's key sequence, and
  • a set containing for each table member, the shape of the key value of that member (derived from the table members and the key sequence)
table-type-descriptor := table row-type-parameter [key-constraint]
row-type-parameter := type-parameter
key-constraint := key-specifier | key-type-constraint
key-specifier := key ( [ field-name (, field-name)* ] )
key-type-constraint := key type-parameter

The row-type-parameter specifies the shape of the table's members. A table type table<R> KC contains a table shape if and only if every table member shape belongs to R and the table shape satisfies the key constraint KC. The type specified by a row-type-parameter must be a subtype of map<any|error>

In a table-type-descriptor table<R> key(ks), R and ks must be consistent in the following sense: for each field name fi in ks, fi must be a required, read-only field of R with a type that is a subtype of anydata. A table shape satisfies a key-constraint key(ks) if and only if its key sequence is ks. A table shape satisfies a key-constraint key<K> if and and only if its set of key value shapes are a subset of K. The shape of a keyless table satisfies the key-constraint key<never>.

As with other structured values, a table value has an inherent type. The inherent type of a table value must be a table-type-descriptor with a key-constraint that is a key-specifier.

A table is iterable: the iteration sequence consists of the members of the table in order and the iteration completion value is always nil.

5.5 Behavioral values

behavioral-type-descriptor :=
   error-type-descriptor
   | function-type-descriptor
   | object-type-descriptor
   | future-type-descriptor
   | service-type-descriptor
   | typedesc-type-descriptor
   | handle-type-descriptor
   | stream-type-descriptor

5.5.1 Errors

error-type-descriptor := error [error-type-param]
error-type-param := < (detail-type-descriptor | inferred-type-descriptor) >
detail-type-descriptor := type-descriptor
inferred-type-descriptor := *

An error value provides information about an error that has occurred. Error values belong to a separate basic type; this makes it possible for language constructs to handle errors differently from other values.

The error type is inherently immutable. An error value contains the following information:

  • a message, which is a string containing a human-readable message describing the error
  • a cause, which is either nil or another error value that was the cause of this error
  • a detail, which is an immutable mapping providing additional information about the error
  • a stack trace, which is an immutable snapshot of the state of the execution stack

The detail mapping must be a subtype of map<Cloneable>.

The shapes of the message, cause and detail record are part of the shape of the error; the stack trace is not part of the shape. A type descriptor error<T> contains an error shape if T contains the shape of the detail. The type error contains a shape if its basic type is error.

A type of error<*> means that the type is a subtype of error, where the precise subtype is to be inferred from the context. This is allowed only within type descriptors occurring in a context that is specified to be inferable.

An error-type-descriptor is always definite and induces an empty set of type-ids. An intersecion type can be used to describe an error type that induces a non-empty set of type-ids.

5.5.2 Functions

function-type-descriptor := function function-signature
function-signature := ( param-list ) return-type-descriptor

A function is a part of a program that can be explicitly executed. In Ballerina, a function is also a value, implying that it can be stored in variables, and passed to or returned from functions. When a function is executed, it is passed an argument list as input and returns a value as output.

param-list :=
   required-params [, defaultable-params] [, rest-param]
   | defaultable-params [, rest-param]
   | [rest-param]
required-params := required-param (, required-param)*
required-param := [annots] [public] type-descriptor [param-name]
defaultable-params := defaultable-param (, defaultable-param)*
defaultable-param := [annots] [public] type-descriptor [param-name] default-value
rest-param := [annots] type-descriptor ... [param-name]
param-name := identifier

A param-name can be omitted from a required-param, defaultable-param or rest-param only when occuring in the function-signature of a function-type-descriptor.

The argument list passed to a function consists of zero or more arguments in order; each argument is a value, but the argument list itself is not passed as a value. The argument list must conform to the param-list as described in this section. Usually, the compiler's type checking will ensure that this is the case; if not, the function will panic.

It is convenient to consider the complete param-list as having a type. This type is described by a tuple-type-descriptor that has a member-type-descriptor for each required-param and defaultable-param, and has a tuple-rest-descriptor if and only if there is a rest-param. The i-th member-type-descriptor of the tuple type descriptor is the same as the type-descriptor of the i-th member of the param-list; the type-descriptor of the tuple-rest-descriptor, if present, is the same as the type-descriptor of the rest-param.

An argument list consisting of values v1,..., vn conforms to a param-list that has type P, if and only if for each i with 1 ≤ i ≤ n, vi belongs to Ti, where Ti is defined to be the type that contains a shape s if and only if P contains a list shape whose i-th member is s.

When an argument list is passed to a function, the non-rest parameters are initialized from the arguments in the argument list in order. The conformance of the argument list to the param-list declared for the function ensures that each parameter will be initialized to a value that belongs to the declared type of the parameter. If there is a rest-param, then that is a initialized to a newly created lists containing the remaining arguments in the argument-list; the inherent type of this list will be T[] where T is the type of the rest-param. The conformance of the argument list ensures that the members of this list will belong to type T.

A defaultable-param is a parameter for which a default value is specified. The expression specifying the default value may refer to previous parameters by name. For each defaultable parameter, the function's type descriptor includes a closure that computes the default value for the parameter using the values of previous parameters. The caller of the function uses the closures in the function's type descriptor to compute default values for any defaultable arguments that were not specified explicitly. These default values are included in the argument list passed to the function. Whether a parameter is defaultable, and what its default is, do not affect the shape of the function and thus do not affect typing. The closures computing the defaultable parameters are created when the type descriptor is resolved; the default value is computed by calling the closure each time the function is called and the corresponding parameter is not specified. Whether a parameter is defaultable is used at compile time, but the closure that computes the default value is only used at runtime.

The name of each parameter is included in the function's type descriptor. A caller of the function may specify the name of the parameter that an argument is supplying. In this case, the caller uses the parameter name at compile time in conjunction with the type descriptor to create the argument list. For each parameter name, the function's type descriptor also includes the region of code within which the name of the parameter is visible; as usual, if public is specified, the region is the entire program, otherwise it is the module in which the function type descriptor occurs. The name of a parameter can only be used to specify an argument in a function call that occurs within the region of code within which the parameter name is visible. The parameter names do not affect the shape of the function and thus do not affect typing.

The process by which the function caller creates an argument list, which may make use of arguments specified both by position and by name, is described in more detail in the section on function calls.

return-type-descriptor := [ returns [annots] type-descriptor ]

When the execution of a function returns to its caller, it returns exactly one value. A function that would in other programming languages not return a value is represented in Ballerina by a function returning (). Note that the function definition does not have to explicitly return (); a return statement or falling off the end of the function body will implicitly return ().

The value returned by a function will belong to the type specified in the return-type-descriptor. An empty return-type-descriptor is equivalent to returns ().

A return-type-descriptor may be depend on the param-list in the following way. A type-reference occurring in a return-type-descriptor can refer to a parameter name if the type of the parameter is a subtype of typedesc. If p is such a parameter, then a reference to p in the return-type-descriptor denotes the type that is the value of p. The return-type-descriptor thus denotes a distinct set of shapes for each invocation of the function. A function-signature with a return-type-descriptor that uses a type-reference to refer to a parameter name in this way is said to be dependently-typed. Functions with dependently-types signatures can be declared and used within Ballerina, but Ballerina does not yet provide a mechanism to define such functions.

Function types are covariant in their return types and contravariant in the type of their parameter lists. More precisely, a function type with return type R and parameter list type P is a subtype of a function type with return type R' and parameter list type P' if and only if R is a subtype of R' and P' is a subtype of P. A function value f belongs to a function type T if the declared type of f is a subtype of T.

5.5.3 Objects

Objects are a combination of fields along with a set of associated functions, called methods, which can be used to manipulate them. An object's methods are associated with the object when the object is constructed and cannot be changed thereafter. The fields and methods of an object are in separate symbol spaces, so it is possible for an object to have a field and a method with the same name.

An object type descriptor, in addition to describing the object type, also defines a way to construct an object of this type, in particular it provides the method definitions that are associated with the object when it is constructed.

It is also possible to have an object type descriptor that only describes an object type and cannot be used to construct an object; this is called an abstract object type descriptor.

object-type-descriptor :=
   object-type-quals object {
      object-member-descriptor*
   }
object-type-quals := (abstract | client | readonly)*
object-member-descriptor :=
   object-field-descriptor
   | object-method
   | object-type-inclusion

If object-type-quals contains the keyword abstract, then the object type descriptor is an abstract object type descriptor.

If object-type-quals contains the keyword client, then the object type is a client object type. A client object type may have remote methods; other objects types must not.

It is an error for a keyword to appear more than once in object-type-quals

5.5.3.1 Fields
object-field-descriptor :=
   metadata object-visibility-qual [readonly] type-descriptor field-name [default-value];

An object-field-descriptor specifies a field of the object. The names of all the fields of an object must be distinct.

The object basic type is selectively immutable. Each field has a read-only bit, which is on if and only if object-type-quals contains readonly or object-field-descriptor includes readonly. If the read-only bit of a field is on, it means the field cannot be assigned to after it has been constructed. The read-only bit of an object value is on if and only if the read-only bit of all its fields is on. When an object-field-descriptor includes readonly, the type of the field is the intersection of readonly and the type specified by the type-descriptor.

5.5.3.2 Methods

Methods are functions that are associated to the object and are called via a value of that type using a method-call-expr.

object-method := method-decl | method-defn
method-decl :=
   metadata
   method-defn-quals
   function method-name function-signature ;
method-defn :=
   metadata
   method-defn-quals
   function method-name function-signature method-defn-body
method-defn-quals := object-visibility-qual [remote]
method-name := identifier
method-defn-body := function-defn-body

The names of all the methods of an object must be distinct: there is no method overloading. The method name init is used for initialization and treated specially.

Within a method-defn-body, the fields and methods of the object are not implicitly in-scope; instead the keyword self is bound to the object and can be used to access fields and methods of the object.

If an object type is abstract, every method must be specified using a method-decl. Otherwise every method must be specified using a method-defn.

A method that is declared or defined with the remote qualifier is a remote method. A remote method is allowed only in a client object. A remote method is invoked using a different syntax from a non-remote method.

5.5.3.3 Visibility
object-visibility-qual := [explicit-visibility-qual]
explicit-visibility-qual := public | private

Each field and method of an object type is visible within and can be accessed from a specific region of code, which is specified by its object-visibility-qual as follows:

  • if the object-visibility qual is private, then the visibility region consists of the methods defined by this object type descriptor;
  • if the object-visibility-qual is empty, then the visibility region is the entire module containing this object type descriptor; this is called module-level visibility;
  • if the object-visibility-qual is public, then the visibility region contains all modules.

The visibility of a method or field of an abstract object type cannot be private.

5.5.3.4 Typing

The shape of an object consists of an unordered collection of object field shapes and an unordered collection of object method shapes. An object field shape or object method shape is a 4-tuple consisting of the name of the field or method, the visibility region, a remote flag and a shape for the value of the field or for the method's function.

An object type is inclusive, in a similar way to an inclusive-record-type-descriptor: an object shape belongs to an object type if it has at least the fields and methods described in the object-type-descriptor. Thus all object values belong to the type object { }.

An object-type-descriptor that has a field with name f, visibility region R and type T contains an object shape only if the object shape contains an object field shape that has name f, visibility region R and a value shape that is contained in T. An object-type-descriptor that has a method with name m, visibility region R, remote qualifier r and function type T contains an object shape only if the object shape contains an object method shape that has name m, visibility region R, a remote flag that matches r and a function value that belongs to type T.

Thus an object type T' is a subtype of an object type T only if for each field or method f of T there is a corresponding field or method f' of T such that the type of f' in T' is a subtype of the type of f in T and the visibility region of f' in T' is the same as the visibility region of f in T.

This implies that:

  • if an object type descriptor T has private fields or methods, then it is not possible to define another object type descriptor that is a subtype of T, and
  • if an object type descriptor T has fields or methods with module-level visibility, then it is possible to define another object type descriptor that is a subtype of T only within the same module as T.

The read-only bit of a field is part of the read-only aspect of the object's shape. An object-type-descriptor whose object-type-quals contains readonly denotes a type to which an object shape belongs only it has the read-only bit set.

5.5.3.5 Initialization

A non-abstract object type provides a way to initialize an object of the type. An object is initialized by:

  1. allocating storage for the object
  2. initializing each field with its default value, if it has one
  3. initializing the methods of the object using the type's method definitions
  4. calling the object's init method, if there is one

The return type of the init method must be a subtype of the union of error and nil, and must contain nil; if init returns an error, it means that initialization of the object failed. The init method can declare parameters in the same way as any other method.

At any point in the body of a init method, the compiler determines which fields are potentially uninitialized. A field is potentially uninitialized at some point if that field does not have a default value and it is not definitely assigned at that point. It is a compile error if a init method:

  • accesses a field at a point where it is potentially initialized, or
  • at a point where there is any potentially uninitialized field
    • returns nil, or
    • uses the self variable other than to access or modify the value of a field.

An object must have an init method unless all its fields have a default value. An object without an init method behaves as it had an init method with no parameters and an empty body (which will always return nil).

The visibility of the init method cannot be private.

Any init method is not part of the shape of an object, and so does not affect when an object value belongs to a type. An abstract object type must not declare an init method. The init method can be called in a method-call-expr only when the expression preceding the . is self.

5.5.3.6 Inclusion and type-ids
object-type-inclusion := * type-reference ;

Every type descriptor referenced directly or indirectly by a type-reference in an object-type-inclusion must be an object-type-descriptor, distinct-type-descriptor or intersection-type-descriptor; the referenced type descriptor will thus necessarily be definite and a subtype of object. The object-field-descriptors and object-methods from the referenced type are included in the type being defined; the meaning is the same as if they had been specified explicitly, except that the method definitions are not copied. More precisely, if an object-method is a method-defn, then a method-decl declaring the method-defn is included instead of the method-defn. An object-field-descriptor or object-method in a object-type-descriptor can override an object-field-descriptor or object-method of the same name in an included object-type-descriptor, provided the type declared for the field or method in the overriding descriptor is a subtype of the type declared in the overridden descriptor. If the including object type is not abstract, then each included method-decl, must be overridden by a method-defn. It is an error for an object-type-descriptor to directly or indirectly include itself.

If a non-abstract object type To includes an abstract object type Ta, then each method declared in Ta must be defined in To using a method-defn with the same visibility. If Ta has a method or field with module-level visibility, the To must be in the same module.

An object-type-descriptor is always definite. The set of type-ids induced by an object-type-descriptor is the union of the set of type-ids induced by the type descriptors that it includes. An induced typeid is primary in an object-type-descriptor if and only if it is primary in any of the included type descriptors. It is an error if the induced set of type-ids includes a non-public type-id from another module.

5.5.4 Futures

future-type-descriptor := future [type-parameter]

A future value refers to a named worker, which will return a value. A future value belongs to a type future<T> if the return type of the named worker is a subtype of T.

A value belongs to a type future (without the type-parameter) if it has basic type future.

5.5.5 [Preview] Services

service-type-descriptor := service

A service is like an object, but differs in the following respects:

  • a method of a service can be declared as a resource method, but cannot be declared as a remote method
  • a service is stateless and does not have fields

A resource method is a special kind of method, with associated configuration data, that is invoked in response to network messages received by a Listener. A service can be thought of as the dual of a client object.

All service values belong to the type service.

It is planned that a future version of Ballerina will provide a mechanism that allows more precise typing of services. In the meantime, implementations can use annotations on type definitions to support this.

5.5.6 Type descriptors

typedesc-type-descriptor := typedesc [type-parameter]

A type descriptor value is an immutable value representing a resolved type descriptor. The type typedesc contains all values with basic type typedesc. A typedesc value t belongs to a type typedesc<T> if and only if the type described by t is a subtype of T. The typedesc type is thus covariant with its type parameter.

Referencing an identifier defined by a type definition in an expression context will result in a type descriptor value.

5.5.7 Handles

handle-type-descriptor := handle

A handle value is a reference to storage managed externally to a Ballerina program. Handle values are useful only in conjunction with functions that have external function bodies; in particular, a new handle value can be created only by a function with an external function body. Handle values are inherently immutable.

A value belongs to a type handle if it has a basic type of handle.

5.5.8 [Preview] Streams

stream-type-descriptor := stream [stream-type-parameters]
stream-type-parameters := < type-descriptor [, type-descriptor]>

A stream is an object-like value that can generate a sequence of values. There is also a value associated with the completion of the generation of the sequence, which is either nil, indicating the generation of the sequence completed successfully, or an error. A stream belongs to type stream<T,C> if the values in the generated sequence all belong to T and if the completion value belongs to C. The type stream<T> is equivalent to stream<T,()>. A value belongs to a type stream (without the type-parameter) if it has basic type stream. A type stream<T,C> where C does not include nil represents an unbounded stream.

A stream supports two primitive operations: a next operation and a close operation. The next operation has the same semantics as the next method on the Iterator abstract object type. The close operation informs the stream that there will be no more next operations and thus allows the stream to release resources used by the stream; the close operation on a stream<T,C> has a result of type C?, where nil means that the close operation was successful.

The normal implementation of a stream<T,C> is a wrapper around an object belonging to abstract object type StreamImplementor<T,C>. The next and close operations on the stream delegate to the next and close methods on the StreamImplementor.

The stream module of the lang library provides additional operations on stream that can be implemented in terms of the primitive next and close operations.

A stream is iterable. A stream of type stream<T,C> has value type T and completion type C. Calling the next method on the iterator created for an iteration has the same effect as performing the next operation on the stream. The stream does not keep a copy of the sequence of values returned by the next operation. Any subsequent iteration operation on the same stream will not generate further values, so the iteration sequence for iterations other than the first will be the empty sequence.

Note that in this version of Ballerina the stream type is not an object type because Ballerina does not yet support parameterized object types.

5.6 Other type descriptors

other-type-descriptor :=
   | type-reference
   | singleton-type-descriptor
   | any-type-descriptor
   | never-type-descriptor
   | readonly-type-descriptor
   | distinct-type-descriptor
   | union-type-descriptor
   | intersection-type-descriptor
   | optional-type-descriptor
   | anydata-type-descriptor
   | json-type-descriptor
   | byte-type-descriptor
   | ( type-descriptor )

It is important to understand that the type descriptors specified in this section do not add to the universe of values. They are just adding new ways to describe subsets of this universe.

5.6.1 Type reference

type-reference := identifier | qualified-identifier

A type descriptor can use a type-reference to refer to a type definition in the same module or another module.

A type-reference referring to a type descriptor T is definite if and only if T. If it is, the type-ids induced by are the same as those induced by T and a type-id is primary in t if and only if it is primary in T. It is an error if the induced set of type-ids includes a non-public type-id from another module.

5.6.2 Singleton types

singleton-type-descriptor := simple-const-expr

A singleton type is a type containing a single shape. A singleton type is described using an compile-time constant expression for a single value: the type contains the shape of that value. Note that it is possible for the variable-reference within the simple-const-expr to reference a structured value; in this case, the value will have its read-only bit set; a value without its read-only bit set will thus not belong to any singleton type.

5.6.3 Any type

any-type-descriptor := any

The type descriptor any describes the type consisting of all values other than errors. A value belongs to the any type if and only if its basic type is not error. Thus all values belong to the type any|error. Note that a structure with members that are errors belongs to the any type.

The any-type-descriptor is not definite.

5.6.4 Never type

never-type-descriptor := never

The type descriptor never describes the type that does not contain any shapes. No value ever belongs to the never.

This can be useful to describe for the return type of a function, if the function never returns. It can also be useful as a type parameter. For example, xml<never> describes the an xml type that has no constituents, i.e. the empty xml value.

Note that for anytype T, the type T|never is the same as T.

5.6.5 Readonly type

readonly-type-descriptor := readonly

A shape belongs to the type readonly if its read-only bit is on.

A value belonging to an inherently immutable basic type will always have its read-only bit on. These basic types are:

  • all simple types
    • nil
    • boolean
    • int
    • float
    • decimal
  • string
  • error
  • function
  • service
  • typedesc
  • handle

A value belonging to a selectively immutable basic type may have its read-only bit on. These basic types are:

  • xml
  • list
  • mapping
  • table
  • object

5.6.6 Distinct types

distinct-type-descriptor := distinct type-descriptor

Each occurrence of a distinct-type-descriptor describes a type that is distinct from any other occurrence of a distinct-type-descriptor. Only object and error values can belong to a type described by a distinct-type-descriptor.

The type denoted by a distinct-type-descriptor D, where D is distinct T, and d is the type-id of this occurrence of D, contains a shape s of an object or error value if and only if both T contains s and the set of type-ids of s contains d. The set of type-ids induced by D consists of d as the primary type-id and the type-ids induced by T as the secondary type-ids. The type T must be definite and must be a subtype of object or a subtype of error. The type D is always definite. Note that D is always a proper subtype of T.

5.6.7 Union types

union-type-descriptor := type-descriptor | type-descriptor

The set of shapes denoted by an union type T1|T2 is the union of the set of shapes denoted by T1 and the set of shapes denoted by T2. Thus, the type T1|T2 contains a shape if and only if either the type denoted by T1 contains the shape or the type denoted by T2 contains the shape.

A union type T1|T2 is definite if and only if both T1 and T2 are definite and the set of type-ids induced by T1 and T2 are the same. If it is definite, then it induces the same set of type-ids as T1 and T2, and a type-id is primary if it is primary in either T1 or T2.

5.6.8 Intersection types

intersection-type-descriptor := type-descriptor & type-descriptor

The set of shapes denoted by an intersection type T1&T2 is the intersection of the set of shapes denoted by T1 and the set of shapes denoted by T2. Thus, the type T1&T2 contains a shape if and only if the types denoted by T1 and T2 both contain the shape. It is a error to have an intersection type that denotes an empty set of shapes. In an intersection type T1&T2, it is an error if either T1 or T2 refer to an object type that is not abstract; it is also an error if T1 and T2 are both function types unless there is a single function-type-descriptor that denotes a type that is a subtype of both T1 and T2.

An intersection type T1&T2 is definite if and only if both T1 and T2 are definite. If it is definite, then it induces the union of the set of type-ids induced by T1 and T2, and a type-id is primary if it is primary in either T1 or T2.

Intersection types are particularly useful in conjunction with readonly. A set of readonly shapes can be described by readonly&T, where T describes the primary aspect of the shape.

5.6.9 Optional types

optional-type-descriptor := type-descriptor ?

A type T? means the union of T and (). It is completely equivalent to T|().

5.6.10 Anydata type

anydata-type-descriptor := anydata

The type descriptor anydata describes the type of plain data. The type anydata contains a shape if and only if it is the shape of a value that is plain data. The anydata type can thus be defined as:

  () | boolean | int | float | decimal
    | string | xml
    | anydata[] | map<anydata> | table<map<anydata>>

5.6.11 JSON types

json-type-descriptor := json

The json type is designed for processing data expression in JSON format. It is a built-in name for a union defined as follows:

type json = () | boolean | int | float | decimal | string | json[] | map<json>;

In addition, the json type is defined to have lax static typing.

5.6.12 Byte type

byte-type-descriptor := byte

The byte type is a predefined name for a union of the int values in the range 0 to 255 inclusive. It is equivalent to the built-in subtype int:Unsigned8.

5.7 Built-in abstract object types

There are several abstract object types that are built-in in the sense that the language treats objects with these types specially. There are two kinds of abstract object type:

5.7.1 Iterator

A value of iterable type with iteration value type T and iteration completion type C provides a way of creating an iterator object that belongs to the object type

    abstract object {
       public next() returns record {| T value; |}|C;
    }

In this specification, we refer to this type as Iterator<T,C>.

Conceptually an iterator is at a position between members of the iteration sequence. Possible positions are at the beginning (immediately before the first member if any), between members and at the end (immediately after the last member if any). A newly created iterator is at the beginning position. For an empty sequence, there is only one possible position which is both at the beginning and at the end.

The next() method behaves as follows:

  • if the iteration has encountered an error, return an error value
  • otherwise, if the iterator has completed successfully by reaching the end position without an error, return nil
  • otherwise
    • move the iterator to next position, and
    • return a record { value: v } where v is the member of the sequence between the previous position and the new position

Mutation of a structured value during iteration is handled as follows. A call to next() must panic if there has been any mutation to the structured value since the iterator was created other than the following:

  • removing a member of the structured value that a preceding call to the iterator has already returned;
  • changing the member associated with an existing key.

In the latter case, next() must return the value associated with the key at the point when next() is called.

Note that it is not possible for the next() method simply to return the values in the iteration sequence, since there would be no way to distinguish a nil or error value that is part of the iteration sequence from a nil or error value that represents the result of the iteration.

5.7.2 Iterable

An object belongs to the abstract object type Iterable<T,C> if it has a method named iterator with no arguments and a return type that is subtype of Iterator<T,C> and it belongs to the distinct type Iterable defined in lang.object. An object that belongs to Iterable<T,C> is iterable: the object returned by the iterator method determines the iteration sequence and iteration completion value.

5.7.3 StreamImplementor

An object belongs to the abstract object type StreamImplementor<T,C> if it belongs to Iterator<T,C> and also optionally has a method close() with return value C?. This is equivalent to belonging to the following type.

    abstract object {
       public next() returns record {| T value; |}|C;
    }
    | abstract object {
       public next() returns record {| T value; |}|C;
       public close() returns C?;
    }

The close method says that there will be no more calls to the next method. Any call to a next method after the close method has been called must result in a panic. A missing close method behaves like a close method that puts the object into a closed state, in which calls to next will result in a panic, and then immediately returns ().

5.7.4 Listener

The Listener type is defined in lang.object as follows.

distinct abstract object {
   public function attach(service s, string? name = ()) returns error?;
   public function detach(service s) returns error?;
   public function start() returns error?;
   public function gracefulStop() returns error?;
   public function immediateStop() returns error?;
}

Note that if an implementation does precise service typing using annotations on type definitions, it will need to treat Listener as being parameterized in the precise service type that is used to the first argument to attach.

5.7.5 RawTemplate

The RawTemplate type describes the type of object constructed by a raw template expression. The type is defined in lang.object as follows.

distinct abstract object {
    public (readonly & string[]) strings;
    public (any|error)[] insertions;
}

5.8 Abstract operations

These section specifies a number of operations that can be performed on values. These operations are for internal use by the specification. These operations are named in CamelCase with an initial upper-case letter to distinguish them from functions in the lang library.

5.8.1 FillMember

The FillMember(s, k) operation is defined for a structured value s and an out-of-line key value k. It can be performed when s does not have a member with key k; if it succeeds, it will result in a member with key k being added to s. It will succeed if the inherent type of s allows the addition of a member with key k and there is a way to construct a filler value for the type descriptor that the inherent type of s requires for member k. The following table specifies when and how a filler value can be constructed for a type descriptor.

Type descriptor Filler value When available
() ()
boolean false
int 0
float +0.0f
decimal +0.0d
string ""
array or tuple type descriptor [] if that is a valid constructor for the type
map or record type descriptor { } if that is a valid constructor for the type
table empty table (with no rows)
object new T() if this is valid and its static type does not include error, where T is the object type descriptor (an abstract object type will not have a filler value)
stream empty stream
xml xml``
built-in subtype of xml xml`` if this belongs to the subtype, i.e. if the subtype is xml:Text
singleton the single value used to specify the type
union () if () is a member of the union
the filler value for basic type B if all members of the union belong to a single basic type B, and the filler value for B also belongs to the union
T? ()
any ()
anydata ()
byte 0
built-in subtype of int 0
json ()

5.8.2 Cloning

There are two cloning operations. Both of these operate on values belonging to the Cloneable type defined as follows:

public type Cloneable readonly|xml|Cloneable[]|map<Cloneable>|table<map<Cloneable>>;

This type is defined in the lang.value module of the lang library. In this document, the type will be referred to as value:Cloneable.

5.8.2.1 Clone

Clone(v) is defined for any value v that belongs to the type value:Cloneable. It performs a deep copy, recursively copying all structural values and their members and recursively copying all sequence values and their constituents. Clone(v) for an immutable value v returns v. If v is of a basic type that has an inherent type, Clone(v) has the same inherent type as v. The graph of references of Clone(v) must have the same structure as that of v. This implies that the number of distinct references reachable from Clone(v) must be the same as the number of distinct references reachable from v. Clone(v) must terminate even if v has cycles.

Clone(v) cannot be implemented simply by recursively calling Clone on all members of v. Rather Clone must maintain a map that records the result of cloning each reference value. When a Clone operation starts, this map as empty. When cloning a reference value, it must use the result recorded in the map if there is one.

The Clone operation is exposed by the clone function in the lang.value module of the lang library.

5.8.2.2 ImmutableClone

ImmutableClone(v) is defined for any value v that belongs to the type value:Cloneable. It performs a deep copy of v similar to Clone(v), except that newly constructed values will be constructed as immutable and so have their read-only bit on. Any immutable value is not copied. So the result of Immutable always has its read-only bit on.

Like Clone, ImmutableClone must preserve graph structure, including cycles. Conceptually the whole graph is constructed before being made immutable.

The ImmutableClone operation is exposed by the cloneReadOnly function in the lang.value module of the lang library.

5.8.3 DeepEquals

DeepEquals(v1, v2) is defined for any values v1, v2 that belong to type anydata. It returns true or false depending of whether the primary aspect of the shape v1 and of v2 are the same. In other words, DeepEquals returns true if and only if the values are the same ignoring whether read-only bits are on or off. DeepEquals(v1, v2) must terminate for any values v1 and v2 of type anydata, even if v1 or v2 have cycles. DeepEquals(v1, v2) returns true if v1 and v2 have the same shape, even if the graphs of references of v1 and v2 have different structures. If two values v1 and v2 have different basic types, then DeepEquals(v1, v2) will be false.

The possibility of cycles means that DeepEquals cannot be implemented simply by calling DeepEquals recursively on members. Rather DeepEquals must maintain a mapping that records for each pair of references whether it is already in process of comparing those references. When a DeepEquals operation starts, this map is empty. Whenever it starts to compare two references, it should see whether it has already recorded that pair (in either order), and, if it has, proceed on the assumption that they compare equal.

DeepEquals(Clone(x), x) is guaranteed to be true for any value of type anydata.

5.8.4 NumericConvert

NumericConvert(t, v) is defined if t is the typedesc for float, decimal or int, and v is a numeric value. It converts v to a value in t, or returns an error, according to the following table.

from \ to float decimal int
float unchanged closest math value round, error for NaN or out of int range
decimal closest math value unchanged
int same math value same math value unchanged

5.9 Functionally constructible types

A type that is functionally constructible allows values of the type to be destructured and constructed using a functional syntax. A functionally constructible type has a list of construction parameters, where each parameter is identified either by a position or by a name, and defines a decomposition that maps a value of the type into an argument list, with one member for each construction parameter.

functionally-constructible-type-reference := error | type-reference

A functionally-constructible-type-reference must refer to a type that is functionally constructible.

The following table describes the functionally constructible types and their construction parameters. The last column specifies what happens if there is no argument for a parameter when a value is constructed.

Constructed type Parameter id Parameter type Parameter description Parameter missing
xml:Element Position 1 string name not allowed
Position 2 map<string> attribute map defaults to empty map
Position 3 xml children defaults to empty xml value
xml:ProcessingInstruction Position 1 string target not allowed
Position 2 string content defaults to empty string
xml:Comment Position 1 string content defaults to empty string
xml:Text Position 1 string characters defaults to empty string
error<T> Position 1 string message not allowed
Position 2 error? cause defaults to nil
Name k type of field k of T, which will be a subtype of value:Cloneable field k of detail record detail record has no field k

In the above table, argument position 1 refers to the first positional argument.

A type descriptor that is a subtype of error is functionally constructible only if it is definite. In particular, it may be an intersection-type-descriptor.

5.10 Binding patterns and variables

5.10.1 Binding patterns

Binding patterns are used to support destructuring, which allows different parts of a single structured value each to be assigned to separate variables at the same time.

binding-pattern :=
   capture-binding-pattern
   | wildcard-binding-pattern
   | list-binding-pattern
   | mapping-binding-pattern
   | functional-binding-pattern
capture-binding-pattern := variable-name
variable-name := identifier
wildcard-binding-pattern := _
list-binding-pattern := [ list-member-binding-patterns ]
list-member-binding-patterns :=
   binding-pattern (, binding-pattern)* [, rest-binding-pattern]
   | [ rest-binding-pattern ]
mapping-binding-pattern := { field-binding-patterns }
field-binding-patterns :=
   field-binding-pattern (, field-binding-pattern)* [, rest-binding-pattern]
   | [ rest-binding-pattern ] 
field-binding-pattern :=
   field-name : binding-pattern
   | variable-name
rest-binding-pattern := ... variable-name
functional-binding-pattern := functionally-constructible-type-reference ( arg-list-binding-pattern )
arg-list-binding-pattern :=
   positional-arg-binding-patterns [, other-arg-binding-patterns]
   | other-arg-binding-patterns
positional-arg-binding-patterns := positional-arg-binding-pattern (, positional-arg-binding-pattern)*
positional-arg-binding-pattern := binding-pattern
other-arg-binding-patterns :=
   named-arg-binding-patterns [, rest-binding-pattern]
   | [rest-binding-pattern]
named-arg-binding-patterns := named-arg-binding-pattern (, named-arg-binding-pattern)*
named-arg-binding-pattern := arg-name = binding-pattern

A binding pattern may succeed or fail in matching a value. A successful match causes values to be assigned to all the variables occurring the binding-pattern.

A binding pattern matches a value in any of the following cases.

  • a capture-binding-pattern always matches a value and causes the matched value to be assigned to named variable;
  • a wildcard-binding-pattern matches a value if the value belongs to type any, in other words if the basic type of the value is not error; it does not cause any assignments to be made;
  • a list-binding-pattern with m binding patterns matches a list with n members if m is less than or equal to n, and the i-th binding pattern matches the i-th member of the list for each i in 1 to m, and either m is equal to n or the list-binding-pattern includes a rest-binding-pattern; if there is a rest-binding-pattern ...v, then a successful match causes a new list value consisting of all members of the matched list except for the the first m values to be assigned to v;
  • a mapping-binding-pattern { f1: p1, f2: p2, ..., fn: pn, r } matches a mapping value m that includes fields f1, f2, ... , fn if pi matches the value of field fi for each i in 1 to n; if r is ...v then a successful match causes a new mapping value consisting of all the other fields to be assigned to v; a field-binding-pattern consisting of just a variable-name x is equivalent to a field-binding-pattern x: x;
  • a functional-binding-pattern T(p1, p1,...,pm, arg1 = pm + 1, arg2 = pm + 2, ..., argm + n = pn, r) matches a value if the shape of the value belongs to T and the decomposition of the value has m or more positional arguments and for each i in 1 to m, pi matches the i-th positional argument, and for each j in 1 to n, the decomposition has a named argument argj and pm + j matches the value of that named argument; if r is ...v then a successful match causes a new mapping value consisting of all named arguments in the decomposition other than arg1, arg2, ... , argn to be assigned to v;

All the variables in a binding-pattern must be distinct e.g. [x, x] is not allowed.

Given a type descriptor for every variable in a binding-pattern, there is a type descriptor for the binding-pattern that will contain a value just in case that the binding pattern successfully matches the value causing each variable to be assigned a value belonging to the type descriptor for that variable.

  • for a capture-binding-pattern, the type descriptor is the type descriptor for that variable;
  • for a wildcard-binding-pattern, the type descriptor is any
  • for a list-binding-pattern, the type descriptor is a tuple type descriptor;
  • for a mapping-binding-pattern, the type descriptor is a record type descriptor;
  • for an error-binding-pattern, the type descriptor is an error type descriptor.

5.10.2 Typed binding patterns

typed-binding-pattern := inferable-type-descriptor binding-pattern
inferable-type-descriptor := type-descriptor | var

A typed-binding-pattern combines a type-descriptor and a binding-pattern, and is used to create the variables occurring in the binding-pattern. If var is used instead of a type-descriptor, it means the type is inferred. How the type is inferred depends on the context of the typed-binding-pattern. An inferable-type-descriptor is an inferable context for a type descriptor, which means that * can be used with the type descriptor to infer certain parts of it.

The simplest and most common form of a typed-binding-pattern is for the binding pattern to consist of just a variable name. In this case, the variable is constrained to contain only values matching the type descriptor.

When the binding pattern is more complicated, the binding pattern must be consistent with the type-descriptor, so that the type-descriptor unambiguously determines a type for each variable occurring in the binding pattern. A binding pattern occurring in a typed-binding-pattern must also be irrefutable with respect to the type of value against which it is to be matched. In other words, the compiler will ensure that matching such a binding pattern against a value will never fail at runtime.

5.10.3 Variable and identifier scoping

For every variable, there is place in the program that declares it. Variables are lexically scoped: every variable declaration has a scope which determines the region of the program within which the variable can be referenced.

There are two kinds of scope: module-scope and block-scope. A variable with module-scope can be referenced anywhere within a module; if declared public, it can also be referenced from outside the module.

Identifiers with module-scope are used to identify not only variables but other module-level entities such as functions. Within module-scope, identifiers are separated into three symbol spaces:

  • the main symbol space includes identifiers for variables, constants, types, functions and other identifiers that do not belong to any of the other two symbol spaces;
  • the prefix symbol space contains prefixes declared by import declarations and XML namespace declaration statements;
  • the annotation tag symbol space contains annotation tags declared by annotation declarations.

The prefix symbol space is special in that it is associated with a source part rather than a module.

Block-scope is divided into symbol spaces in the same way as module-scope, except that block-scope does not have a symbol space for annotation tags, since annotation tags cannot be declared with block-scope.

An identifier declared with block-scope can be referenced only within a particular block (always delimited with curly braces). Block-scope variables are created by a variety of different constructs, many of which use a typed-binding-pattern. Parameters are treated as read-only variables with block-scope.

It is not an error if an identifier is declared with block-scope and there is already a declaration of the same identifier in the same symbol space with module-scope. In this case, the block-scope declaration will hide the module-scope declaration for the region where the block-scope declaration is in scope. However, it is a compile error if an identifier is declared with block-scope and its scope overlaps with the scope of another declaration of the same identifier in the same symbol space also with block-scope.

6. Expressions

expression := 
   literal
   | string-template-expr
   | xml-template-expr
   | raw-template-expr
   | structural-constructor-expr
   | new-expr
   | service-constructor-expr
   | variable-reference-expr
   | field-access-expr
   | optional-field-access-expr
   | xml-attribute-access-expr
   | annot-access-expr
   | member-access-expr
   | function-call-expr
   | method-call-expr
   | functional-constructor-expr
   | anonymous-function-expr
   | let-expr
   | type-cast-expr
   | typeof-expr
   | unary-expr
   | multiplicative-expr
   | additive-expr
   | shift-expr
   | range-expr
   | numerical-comparison-expr
   | is-expr
   | equality-expr
   | binary-bitwise-expr
   | logical-expr
   | conditional-expr
   | checking-expr
   | trap-expr
   | query-expr
   | xml-navigate-expr
   | ( expression )

For simplicity, the expression grammar is ambiguous. The following table shows the various types of expression in decreasing order of precedence, together with associativity.

Operator Associativity
m:x
x.k
x.@a
f(x)
x.f(y)
x[y]
new T(x)
+x
-x
~x
!x
<T> x
typeof x check x checkpanic x trap x
x * y
x / y
x % y
left
x + y
x - y
left
x << y
x >> y
x >>> y
left
x ... y
x ..< y
non
x < y
x > y
x <= y
x >= y
x is y
non
x == y
x != y
x === y
x !== y
left
x & y left
x ^ y left
x | y left
x && y left
x || y left
x ?: y right
x ? y : z right
(x) => y
let x = y in z
from x in y select z
right

6.1 Expression evaluation

When the evaluation of an expression completes normally, it produces a result, which is a value. The evaluation of an expression may also complete abruptly. There are two kinds of abrupt completion: check-fail and panic. With both kinds of abrupt completion there is an associated value, which always has basic type error.

The following sections describes how each kind expression is evaluated, assuming that evaluation of subexpressions complete normally. Except where explicitly stated to the contrary, expressions handle abrupt completion of subexpressions as follows. If in the course of evaluating an expression E, the evaluation of some subexpression E1 completes abruptly, then then evaluation of E also completes abruptly in the same way as E1.

6.2 Static typing of expressions

A type is computed for every expression at compile type; this is called the static type of the expression. The compiler and runtime together guarantee that if the evaluation of an expression at runtime completes normally, then the resulting value will belong to the static type. A type is also computed for check-fail abrupt completion, which will be a (possibly empty) subtype of error; however, for panic abrupt completion, no type is computed.

The detailed rules for the static typing of expressions are quite elaborate and are not yet specified completely in this document.

6.2.1 Lax static typing

In some situations it is convenient for static typing to be less strict than normal. One such situation is when processing data in Ballerina using a static type that is less precisse than the type that the data is in fact expected to belong to. For example, when the Ballerina json type is used for the processing of data in JSON format, the Ballerina static type will not capture the constraints of the particular JSON format that is been processed.

Ballerina supports this situation through the concept of lax static typing, which has two parts: the first part is that a type descriptor can be classified as lax; the second part is that particular kinds of expression can have less strict static typing rules when the static type of a subexpression is described by a lax type descriptor. With these less strict rules, a potential type error that would have been a compile-time error according to the normal strict static typing rules would instead be allowed at compile-time and result in an error value at runtime; the effect is thus that some static type-checking is instead done dynamically.

In this version of Ballerina, only the first step has been taken towards supporting this concept. There is a fixed set of type descriptors that are classified as lax: specifically json is lax, and map<T> is lax if T is lax. The only kinds of expression for which lax typing rules are specified are field-access-expr and optional-field-access-expr.

6.2.2 Contextually expected type

For a context in which an expression occurs, there may be a type descriptor that describes the static type that the expression is expected to have. This is called the contextually expected type. For example, if a variable is declared by a type descriptor TD, then TD will be the contextually expected type for the expression initializing the variable. A type descriptor must be resolved before it can be used to provide a contextually expected type.

Many kinds of expression that construct values use the contextually expected type to determine the type of value constructed, rather than requiring the type to be specified explicitly. For each such kind of expression, there is a set of basic types (most often consisting of a single basic type) that the value constructed by that kind of expression will always belong to. In this case, the contextually expected type is narrowed by intersecting it with this set of basic types; this narrowed type is called the applicable contextually expected type. The narrowing is performed on the type descriptor by first normalizing the type descriptor into a union, where each member of the union is not a union and describes shapes from a single basic type, and then eliminating any members of the union with the wrong basic type; if this leaves no members, then it is a compile-time error; if it leaves a single member of the union, then the the applicable contextually expected type is this single member, otherwise it is a union of the remaining members.

Note the language provides a way to say that the type of a variable is to be inferred from the static type of the expression used to initialize the variable. In this case, there is no contextually expected type for the evaluation of the expression. Not having a contextually expected type is different from having a contextually expected type that allows all values.

6.2.3 Precise and broad types

There is an additional complexity relating to inferring types. Expressions in fact have two static types, a precise type and a broad type. Usually, the precise type is used. However, in a few situations, using the precise type would be inconvenient, and so Ballerina uses the broad type. In particular, the broad type is used for inferring the type of an implicitly typed non-final variable. Similarly, the broad type is used when it is necessary to infer the member type of the inherent type of a structured value.

In most cases, the precise type and the broad type of an expression are the same. For a compound expression, the broad type of an expression is computed from the broad type of the sub-expressions in the same way as the precise type of the expression is computed from the precise type of sub-expressions. Therefore in most cases, there is no need to mention the distinction between precise and broad types.

The most important case where the precise type and the broad type are different is literals. The precise type is a singleton type containing just the shape of the value that the literal represents, whereas the broad type is the precise type widened to contain the entire basic type of which it is a subtype. For example, the precise type of the string literal "X" is the singleton type "X", but the broad type is string.

For a type-cast-expr, the precise type and the broad type are the type specified in the cast.

6.3 Casting and conversion

Ballerina makes a sharp distinction between type conversion and type casting.

Casting a value does not change the value. Any value always belongs to multiple types. Casting means taking a value that is statically known to be of one type, and using it in a context that requires another type; casting checks that the value is of that other type, but does not change the value.

Conversion is a process that takes as input a value of one type and produces as output a possibly distinct value of another type. Note that conversion does not mutate the input value.

Ballerina always requires programmers to make conversions explicit, even between different types of number. There is only one exception: a value of type xml:Text can be implicitly converted to a string. If a value of type xml:Text is the empty xml value, then it is implicitly converted to the empty string; otherwise the value is implicitly converted to a string that has a constituent character for each singleton item in the xml:Text sequence.

6.4 Constant expressions

const-expr := 
   literal
   | string-template-expr
   | xml-template-expr
   | raw-template-expr
   | structural-constructor-expr
   | functional-constructor-expr
   | constant-reference-expr
   | type-cast-expr
   | unary-expr
   | multiplicative-expr
   | additive-expr
   | shift-expr
   | range-expr
   | numerical-comparison-expr
   | is-expr
   | equality-expr
   | binary-bitwise-expr
   | logical-expr
   | conditional-expr
   | ( const-expr )

A value resulting from the evaluation of a const-expr always has its read-only bit on.

Within a const-expr, any nested expression must also be a const-expr. A functional-constructor-expr within a const-expr must not construct an error value.

constant-reference-expr := variable-reference-expr

A constant-reference-expr must reference a constant defined with module-const-decl.

A const-expr is evaluated at compile-time. Constructors called within a const-expr construct their values as immutable. Note that the syntax of const-expr does not allow for the construction of error values. The result of a const-expr is always immutable.

simple-const-expr :=
  nil-literal
  | boolean-literal
  | [Sign] int-literal
  | [Sign] floating-point-literal
  | string-literal
  | constant-reference-expr

A simple-const-expr is a restricted form of const-expr used in contexts where various forms of constructor expression would not make sense. Its semantics are the same as a const-expr.

6.5 Literals

literal :=
   nil-literal
   | boolean-literal
   | numeric-literal
   | string-literal
   | byte-array-literal
numeric-literal := int-literal | floating-point-literal

A numeric-literal represents a value belonging to one of the basic types int, float or decimal. The basic type to which the value belongs is determined as follows:

The precise type of a numeric-literal is the singleton type containing just the shape of the value that the numeric-literal represents. The broad type is the basic type of which the precise type is a subset.

byte-array-literal := Base16Literal | Base64Literal
Base16Literal := base16 WS ` HexGroup* WS `
HexGroup := WS HexDigit WS HexDigit
Base64Literal := base64 WS ` Base64Group* [PaddedBase64Group] WS `
Base64Group :=
   WS Base64Char WS Base64Char WS Base64Char WS Base64Char
PaddedBase64Group :=
   WS Base64Char WS Base64Char WS Base64Char WS PaddingChar
   | WS Base64Char WS Base64Char WS PaddingChar WS PaddingChar
Base64Char := A .. Z | a .. z | 0 .. 9 | + | /
PaddingChar := =
WS := WhiteSpaceChar*

The static type of byte-array-literal is byte[N], where N is the number of bytes encoded by the Base16Literal or Base64Literal. The inherent type of the array value created is also byte[N].

6.6 Template expressions

Template expressions make use of strings enclosed in backticks with interpolated expressions.

BacktickString :=
  ` BacktickItem* Dollar* `
BacktickItem :=
   BacktickSafeChar
   | BacktickDollarsSafeChar
   | Dollar* interpolation
interpolation := ${ expression }
BacktickSafeChar := ^ ( ` | $ )
BacktickDollarsSafeChar :=  $+ ^ ( { | ` | $)
Dollar := $

6.6.1 String template expression

string-template-expr := string BacktickString

A string-template-expr interpolates the results of evaluating expressions into a literal string. The static type of the expression in each interpolation must be a simple type and must not be nil. Within a BacktickString, every character that is not part of an interpolation is interpreted as a literal character. A string-template-expr is evaluated by evaluating the expression in each interpolation in the order in which they occur, and converting the result of the each evaluation to a string as if using by toString function of the lang.value module of the lang library. The result of evaluating the string-template-expr is a string comprising the literal characters and the results of evaluating and converting the interpolations, in the order in which they occur in the BacktickString.

A literal ` can be included in string template by using an interpolation ${"`"}.

6.6.2 XML template expression

xml-template-expr := xml BacktickString

An XML template expression constructs an xml value as follows:

  1. The backtick string is parsed to produce a string of literal characters with interpolated expressions
  2. The result of the previous step is parsed as XML content. More precisely, it is parsed using the production content in the W3C XML Recommendation. For the purposes of parsing as XML, each interpolated expression is interpreted as if it were an additional character allowed by the CharData and AttValue productions but no other. The result of this step is an XML Infoset consisting of an ordered list of information items such as could occur as the [children] property of an element information item, except that interpolated expressions may occur as Character Information Item or in the [normalized value] of an Attribute Information Item. Interpolated expressions are not allowed in the value of a namespace attribute.
  3. This infoset is then converted to an xml value, as described in the XML type section, together with an ordered list of interpolated expressions, and for each interpolated expression a position within the XML value at which the value of the expression is to be inserted.
  4. The static type of an expression occurring in an attribute value must be a simple type and must not be nil. The static type type of an expression occurring in content can either be xml or a non-nil simple type.
  5. When the xml-template-expr is evaluated, the interpolated expressions are evaluated in the order in which they occur in the BacktickString, and converted to strings if necessary. A new copy is made of the xml value and the result of the expression evaluations are inserted into the corresponding position in the newly created xml value. This xml value is the result of the evaluation.

An xml-template-expr occurring within a const-expr will construct an xml value that has its read-only bit on.

6.6.3 Raw template expression

raw-template-expr := BacktickString

A raw-template-expr constructs an object belonging to the abstract RawTemplate object type.

A raw-template-expr is evaluated by

  • constructing an array v whose members are the result of evaluating each interpolation's expression in the order in which they occur in the BacktickString;
  • constructing an object with two fields
    • an insertions field with value v
    • a strings field consisting of a read-only array of strings containing the characters in BacktickString outside of interpolations, split at the interpolation points; the length of the this array is one more than the length of v.

The result of the raw-template-expr is the newly constructed object.

For each raw-template-expr there is a separate non-abstract object type, which is a subtype of the RawTemplate type. The objects constructed by a raw-template-expr belong to this non-abstract object type. The type of the insertions field is determined from the contextually expected type or the static type of the expressions in the BacktickString in the same way as with a list-constructor-expr. The strings and insertions fields will be read-only if required by the contextually expected type. The value of the strings field is constructed once by the non-abstract object type. The value of the insertions is constructed once for each evaluation of the raw-template-expr.

6.7 Structural constructors

Each basic type of structure has its own expression syntax for constructing a value of the type.

structural-constructor-expr := list-constructor-expr | table-constructor-expr | mapping-constructor-expr

An structural-constructor-expr occurring within a const-expr will construct a structural value that has its read-only bit on.

6.7.1 List constructor

list-constructor-expr := [ [ expr-list ] ]
expr-list := expression (, expression)*

A list-constructor-expr creates a new list value. The members of the list come from evaluating each expression in the expr-list in order.

If there is a contextually expected type, then the inherent type of the newly created list is derived from the applicable contextually expected type. If the applicable contextually expected type is a list type descriptor, then that used as the inherent type. If the applicable contextually expected type is a union type descriptor, then any members of the union that do not contain list shapes of length N will be ignored, where N is the number of expressions in the expr-list; it is a compile-time error if this does not leave a single list type descriptor, which is then used as the inherent type. The static type of the list-constructor-expr will be the same as the inherent type.

If there is no contextually expected type, then the inherent type will be a tuple-type-descriptor with a member-type-descriptor for each expression in the expr-list; the type of each member-type-descriptor will be the broad type of the corresponding expression in the expr-list.

If there is a contextually expected type, then the type that the inherent type requires for each list member provides the contextually expected type for the expression for the member; otherwise there is no contextually expected type for the expressions for members.

A member of a list can be filled in automatically if the FillMember abstract operation would succeed on it. The inherent type of a list establishes either a fixed length for the list or just a minimum length for the list, which may be zero. In either case, a list constructor may specify only the first k members, provided that for each i from k + 1 up to the fixed length of the list, the i-th member can be filled in automatically.

6.7.2 Mapping constructor

mapping-constructor-expr := { [field (, field)*] }
field := 
  specific-field
  | computed-name-field
  | spread-field
specific-field :=
   [readonly] (field-name | string-literal) : value-expr
   | [readonly] [variable-name]
value-expr := expression
computed-name-field := [ field-name-expr ] : value-expr
field-name-expr := expression
spread-field := ... expression

A mapping-constructor-expr creates a new mapping value.

A specific-field specifies a single field, where the field name is known at compile-time. A specific-field that consists of just a variable name x is equivalent to a field x: x.

The static type of the expression in a spread-field must allow only mapping values, i.e. must be a subtype of map<any|error>. All the fields of the mapping value that results from evaluating that expression are included in the mapping value being constructed. It is a compile-time error if the static type of the expression in a spread-field allows a field that duplicates a specific-field or that could also occur in another spread-field. Note that a spread-field with an inclusive record type of record { never x?; } cannot duplicate a specific field for x.

If there is a contextually expected type, then the inherent type of the newly created mapping is derived from the applicable contextually expected type. If the applicable contextually expected type is a mapping type descriptor, then that used as the inherent type. If the applicable contextually expected type is a union type descriptor, then any members of the union that are inconsistent with the field names specified in a specific-field in the mapping-constructor-expr will be ignored; it is a compile-time error if this does not leave a single mapping type descriptor, which is then used as the inherent type. The static type of the mapping-constructor-expr will be the same as the inherent type.

If there is no contextually expected type, then the inherent type will be an exclusive-record-type-descriptor with an individual-field-descriptor for each specific-field; the type of each field-descriptor will be the broad type of the value-expr in the field, unless the field is read-only in which case the type of the field-descriptor will be the precise type. The static type of the expression in every spread-field will also be added to the inherent type. If there are fields specified as a computed-name-field, then there will also be a record-rest-descriptor T..., where T is the union of the broad types of the value-expr in all such fields.

If a specific-field does not use a string-literal for the name of the field and the inherent type descriptor is a record type descriptor, then the record type descriptor must include an individual-type-descriptor for that field.

If the inherent type descriptor is a record type descriptor, a field will be added to the constructed value using the default value from the type descriptor for any field that is not specified explicitly in the mapping constructor and that has a default value.

If there is a contextually expected type, then the type that the inherent type requires for each field provides the contextually expected type for the value-expr in a field; otherwise there is no contextually expected type for the value-expr for fields. If there is a contextually expected type, the contextually expected type for the expression in a spread-field is map<T>, where the T is the smallest type such that the inherent type is a subtype of map<T>. The contextually expected type for a field-name-expr is string.

A computed-name-field specifies a single field, where the name of the field is specified by an expression enclosed in square brackets. A mapping-constructor-expr first constructs a mapping value without considering any computed-name-field. The effect of a computed-name-field is to modify the member of the mapping with the specified name after the mapping has been constructed. If the modification is incompatible with the inherent type, then the mapping-constructor-expr will panic. The modifications are performed in the order in which the computed-name-fields occur in the mapping-constructor-expr.

If the applicable contextually expected type is a subtype of readonly, then the mapping will be constructed with its read-only bit on. If the inherent type makes a specific field readonly, then that field will be constructed with its read-only bit on. A specific-field that starts with readonly will also be constructed with its read-only bit on.

6.7.3 Table constructor

table-constructor-expr := table [key-specifier] [ [row-list] ]
row-list := mapping-constructor-expr (, mapping-constructor-expr)*

A table-constructor-expr creates a new table value. The members of the table come from evaluating each mapping-constructor-expr in the row-list in order.

For example,

table key(email) [
   { email: "sanjiva@weerawarana.org", firstName: "Sanjiva", lastName: "Weerawarana" },
   { email: "jjc@jclark.com", firstName: "James", lastName: "Clark" }
]

The inherent type of the constructed table is a table type descriptor including a key-specifier table<T> key(ks), where T is the member type and ks is the key sequence. The inherent type is determined from the contextually expected type togther with the table-constructor-expr. The static type of the table-constructor-expr will be the same as this inherent type.

If there is a contextually expected type, then the member type of inherent type of the newly created table is derived from the applicable contextually expected type, which must be a table-type-descriptor. If there is no contextually expected type, then the member type of the inherent type is derived from the the static type of the expressions for the members: the member type will be the smallest record type that is a supertype of the static types of all the expressions in the row-list. It is an error if there is no contextually expected type and the row-list is empty.

The key sequence of the inherent type comes from the key-specifier of applicable contextually expected type or the key-specifier in the table-constructor-expr. If both of these are present, then they must be the same. If neither of them are present, then the key sequence is empty. The key sequence of the table value is the same as that of its inherent type. The key sequence and member type must meet the same consistency requirements as if they were specified together in a table type descriptor. For every field-name in the key sequence of the inherent type every mapping-constructor-expr in the row-list must include a specific-field that has a value-expr that is a const-expr. It is a compile-time error if two or more rows of the table have the same key value.

If there is a contextually expected type, then the type that the inherent type requires for each table member provides the contextually expected type for the expressions in the expr-list; otherwise there is no contextually expected type for these expressions.

6.8 New expression

new-expr := explicit-new-expr | implicit-new-expr
explicit-new-expr := new type-descriptor ( arg-list )

A new-expr constructs a new object or stream. The type-descriptor in an explicit-new-expr must specify an object type or a stream type.

When the type-descriptor is an object type, the explicit-new-expr allocates storage for the object and initializes it, passing the supplied arg-list to the object's init method. It is a compile error if the type-descriptor if the arg-list does not match the signature of the object type's init method. If the result of calling the init method is an error value e, then the result of evaluating the explicit-new-expr is e; otherwise the result is the newly initialized object.

When the type-descriptor is a stream type stream<T,E>, the arg-list must either be empty or be a single argument belonging to abstract object type StreamImplementor<T,E?>. When the arg-list is empty, the result will be an empty stream (i.e. a stream whose next method returns nil). When the arg-list evaluates to a StreamImplementor object, the result will be a stream that wraps that object.

An explicit-type-expr specifying a type descriptor T has static type T, except that if T is an object type and the type of the init method is E?, where E is a subtype of error, then it has static type T|E.

implicit-new-expr := new [( arg-list )]

An implicit-new-expr is equivalent to an explicit-new-expr that specifies the applicable contextually expected type as the type descriptor. An implicit-new-expr consisting of just new is equivalent to new().

6.9 Service constructor

service-constructor-expr := [annots] service service-body-block
service-body-block := { service-method-defn* }
service-method-defn :=
   metadata
   [resource]
   function identifier function-signature method-defn-body

A service-constructor-expr constructs a service value. The result of evaluating a service-constructor-expr is a value of type service. If a service-method-defn contains a resource qualifier, then it defines a resource method. The self variable can be used in a method-defn-body of a service-method-defn in the same way as for objects.

Each service value has a distinct type descriptor. (Evaluating a service constructor thus has an effect analogous to defining an anonymous object type and then creating a value of that type.)

The return type of a resource method must be a subtype of error? and must contain nil.

6.10 Variable reference expression

variable-reference-expr := variable-reference
variable-reference := identifier | qualified-identifier | xml-qualified-name
xml-qualified-name := xml-namespace-prefix : identifier

A variable-reference can refer to a variable, a parameter, a constant (defined with a module constant declaration), a function or a type (defined with a type definition).

When the variable reference has a prefix and the prefix has been declared using an xmlns-decl rather than an import-decl, then the result of evaluating the variable-reference-expr is a string of the form:

   {namespace-uri}local-name

where the namespace-uri comes from xml-namespace-uri specified in the xmlns-decl, and the local-name comes from the identifier following the colon.

If the variable-reference references a type defined with a type definition, then the result of evaluating the variable-reference-expr is a typedesc value for that type.

6.11 Field access expression

field-access-expr := expression . field-name

A field-access-expr accesses a field of an object or a member of a mapping. The semantics depends on the static type T of expression. A field-access-expr where T is a subtype of xml is interpreted as an xml-required-attribute-access-expr.

If T is a subtype of the object basic type, then T must have a field field-name and the static type of the field-access-expr is the type of that field. In this case, the field-access-expr is evaluated by first evaluating the expression to get a value obj; the result of the field-access-expr is the value of that field of obj. The rest of this subsection applies when T is not a subtype of the object basic type.

Let T' be the intersection of T and basic type mapping, let K be the singleton type containing just the string field-name, and let M be the member type for K in T'. The compile-time requirements on the field-access-expr depend on whether the type descriptor describing T is lax:

The static type of field-access-expr is M|E, where E is empty if K is a required key type and T' is a subtype of T, and error otherwise (E can only be error in the lax case.) In the lax case, if M is lax, then the static type of the field-access-expr is lax even if E is an error.

A field-access-expr is evaluated as follows:

  1. expression is evaluated resulting in a value v
  2. if v has basic type error, the result is v (this can only happen in the lax case)
  3. otherwise, if v does not have basic type mapping, the result is a new error value (this can only happen in the lax case)
  4. otherwise, if v does not have a member whose key is field-name, the result is a new error value (this can only happen in the lax case)
  5. otherwise, the result is the member of v whose key is field-name.

6.12 Optional field access expression

optional-field-access-expr := expression ?. field-name

An optional-field-access-expr accesses a possibly undefined mapping member, returning () if the member does not exist.

An optional-field-access-expr where the static type of expression is a subtype of xml is interpreted as an xml-optional-attribute-access-expr.

Let T be the static type of expression, let T' be the intersection of T and basic type mapping, let K be the singleton type containing just the string field-name and let M be the member type of K in T'. The compile-time requirements on the optional-field-access-expr depend on whether the type descriptor describing T is lax:

The static type of the optional-field-access-expr is M|N|E where

An optional-field-access-expr is evaluated as follows:

  1. expression is evaluated resulting in a value v
  2. if v is (), the result is ()
  3. otherwise, if v has basic type error, the result is v (this can only happen in the lax case)
  4. otherwise, if v does not have basic type mapping, the result is a new error value (this can only happen in the lax case)
  5. otherwise, if v does not have a member whose key is field-name, the result is ()
  6. otherwise, the result is the member of v whose key is field-name.

6.13 XML attribute access expression

xml-attribute-access-expr := xml-required-attribute-access-expr | xml-optional-attribute-access-expr
xml-required-attribute-access-expr := expression . xml-attribute-name
xml-optional-attribute-access-expr := expression ?. xml-attribute-name
xml-attribute-name := xml-qualified-name | qualified-identifier | identifier

An XML attribute access expression provides convenient access to an attribute of an XML element. It is a compile-time requirement that the static type of the expression is a subtype of xml.

A string representing the name of the attribute is computed at compile-time from the xml-attribute-name. When the xml-attribute-name is an identifier without a prefix, the attribute name string is the identifier. When the xml-attribute-name has a prefix, normally the xml-attribute-name is an xml-qualified-name, in which the prefix is an xml-namespace-prefix declared using an xmlns-decl. In this case, the xml-qualified-name is expanded at compile-time into an attribute name string of the form

   {namespace-uri}local-name

where the namespace-uri comes from xml-namespace-uri specified in the xmlns-decl, and the local-name comes from the identifier following the colon.

It is also allowed for the xml-attribute-name to be specified as a qualified-identifier, in which the prefix is a module-prefix declared using an import-decl. In this case the qualified-identifier must refer to a module-const-decl of type string, and the attribute name string is the value of the referenced constant. This allows e.g. xml:lang to work.

An xml-optional-attribute-access-expr is evaluated as follows. The expression is evaluated resulting in an xml value v. If v is an empty xml value, the result is (). Otherwise, if v is not a singleton element, the result is an error. Otherwise, let m be that element's attribute map and let k be the attribute name string computed at compile-time from the xml-attribute-name. If m has a member s with key k, the the result is s. Otherwise, the result is ().

An xml-required-attribute-access-expr is evaluated the same as an xml-optional-attribute-expr, except that for cases where the result of the xml-optional-attribute-expr would be (), the result of the xml-required-attribute-access-expr is an error.

The static type of an xml-required-attribute-access-expr is string|error The static type of an xml-optional-attribute-access-expr is string|error|().

6.14 Annotation access expression

annot-access-expr := expression .@ annot-tag-reference

The annot-tag-reference must refer to an annotation tag declared with an annotation declaration. The static type of expression must be a subtype of typedesc.

An annot-access-expr is evaluated by first evaluating expression resulting in a typedesc value t. If t has an annotation with the tag referenced by annot-tag-reference, then the result of the annot-access-expr is the value of that annotation; otherwise, the result is nil.

The static type of the annot-access-expr is T? where T is the type of the annotation tag.

6.15 Member access expression

member-access-expr := container-expression [ (key-expression | multi-key-expression) ]
container-expression := expression
key-expression := expression
multi-key-expression := expression (, expression)+

A member-access-expr accesses a member of a structured value using its key, or a constituent of a sequence value using its index.

The requirements on the static type of container-expression and key-expression are as follows:

A multi-key-expression is allowed only when the static type of a container-expression is a subtype of table. A multi-key-expression E1, E2,..., En is equivalent to a list-constructor-expr [E1, E2,..., En].

A member-access-expr is evaluated as follows:

  1. the container-expression is evaluated to get a value c;
  2. the key-expression is evaluated to get a value k;
  3. depending on the basic type of c
    • if it is string, and k is < 0 or ≥ the length of c, then the evaluation completes abruptly with a panic; otherwise, the result is a string of length 1 containing the character with index k in c;
    • if it is xml, and k is < 0, then the evaluation completes abruptly with a panic; if k is ≥ the length of c, then the result is an empty xml value; otherwise, the result is a singleton xml value containing the item with index k in c;
    • if it is list, and k is < 0 or ≥ the length of c, then the evaluation completes abruptly with a panic; otherwise, the result is the member of c with index k;
    • if it is table, then if c does not contain a member with key k, the result is (); otherwise, the result is the member of c with key k.
    • if it is mapping, then if c is () or c does not contain a member with key k, the result is (); otherwise, the result is the member of c with key k.

Let T the static type of container-expression. If T is a subtype of string, then the static type of the member-access-expr is string:Char, that is the subtype of strings containing strings of length 1. If T is a subtype of xml<M>, then the static type of the member-access-expr is M|E, where E is the type of the empty xml value. If T is a subtype of table<R>, then the static type of member-access-expr is R?. Otherwise, let K be the static type of key-expression and let M be the member type of K in T; if T contains nil, or T is a subtype of mapping and K is an optional key type for T, then the static type of the member-access-expr is M?, otherwise the static type is M.

6.16 Function call expression

function-call-expr := function-reference ( arg-list )
function-reference := variable-reference
arg-list :=
   positional-args [, other-args]
   | [other-args]
positional-args := positional-arg (, positional-arg)*
positional-arg := expression
other-args := named-args | rest-arg
named-args := named-arg (, named-arg)*
named-arg := arg-name = expression
arg-name := identifier
rest-arg := ... expression

A function-call-expr is evaluated by constructing an argument list and passing the argument list to the function referred to by the variable-name. If the function terminates normally, then the result of the function-call-expr is the return value of the function; otherwise the function-call-expr completes abruptly with a panic. The static type of the function-call-expr is the return type of the function type.

A function-call-expr where the function-reference refers to a type is interpreted as a functional-constructor-expr.

The variable-reference must refer to a variable with function type. The type descriptor of that function type is used to construct an argument list from the specified arg-list. Note that it is the type descriptor of the declared type of the variable that is used for this purpose, rather than the runtime type descriptor of the referenced function value.

The expressions occurring in the arg-list are evaluated in the order in which they occur in the arg-list. The result of evaluating each positional-arg is added to the argument list in order. The contextually expected type for the expression in the i-th positional-arg is the type of the i-th parameter.

If there is a rest-arg, then it is evaluated. The result must be a list value. Each member of the list value is added to the argument in the order that it occurs. The static type of the list value must be such as to guarantee that the resulting argument list will conform to the function's declared param-list. The rest-arg is not restricted to supplying the part of the argument list that will be bound to a rest-param, and its static type is not restricted to being an array type. If there is rest-arg, then no parameter defaults are added.

If there is no rest-arg, then each non-rest parameter that was not supplied by positional argument is added in order from a named argument, if there is one, and otherwise using the parameter default. An arg-list can only use a named argument to specify a parameter if the name of the parameter is visible at the point where the arg-list occurs. The contextually expected type for the expression specifying a named argument is the type declared for the corresponding parameter. A default parameter is computed by calling the closure in the type descriptor, passing it the previous arguments in the argument list. It is a compile-time error if there is a non-rest parameter for which there was no positional argument and no named argument and which is not defaultable. It is also an error if there is a named argument for a parameter that was supplied by a positional argument.

When a function to be called results from the evaluation of an expression that is not merely a variable reference, the function can be called by first storing the value of the expression in a variable.

6.17 Method call expression

method-call-expr := expression . method-name ( arg-list )

A method-call-expr either calls a method or calls a function in the lang library. The evaluation of the method-call-expr starts by evaluating expression resulting in some value v. There is no contextually expected type for expression.

If the static type of expression is a subtype of object, and the object type includes a method named method-name, then the method-call-expr is executed by calling that method on v. A method-call-expr can be used within a method of a service to call another method of that service other than a resource method; in this case, expression must be self. The arg-list is used to construct an argument list that is passed to the method in the same way as with a function-call-expr. A method-call-expr cannot be used to call a remote method; it can only be called by a remote-method-call-action. A method-call-expr cannot be used to call a resource method.

Otherwise, the method-call-expr will be turned into a call to a function in the lang library m:method-name(expression, arg-list), where m is an automatically created module prefix for a module lang.M of the lang library, where M is selected as follows.

It is a compile-time error if the resulting function call does not satisfy all the constraints that would apply if it has been written explicitly as a function-call-expr.

6.18 Functional constructor

functional-constructor-expr := functionally-constructible-type-reference ( arg-list )

A functional constructor uses functional syntax to construct a new value of a functionally constructible type. The functionally-constructible-type-reference identifies the type of value to be constructed.

An argument list consisting of positional and named argument values is constructed by evaluating the expressions in the arg-list. The construction parameter types determine the contextually expected type of each of the expressions. If there is no argument for a construction parameter that can be defaulted, then an argument for that parameter is added to the argument list with the default value.

When constructing an error value, the ImmutableClone abstract operation is applied to each named argument. The detail record is constructed as immutable. The stack trace describes the execution stack at the point where the functional constructor was evaluated. Any fields of the detail record that were not specified by a named argument can be defaulted using the defaults specified in the detail record type.

A functional-constructor-expr occurring within a const-expr will construct a value with its read-only bit on.

6.19 Anonymous function expression

anonymous-function-expr := explicit-anonymous-function-expr | infer-anonymous-function-expr
explicit-anonymous-function-expr := [annots] function function-signature (block-function-body|expr-function-body)

Evaluating an anonymous-function-expr creates a closure, whose basic type is function. With an explicit-anonymous-function-expr, the type of the function is specified explicitly as usual with a function-signature. With an infer-anonymous-function-expr, the type of the function is inferred.

If block-function-body refers to a block-scope variable defined outside of the block-function-body, the closure will capture a reference to that variable; the captured reference will refer to the same storage as the original reference not a copy.

expr-function-body := => expression

An expr-function-body is used for an expression-bodied function, that is a function whose body is specified by an expression. An expr-function-body of the form => E is short for a block-function-body { return E}.

infer-anonymous-function-expr := infer-param-list expr-function-body
infer-param-list :=
   identifier
   | ([identifier (, identifier)*])

An infer-anonymous-function-expr can only be used in a context where a function type is expected. The types of the parameters are inferred from the expected function type. The scope of the parameters is expr-function-body. The static type of the infer-anonymous-function-expr will be a function type whose return type is the static type of the expression in expr-function-body. If the contextually expected type for the anonymous-function-expr is a function type with return type T, then the contextually expected type for expression in expr-function-body is T.

6.20 Let expression

let-expr := let let-var-decl [, let-var-decl]* in expression
let-var-decl := [annots] typed-binding-pattern = expression

A let-expr binds variables and then evaluates an expression with those variables in scope.

A let-expr let T B = E1 in E2 is evaluated as follows. E1 is evaluated resulting in a value v. The typed binding pattern T B is matched to v, causing assignments to the variables occuring in B. Then E2 is evaluated with those variables in scope; the resulting value is the result of the let-expr.

A let-expr let D1, D2,...,Dn in E is transformed into let D1 in let D2 in ... let Dn in E.

The typed-binding-pattern is used unconditionally, meaning that it is a compile error if the static types do not guarantee the success of the match. If the typed-binding-pattern uses var, then the type of the variable is inferred from the precise static type of the expression following =.

Since expressions cannot modify variables, the variables bound in a let-var-decl are implicitly final.

6.21 Type cast expression

type-cast-expr := < type-cast-param > expression
type-cast-param := [annots] type-descriptor | annots

Normally, the parameter for a type-cast-expr includes a type-descriptor. However, it is also allowed for the parameter to consist only of annotations; in this case, the only effect of the type cast is for the contextually expected type for expression to be augmented with the specified annotations. The rest of this subsection describes the normal case, where the type-cast-expr includes a type-descriptor.

A type-cast-expr casts the value resulting from evaluating expression to the type described by the type-descriptor, performing an implicit conversion or numeric conversion if required.

A type-cast-expr is evaluated by first evaluating expression resulting in a value v. Let T be the type described by type-descriptor. If v belongs T, then the result of the type-cast-expr is v. Otherwise, if v belongs to xml:Text and T includes shapes from basic type string, then let s be the result of implicitly converting v to a string; if s belongs to T, then the result of the type-cast-expr is s. Otherwise, if T includes shapes from exactly one basic numeric type N and v is belongs to another basic numeric type, then let n be NumericConvert(N, v); if n is not an error and n belongs to T, then the result of the type-cast-expr is n. Otherwise, the evaluation of the type-cast-expr completes abruptly with a panic.

Let T be the static type described by type-descriptor, and let TE be the static type of expression. Then the static type of the type-cast-expr is the intersection of T and TE', where TE' is TE with its xml shapes and numeric shapes transformed to take account to the possibility of the implicit conversion and numeric conversion specified in the previous paragraph.

The contextually expected type for expression is the intersection of the contextually expected type of the type-cast-expr and the type described by the type-descriptor.

Note that a type-cast-expr of <readonly> can be used both to cause constructors withing the expression to construct values with the read-only bit on and to verify that the value resulting from the evaluation of expression has its read-only bit on.

6.22 Typeof expression

typeof-expr := typeof expression

The result of a typeof-expr is a typedesc value for the runtime type of v, where v is the result of evaluating expression.

The runtime type of v is the narrowest type to which v belongs.

The static type of typeof-expr is typedesc<T>, where T is the static type of expression.

6.23 Unary expression

unary-expr :=
   + expression
   | - expression
   | ~ expression
   | ! expression

The unary - operator performs negation. The static type of the operand must be a number; the static type of the result is the same basic type as the static type of the operand. The semantics for each basic type are as follows:

If the contextually expected type for a - expression is T, then the contextualy expected type for the operand expressions is T', where a value v is in T' if it belongs to int, decimal or float, and T contains a value with the same basic type as v.

The unary + operator returns the value of its operand expression. The static type of the operand must be a number, and the static type of the result is the same as the static type of the operand expression.

The ~ operator inverts the bits of its operand expression. The static type of the operand must be int, and the static type of the result is an int.

The ! operator performs logical negation. The static type of the operand expression must be boolean. The ! operator returns true if its operand is false and false if its operand is true.

6.24 Multiplicative expression

multiplicative-expr :=
   expression * expression
   | expression / expression
   | expression % expression

The * operator performs multiplication; the / operator performs division; the % operator performs remainder.

The static type of both operand expressions is required to be a subtype of the same basic type; this basic type will be the static type of the result. The following basic types are allowed:

If the contextually expected type for a multiplicative-expr is T, then the contextualy expected type for both operand expressions is T', where a value v is in T' if it belongs to int, decimal or float, and T contains a value with the same basic type as v.

6.25 Additive expression

additive-expr :=
   expression + expression
   | expression - expression

The + operator is used for both addition and concatenation; the - operator is used for subtraction.

It is required that either

The semantics for each basic type is as follows:

If the contextually expected type for a multiplicative-expr is T, then the contextualy expected type for both operand expressions is T', where a value v is in T' if it belongs to int, decimal, float, string or xml and T contains a value with the same basic type as v.

6.26 Shift expression

shift-expr :=
   expression << expression
   expression >> expression
   expression >>> expression

A shift-expr performs a bitwise shift. Both operand expressions must have static type that is a subtype of int. The left hand operand is the value to be shifted; the right hand value is the shift amount; all except the bottom 6 bits of the shift amount are masked out (as if by x & 0x3F). Then a bitwise shift is performed depending on the operator:

If the operator is >> or >>> and the left hand operand is a subtype of int:UnsignedK when K is 8, 16 or 32, then the static type of the result is int:UnsignedN where N is the smallest such K; otherwise, the static type of the result is int.

6.27 Range expression

range-expr :=
   expression ... expression
   | expression ..< expression

The result of a range-expr is a new object belonging to the abstract object type Iterable<int,()> that will iterate over a sequence of integers in increasing order, where the sequence includes all integers n such that

It is a compile error if the static type of either expression is not a subtype of int.

A range-expr is designed to be used in a foreach statement, but it can be used elsewhere.

6.28 Numerical comparison expression

numerical-comparison-expr :=
   expression < expression
   | expression > expression
   | expression <= expression
   | expression >= expression

A numerical-comparison-expr compares two numbers.

The static type of both operands must be of the same basic type, which must be int, float or decimal. The static type of the result is boolean.

Floating point comparisons follow IEEE, 754-2008, so

6.29 Type test expression

is-expr :=
   expression is type-descriptor

An is-expr tests where a value belongs to a type.

An is-expr is evaluated by evaluating the expression yielding a result v. If v belongs to the type denoted by type-descriptor, then the result of the is-expr is true; otherwise the result of the is-expr is false.

6.30 Equality expression

equality-expr :=
   expression == expression
   | expression != expression
   | expression === expression
   | expression !== expression

An equality-expr tests whether two values are equal. For all four operators, it is a compile time error if the intersection of the static types of the operands is empty.

The === operator tests for exact equality. The !== operator results in the negation of the result of the === operator. Two values v1 and v2 are exactly equal if they have the same basic type T and

The == operator tests for deep equality. The != operator results in the negation of the result of the == operator. For both == and !=, both operands must have a static type that is a subtype of anydata. Two values v1, v2 are deeply equal if DeepEquals(v1, v2) is true.

Note that === and == are the same for simple values except for floating point types.

For the float type, the operators differ as regards floating point zero: == treats positive and negative zero from the same basic type as equal whereas === treats them as unequal. Both == and === treat two NaN values from the same basic type as equal. This means that neither of these operators correspond to operations defined by IEEE 754-2008, because they do not treat NaN in the special way defined for those operations.

For the decimal type, the operators differ in whether they consider the precision of the value. For example, 1.0 == 1.00 is true but 1.0 === 1.00 is false.

6.31 Binary bitwise expression

binary-bitwise-expr :=
   bitwise-and-expr
   | bitwise-xor-expr
   | bitwise-or-expr
bitwise-and-expr := expression & expression
bitwise-xor-expr := expression ^ expression
bitwise-or-expr := expression | expression

A binary-bitwise-expr does a bitwise AND, XOR, or OR operation on its operands.

The static type of both operands must be a subtype of int. The static type of the result is as follows:

6.32 Logical expression

logical-expr := logical-and-expr | logical-or-expr
logical-and-expr := expression && expression
logical-or-expr := expression || expression

The static type of each expression in a logical-expr must be a subtype of boolean.

A logical-and-expr is evaluated as follows:

  1. the left-hand expression is evaluated, resulting in a value x;
  2. if x is false, then the result of the logical-and-expr is x, and the right-hand expression is not evaluated;
  3. otherwise, the result of the logical-and-expr is the result of evaluating the right-hand expression.

A logical-or-expr is evaluated as follows:

  1. the left-hand expression is evaluated, resulting in a value x;
  2. if x is true, then the result of the logical-or-expr is x, and the right-hand expression is not evaluated;
  3. otherwise, the result of the logical-or-expr is the result of evaluating the right-hand expression.

6.33 Conditional expression

conditional-expr :=
   expression ? expression : expression
   | expression ?: expression

L ?: R is evaluated as follows:

  1. Evaluate L to get a value x
  2. If x is not nil, then return x.
  3. Otherwise, return the result of evaluating R.

6.34 Checking expression

checking-expr := checking-keyword expression
checking-keyword := check | checkpanic

Evaluates expression resulting in value v. If v has basic type error, then

If the static type of expression e is T|E, where E is a subtype of error, then the static type of check e is T.

6.35 Trap expression

The trap expression stops a panic and gives access to the error value associated with the panic.

trap-expr := trap expression

Semantics are:

6.36 Query expression

A query expression provides a language-integrated query feature using SQL-like syntax. In this version of Ballerina, the functionality is similar to a list comprehensions; future versions will provided richer functionality.

query-expr := [query-construct-type] query-pipeline select-clause [on-conflict-clause]
query-construct-type :=
  table key-specifier
  | stream
query-pipeline := from-clause intermediate-clause*
intermediate-clause :=
   from-clause
   | where-clause
   | let-clause
   | join-clause
   | limit-clause

A query expression consists of a sequence of clauses. The semantics of clauses is specified in terms of transforming a sequence of frames, where a frame is a binding of variables to values. The input to each clause is a sequence of frames. As each clause is executed, it iterates over its input frames and emits output: the final clause, which is a select clause, emits output values; the other clauses emit output frames. When a query-expr is evaluated, its clauses are executed in a pipeline by making the sequence of frames emitted by one clause be the input to the next clause. Each clause in the pipeline is executed lazily, pulling input from its preceding clause. The input to the first clause is a single empty frame.

The execution of a clause may complete early with an error value, in which case this error value is the result of the query, except when constructing a stream. Otherwise, the result of evaluating a query expression is a single value, which is constructed from the sequence of values emitted by the last clause. The result must be one of the following basic types:

A query-expr that constructs a stream must start with the stream keyword and is evaluated differently from a query-expr that constructs a value of other basic types. The clauses in the query-expr are executed lazily: clauses in the query-expr becomes closures, which are called as a result of next operations being performed on the stream. If a next operation causes the execution of a clause that completes early with an error value, then the error value is returned by the next operation; the evaluation of the query-expr will already have resulted in a stream value. If the next operation results in the evaluation of an expression within the query-expr completing abruptly with a check-fail, the associated error value will be returned as the result of the next operation. If the next operation results in the evaluation of an expression within the query-expr completely abruptly with a panic, then the next operation will complete abruptly with a panic.

If the query-expr starts with table, then the query-expr will construct a table; the key-specifier specifies the key sequence of the constructed table in the same way as with a table-constructor-expr. Otherwise, the applicable contextually expected type determines which basic type is constructed. If there is no contextually expected type, then it is determined from the static type of the expression in the initial from clause; it is a compile-time error if this static type is not a subtype of one of the basic types that a query expression can construct.

When during the construction of a table, an emitted value is added as a new member, it replaces any existing member with the same key value; when a new member replaces an existing member, it will have the same position in the order of members as the existing member. This behavior may be controlled by an on conflict clause.

Variables bound by the clauses of a query-pipeline are implicitly final, and cannot be modified.

6.36.1 From clause

from-clause := from typed-binding-pattern in expression

A from clause is executed as follows:

  • for each input frame f
    • evaluate the in expression with f in scope resulting in an iterable value c;
    • create an Iterator object i from c
    • do the following in a loop L
      • call i.next() resulting in a value r;
      • if r is an error, then complete execution of the from-clause early with error r;
      • if r is (), stop loop L;
      • let v be r.value;
      • emit a frame consisting of f augmented with the variables resulting from binding typed-binding-pattern to v.

6.36.2 Where clause

where-clause := where expression

A where clause is executed as follows:

  • for each input frame f
    • execute the expression with f in scope resulting in value b;
    • if b is true, emit f.

6.36.3 Let clause

let-clause := let let-var-decl [, let-var-decl]* 

A let clause consisting of a single let-var-decl is executed as follows:

  • for each input frame f
    • evaluate the expression with f in scope resulting in a value v;
    • emit a frame consisting of f augmented with the result of binding type-binding-pattern to v.

A let clause with more than one let-var-decl is transformed into multiple let clauses: let x1 = E1, x2 = E2 is transformed into let x1 = E1 let x2 = E2.

6.36.4 Join clause

join-clause := join typed-binding-pattern in expression join-on-condition
join-on-condition := on expression equals expression

A join clause performs an inner equijoin.

A join clause is executed as follows:

  • compute a mapping for the right side of the join; during this computation variable bindings from input frames are not in scope:
    • create an empty mapping m that maps from keys to lists of frames (using DeepEquals to compare keys);
    • evaluate the expression following in resulting in an iterable value c;
    • create an Iterator object i from c;
    • do the following in a loop L
      • call i.next() resulting in a value r;
      • if r is an error, then complete execution of the join-clause early with error r;
      • if r is (), stop loop L;
      • let v be r.value;
      • let f be a frame with the result of binding the typed-binding-pattern to v;
      • evaluate the expression to right of equals with f in scope resulting in a value k;
      • add an entry to m with key k and and frame f;
  • for each input frame f
    • evaluate the expression to the left of equals with frame f in scope resulting in a value k;
    • let fs be the list of frames for key k in m;
    • for each frame f' in fs
      • emit a frame consisting of f augmented with f'.

In the above, if c evaluates to a table, and the result of evaluating the expression to the right of equals for a member of the table will always be the key value for that member of the table, then the above can be optimized by using the table instead of creating a new mapping.

Variables bound by previous clauses are not in scope for the expression following in, nor for the expression on the right of equals. Variables bound by the typed-binding-pattern are not in scope for the expression following in, nor for the expression on the left of equals.

6.36.5 Limit clause

limit-clause := limit expression

A limit clause limits the number of frames emitted by a query pipeline.

A limit clause is executed as follows:

  • evaluate expression resulting in a value n; variable bindings from input frames are not in scope for this evaluation;
  • if n is less than zero, then panic;
  • for each input frame f, while n is greater than 0
    • decrement n;
    • emit f.

The static type of expression must be a subtype of int.

6.36.6 Select clause

select-clause := select expression

A select clause is executed as follows:

  • for each input frame f
    • evaluate the expression with f in scope resulting in value v
    • emit v

6.36.7 On conflict clause

on-conflict-clause := on conflict expression

An on conflict clause is allowed only for a query expression that constructs a table with a key sequence. The expression is evaluated when the select clause emits a value that conflicts with a previous value, in the sense that both values have the same key value in the table. The expression is evaluated with the same frame in scope as the select clause that emitted the value that conflicts with the previous value. The static type of the expression must be a subtype of error?. If the result of evaluating the expression is an error e, then the result of evaluating the query-expr is e. Otherwise, the result must be nil, and the earlier new value replaces the earlier conflicting value, in the same way as if there not been an on conflict clause.

Note that the expression may have side-effects; for example, it may call a function that logs an error.

6.37 XML navigation expression

XML navigation expressions allow for convenient navigation of XML element structure, in a similar way to XPath.

xml-navigate-expr := xml-filter-expr | xml-step-expr

6.37.1 XML name pattern

xml-name-pattern := xml-atomic-name-pattern [| xml-atomic-name-pattern]*

xml-atomic-name-pattern :=
  *
  | identifier
  | xml-namespace-prefix : identifier
  | xml-namespace-prefix : *

An XML name pattern matches a string specifying the name of an XML element.

An xml-atomic-name-pattern that is * matches any name.

An xml-namespace-prefix in an xml-atomic-name-pattern must be declared by an xmlns-decl. If there is an in-scope default namespace (declared by an xmlns-decl), an xml-atomic-pattern that is just an identifier specifies a name in that default namespace.

6.37.2 XML filter expression

xml-filter-expr := expression .< xml-name-pattern >

An xml-filter-expr selects constituents of a sequence that are elements with a name matching a specified name pattern. The static type of the expression must be a subtype of xml. The static type of the xml-filter-expr is xml<xml:Element>.

6.37.3 XML step expression

An xml-step-expr provides access to the children or descendants of an element, similar to a location path in XPath.

xml-step-expr := expression xml-step-start xml-step-extend*

xml-step-start :=
   xml-all-children-step
   | xml-element-children-step
   | xml-element-descendants-step
xml-all-children-step := /*
xml-element-children-step := /< xml-name-pattern >
xml-element-descendants-step := /**/< xml-name-pattern >

xml-step-extend :=
   .< xml-name-pattern >
   | [ expression ]
   | . method-name ( arg-list )

The static type of the expression must be a subtype of xml.

An xml-step-expr that starts with an xml-all-children-step

   E /* X

is equivalent to

   xml:map(xml:elements(E), v => xml:getChildren(v) X)

where v is a variable name not used in X.

An xml-step-expr that starts with an xml-element-children-step

   E /< NP > X

is equivalent to

   xml:map(xml:elements(E), v => xml:getChildren(v) .<NP> X)

where v is a variable name not used in X.

An xml-step-expr that starts with an xml-element-descendants-step

   E /**/< NP > X

is equivalent to

   xml:map(xml:elements(E), v => xml:getDescendants(v) .<NP> X)

where v is a variable name not used in X.

7. Actions and statements

7.1 Actions

action :=
   start-action
   | wait-action
   | send-action
   | receive-action 
   | flush-action
   | remote-method-call-action
   | query-action
   | type-cast-action
   | checking-action
   | trap-action
   | ( action )
action-or-expr := action | expression
type-cast-action := < type-cast-param > action
checking-action := checking-keyword action
trap-action := trap action

Actions are an intermediate syntactic category between expressions and statements. Actions are similar to expressions, in that they yield a value. However, an action cannot be nested inside an expression; it can only occur as part of a statement or nested inside other actions. This is because actions are shown in the sequence diagram in the graphical syntax.

The syntax for actions is defined by the above grammar and precedence rules. The precedence rules are that a remote-method-call-action has higher precedence than a checking-action or a trap-action.

An action is evaluated in the same way as an expression. Static typing for actions is the same as for expressions.

A type-cast-action, checking-action or trap-action is evaluated in the same way as a type-cast-expr, checking-expr or trap-expr respectively.

7.2 Threads and strands

Ballerina's concurrency model supports both threads and coroutines. A Ballerina program is executed on one or more threads. A thread may run on a separate core simultaneously with other threads, or may be pre-emptively multitasked with other threads onto a single core.

Each thread is divided into one or more strands. No two strands belonging to the same thread can run simultaneously. Instead, all the strands belonging to a particular thread are cooperatively multitasked. Strands within the same thread thus behave as coroutines relative to each other. A strand enables cooperative multitasking by yielding. When a strand yields, the runtime scheduler may suspend execution of the strand, and switch its thread to executing another strand. The following actions cause a strand to yield:

In addition, any function with an external-function-body can potentially yield; it should only do so if it performs a system call that would block. or calls a Ballerina function that itself yields. Functions in the lang library do not themselves yield, although if they call a function passed as an argument, that function may result in yielding.

There are two language constructs whose execution causes the creation of new strands: named-worker-decl and start-action. These constructs may use annotations to indicate that the newly created strand should be in a separate thread from the current strand. In the absence of such annotations, the new strand must be part of the same thread as the current strand.

7.3 Function and worker execution

block-function-body :=
   { [default-worker-init named-worker-decl+] default-worker }
default-worker-init := sequence-stmt
default-worker := sequence-stmt
named-worker-decl :=
   [annots] worker worker-name return-type-descriptor { sequence-stmt }
worker-name := identifier

A worker represents a single strand of a function invocation. A statement is always executed in the context of a current worker. A worker is in one of three states: active, inactive or terminated. When a worker is in the terminated state, it has a termination value. A worker terminates either normally or abnormally. An abnormal termination results from a panic, and in this case the termination value is always an error. If the termination value of a normal termination is an error, then the worker is said to have terminated with failure; otherwise the worker is said to have terminated with success. Note that an error termination value resulting from a normal termination is distinguished from an error termination value resulting from an abnormal termination.

A function always has a single default worker, which is unnamed. The strand for the default worker is the same as the strand of the worker on which function was called. When a function is called, the current worker becomes inactive, and a default worker for the called function is started. When the default worker terminates, the function returns to its caller, and the caller's worker is reactivated. Thus only one worker in each strand is active at any given time. If the default worker terminates normally, then its termination value provides the return value of the function. If the default worker terminates abnormally, then the evaluation of the function call expression completes abruptly with a panic and the default worker's termination value provides the associated value for the abrupt completion of the function call. The function's return type is the same as the return type of the function's default worker.

A function also has zero or more named workers. Each named worker runs on its own new strand. The termination of a function is independent of the termination of its named workers. The termination of a named worker does not automatically result in the termination of its function. When a default worker terminates causing the function to terminate, the function does not automatically wait for the termination of its named workers.

A named worker has a return type. If the worker terminates normally, the termination value will belong to the return type. If the return type of a worker is not specified, it defaults to nil as for functions. A return-type-descriptor in a named-worker-decl is an inferable context for a type descriptor, which means that * can be used to infer parts of the type descriptor; in particular, it is convenient to use error<*> to specify the error type.

When a function has named workers, the default worker executes in three stages, as follows:

  1. The statements in default-worker-init are executed.
  2. All the named workers are started. Each named worker executes its sequence-stmt on its strand.
  3. The statements in default-worker are executed. This happens without waiting for the termination of the named workers started in stage 2.

Variables declared in default-worker-init are in scope within named workers, whereas variables declared in default-worker are not.

The execution of a worker's sequence-stmt may result in the execution of a statement that causes the worker to terminate. For example, a return statement causes the worker to terminate. If this does not happen, then the worker terminates as soon as it has finished executing its sequence-stmt. In this case, the worker terminates normally, and the termination value is nil. In other words, falling off the end of a worker is equivalent to return;, which is in turn equivalent to return ();.

The parameters declared for a function are in scope in the block-function-body. They are implicitly final: they can be read but not modified. They are in scope for named workers as well as for the default worker.

The scope of a worker-name in a named-worker-decl that is part of a block-function-body is the entire block-function-body with the exception of the default-worker-init. When a worker-name is in scope, it can be used in a variable-reference-expr. The result of evaluating such a variable reference is a future value that refers to the value to be returned by that named worker. The static type of the result is future<T>, where T is the return type of the worker.

A strand can initiate a wait on another strand by using a wait-action with a value of type future. Only one wait on a strand can succeed; this wait receives the value returned by the strand. Any other waits on that strand fail. It is a compile-time error if for any named worker it is possible for the name of that worker to be evaluated as a variable-reference more than once for any execution of that worker. This ensures that wait operations that use just a worker-name to identify the strand to wait on cannot fail at runtime.

In the above, function includes method, and function call includes method call.

7.4 Statement execution

statement := 
   action-stmt
   | block-stmt
   | local-var-decl-stmt
   | local-type-defn-stmt
   | xmlns-decl-stmt
   | assignment-stmt
   | compound-assignment-stmt
   | destructuring-assignment-stmt
   | call-stmt
   | if-else-stmt
   | match-stmt
   | foreach-stmt
   | while-stmt
   | break-stmt
   | continue-stmt
   | fork-stmt
   | panic-stmt
   | return-stmt
   | lock-stmt

The execution of any statement may involve the evaluation of actions and expressions, and the execution of substatements. The following sections describes how each kind of statement is evaluated, assuming that the evaluation of those expressions and actions completes normally, and assuming that the execution of any substatements does not cause termination of the current worker. Except where explicitly stated to the contrary, statements handle abrupt completion of the evaluation of expressions and actions as follows. If in the course of executing a statement, the evaluation of some expression or action completes abruptly with associated value e, then the current worker is terminated with termination value e; if the abrupt termination is a check-fail, then the termination is normal, otherwise the termination is abnormal. If the execution of a substatement causes termination of the current worker, then the execution of the statement terminates at that point.

sequence-stmt := statement*
block-stmt := { sequence-stmt }

A sequence-stmt executes its statements sequentially. A block-stmt is executed by executing its sequence-stmt.

7.5 Fork statement

fork-stmt := fork { named-worker-decl+ }

The fork statement starts one or more named workers, which run in parallel with each other, each in its own new strand.

Variables and parameters in scope for the fork-stmt remain in scope within the workers (similar to the situation with parameters and workers in a function body).

The scope of the worker-name in a named-worker-decl that is part of a fork-stmt consists of both other workers in the same fork-stmt and the block containing the fork-stmt starting from the point immediately after the fork-stmt. Within its scope, the worker-name can be used in a variable-reference-expr in the same way as the worker-name of a named-worker-decl that is part of a block-function-body.

7.6 Start action

start-action := [annots] start (function-call-expr|method-call-expr|remote-method-call-action)

The keyword start causes the following function or method invocation to be executed on a new strand. The arguments for the function or method call are evaluation on the current strand. A start-action returns a value of basic type future immediately. If the static type of the call expression or action C is T, then the static type of start C is future<T>.

7.7 Wait action

A wait-action waits for one or more strands to terminate, and gives access to their termination values.

wait-action :=
   single-wait-action
   | multiple-wait-action
   | alternate-wait-action

wait-future-expr := expression but not mapping-constructor-expr

A wait-future-expr is used by a wait-action to refer to the worker to be waited for. Its static type must be future<T> for some T. As elsewhere, a wait-future-expr can use an in-scope worker-name in a variable-reference-expr to refer to named worker.

Evaluation of a wait-action performs a wait operation on the future value that results from evaluating a wait-future-expr. This wait operation may complete normally or abruptly. The wait operation initiates a wait for the strand that the future refers to. If the wait fails, then the wait operation completes normally and the result is an error. If the wait succeeds, and the strand completed normally, then the wait operation completes normally, and the result is the termination value of the strand. If the wait succeeds, but the strand completed abnormally, then the wait operation completes abruptly with a panic and the associated value is the termination value of the strand.

In addition to a static type, a wait-future-expr has a compile-time eventual type. If a wait-future-expr is a variable-reference-expr referring to the worker-name of a named worker with return type T, then the eventual type of the wait-future-expr is T. Otherwise, the eventual of a wait-future-expr with static type future<T> is T|error. The result of a wait operation that completes normally will belong to the eventual type of the wait-future-expr, since the compiler ensures that a wait for a wait-future-expr that is a variable reference to a named worker cannot fail.

Note that it is only possible to wait for a named worker, which will start its own strand. It is not possible to wait for a default worker, which runs on an existing strand.

A mapping-constructor-expr is not recognized as a wait-future-expr (it would not type-check in any case).

7.7.1 Single wait action

single-wait-action := wait wait-future-expr

A single-wait-action waits for a single future.

A single-wait-action is evaluated by first evaluating wait-future-expr resulting in a value f of basic type future; the single-wait-action then performs a wait operation on f.

The static type of the single-wait-action is the eventual type of the wait-future-expr.

7.7.2 Multiple wait action

multiple-wait-action := wait { wait-field (, wait-field)* }
wait-field :=
  variable-name
  | field-name : wait-future-expr

A multiple-wait-action waits for multiple futures, returning the result as a record.

A wait-field that is a variable-name v is equivalent to a wait-field v: v, where v must be the name of an in-scope named worker.

A multiple-wait-action is evaluated by evaluating each wait-future-expr resulting in a value of type future for each wait-field. The multiple-wait-action then performs a wait operation on all of these futures. If all the wait operations complete normally, then it constructs a record with a field for each wait-field, whose name is the field-name and whose value is the result of the wait operation. If any of the wait operations complete abruptly, then the multiple-wait-action completes abruptly.

The static type of the multiple-wait-action is a record where the static type of each field is the eventual type of the corresponding wait-future-expr.

7.7.3 Alternate wait action

alternate-wait-action := wait wait-future-expr (| wait-future-expr)+

An alternate-wait-action waits for one of multiple futures to terminate.

An alternate-wait-action is evaluated by first evaluating each wait-future-expr, resulting in a set of future values. The alternate-wait-action then performs a wait operation on all of these members of this set. As soon as one of the wait operations completes normally with a non-error value v, the alternate-wait-action completes normally with result v. If all of the wait operations complete normally with an error, then it completes normally with result e, where e is the result of the last wait operation to complete. If any of the wait operations completely abruptly before the alternate-wait-action completes, then the alternate-wait-action completes abruptly.

The static type of the alternate-wait-action is the union of the eventual type of all of its wait-future-exprs.

7.8 Worker message passing

Messages can be sent between workers.

Sends and receives are matched up at compile-time. This allows the connection between the send and the receive to be shown in the sequence diagram. It is also guarantees that any sent message will be received, provided that neither the sending nor the receiving worker terminate abnormally or with an error.

Messages can only be sent between workers that are peers of each other. The default worker and the named workers in a function are peers of each other. The workers created in a fork-stmt are also peers of each other. The workers created in a fork-stmt are not peers of the default worker and named workers created by a function.

peer-worker := worker-name | default

A worker-name refers to a worker named in a named-worker-decl, which must be in scope; default refers to the default worker. The referenced worker must be a peer worker.

Each worker maintains a separate logical queue for each peer worker to which it sends messages; a sending worker sends a message by adding it to the queue; a receiving worker receives a message by removing it from the sending worker's queue for that worker; messages are removed in the order in which they were added to the queue.

7.8.1 Send action

send-action := sync-send-action | async-send-action
sync-send-action := expression ->> peer-worker
async-send-action := expression -> peer-worker ;

A send-action sends a message to the worker that is identified by peer-worker. A send-action starts by evaluating the expression, resulting in a value v; the Clone abstract operation to v resulting in value c. This value c is added to the message queue maintained by the sending worker for messages to be sent to the sending worker;this queue continues to exist even if the receiving worker has already terminated.

For each send-action S, the compiler determines a unique corresponding receive-action R, such that a message sent by S will be received by R, unless R's worker has terminated abnormally or with failure. It is a compile-time error if this cannot be done. The compiler determines a failure type for the corresponding receive-action. If the receive-action was not executed and its worker terminated normally, then the termination value of the worker will belong to the failure type. The failure type will be a (possibly empty) subtype of error.

The difference between async-send-action and sync-send-action is in what happens after the message is added to the queue. The evaluation of async-send-action completes immediately after this, and the result is always (). A subsequent flush action can be used to check whether the message was received. With sync-send-action, evaluation waits until the receiving worker either executes a receive action that receives the queued message or terminates. The evaluation of sync-send-action completes as follows:

  • if the queued message was received, then normally with result nil;
  • otherwise
    • if the receiving worker terminated with failure, then normally with the result being the the termination value of the receiving worker, which will be an error;
    • if the receiving worker terminated abnormally, then abruptly with a panic, with the associated value being the termination value of the receiving worker.

The static type of the sync-send-action is F|() where F is the failure type of the corresponding receive action. If F is empty, then this static type will be equivalent to ().

The static type of the expression must be a subtype of value:Cloneable. The contextually expected type used to interpret expression is the contextually expected type from the corresponding receive-action.

If the receive-action corresponding to an async-send-action has a non-empty failure type, then it is a compile-time error unless it can be determined that a sync-send-action or a flush-action will be executed before the sending worker terminates with success.

If a worker W is about to terminate normally and there are messages still to be sent in a queue (which must be the result of executing an async-send-action), then the worker waits until the messages have been received or some receiving worker terminates. If a receiving worker R terminates without the message being received, R must have terminated abnormally, because the rule in the preceding paragraph. In this case, W terminates abnormally and W will use R's termination value as its termination value.

7.8.2 Receive action

receive-action := single-receive-action | multiple-receive-action
7.8.2.1 Single receive action
single-receive-action := <- peer-worker

A single-receive-action receives a message from a single worker.

For each single-receive-action R receiving from worker W, the compiler determines a corresponding send set. The corresponding send set S is a set of send actions in W, such that

  • in any execution of W that terminates successfully, exactly one member of S is executed and is executed once only
  • if R is evaluated, it will receive the single message sent by a member of S, unless W has terminated abnormally or with failure.

The compiler terminates a failure type for the corresponding send set. If no member of the corresponding send set was evaluated and the sending worker terminated normally, then the termination value of the sending worker will belong to the failure type. The failure type will be a (possibly empty) subtype of error.

A single-receive-action is evaluated by waiting until there is a message available in the queue or the sending worker terminates. The evaluation of single-receive-action completes as follows:

  • if a message becomes available in the queue, then the first available message is removed and the evaluation completes normally with the result being that message;
  • otherwise
    • if the sending worker terminated with failure, then normally with the result being the the termination value of the sending worker, which will be an error;
    • if the sending worker terminated abnormally, then abruptly with a panic, with the associated value being the termination value of the sending worker.

The static type of the single-receive-action is T|F where T is the union of the static type of the expressions in the corresponding send set and F is the failure type of the corresponding send set.

7.8.2.2 Multiple receive action
multiple-receive-action :=
   <-  { receive-field (, receive-field)* }
receive-field :=
   peer-worker
   | field-name : peer-worker

A multiple-receive-action receives a message from multiple workers.

A peer-worker can occur at most once in a multiple-receive-action.

A receive-field consisting of a peer-worker W is equivalent to a field W:W.

The compiler determines a corresponding send set for each receive field, in the same way as for a single-receive-action. A multiple-receive-action is evaluated by waiting until there is a message available in the queue for every peer-worker. If any of the peer workers W terminate before a message becomes available, then the evaluation of the multiple-receive-action completes as follows

  • if the sending worker terminated with failure, then normally with the result being the the termination value of the sending worker, which will be an error;
  • if the sending worker terminated abnormally, then abruptly with a panic, with the associated value being the termination value of the sending worker.

Otherwise, the result of the evaluation of multiple-receive-action completes by removing the first message from each queue and constructing a record with one field for each receive-field, where the value of the record is the message received.

The contextually expected typed for the multiple-receive-action determines a contextually expected type for each receive-field, in the same way as for a mapping constructor. The contextually expected type for each receive-field provides the contextually expected type for the expression in each member of the corresponding send set.

The static type of multiple-receive-action is R|F where

  • R is a record type, where R is determined in the same way as for a mapping constructor, where the static type of each field comes from the union of the static types of the expressions in each member of the corresponding send set and the contextually expected type is the contextually expected type of the multiple-receive-action
  • F is the union of the failure types for the corresponding send set for each receive-field

7.8.3 Flush action

flush-action := flush [peer-worker]

If peer-worker is specified, then flush waits until the queue of messages to be received by peer-worker is empty or until the peer-worker terminates.

Send-receive correspondence for async-send-action implies that the queue will eventually become empty, unless the peer-worker terminates abnormally or with failure. The evaluation of flush-action completes as follows:

  • if the queue of messages is empty, then normally with result nil;
  • otherwise
    • if the peer-worker terminated with failure, then normally with the result being the the termination value of the peer-worker, which will be an error;
    • if the peer-worker terminated abnormally, then abruptly with a panic, with the associated value being the termination value of the peer-worker.

If the flush-action has a preceding async-send-action without any intervening sync-send-action or other flush-action, then the static type of the flush-action is F|(), where F is the failure type of the receive-action corresponding to that async-send-action. Otherwise, the static type of the flush-action is nil.

If peer-worker is omitted, then the flush-action flushes the queues to all other workers. In this case, the static type will be the union of the static type of flush on each worker separately.

7.8.4 Send-receive correspondence

This section provides further details about how compile-time correspondence is established between sends and receive. This is based on the concept of the index of a message in its queue: a message has index n in its queue if it is the nth message added to the queue during the current execution of the worker.

  • A send action/statement has index i in its queue if the message that it adds to the queue is always the i-th message added to the queue during the execution of its worker. It is a compile time error if a send statement or action does not have an index in its queue.
  • A receive action has index i in a queue if any message that it removes from the queue is always the i-th message removed from the queue during the execution of its worker. It is a compile time error if a receive action does not have an index in each of its queues.
  • A send action/statement and a receive action correspond if they have the same index in a queue.
  • It is a compile time error if two or more receive actions have the same index in a queue.
  • A send action/statement is in the same send set as another send action/statement if they have the same index in a queue. It is allowed for a send set to have more than one member.
  • The maximum index that a receive action has in a queue must be the same as the maximum index that a send action or statement has in that queue.
  • It is a compile time error if it is possible for a worker to terminate with success before it has executed all its receive actions.
  • It is a compile time error if it is possible for a worker to terminate with success before it has executed one member from every send set.

7.9 Remote interaction

A client object is a stub that allows a worker to send network messages to a remote process according to some protocol. A local variable declared with client object type will be depicted as a lifeline in the function's sequence diagram. The remote methods of the client object correspond to distinct network messages defined by the protocol for the role played by the client object. The return value of a remote method represents the protocol's response. A remote-method-call-action is depicted as a horizontal arrow from the worker lifeline to the client object lifeline.

remote-method-call-action := expression -> method-name ( arg-list )

Calls a remote method. This works the same as a method call expression, except that it is used only for a method with the remote modifier.

7.10 Query action

query-action := query-pipeline do-clause
do-clause := do block-stmt

The clauses in the query-pipeline of query-action are executed in the same way as the clauses in the query-pipeline of a query-expr.

The query-action is executed as follows. For each input frame f emitted by the query-pipeline, execute the block-stmt with f in scope. If a clause in the query-pipeline completes early with error e, the result of the query-action is e. Otherwise, the result of the query-action is nil.

7.11 Local variable declaration statements

local-var-decl-stmt :=
   local-init-var-decl-stmt
   | local-no-init-var-decl-stmt
local-init-var-decl-stmt :=
   [annots] [final] typed-binding-pattern = action-or-expr ;

A local-var-decl-stmt is used to declare variables with a scope that is local to the block in which they occur.

The scope of variables declared in a local-var-decl-stmt starts immediately after the statement and continues to the end of the block statement in which it occurs.

A local-init-var-decl-stmt is executed by evaluating the action-or-expr resulting in a value, and then matching the typed-binding-pattern to the value, causing assignments to the variables occurring in the typed-binding-pattern. The typed-binding-pattern is used unconditionally, meaning that it is a compile error if the static types do not guarantee the success of the match. If the typed-binding-pattern uses var, then the type of the variable is inferred from the static type of the action-or-expr; if the local-init-var-decl-stmt includes final, the precise type is used, and otherwise the broad type is used. If the typed-binding-pattern specifies a type-descriptor, then that type-descriptor provides the contextually expected type for action-or-expr.

If final is specified, then the variables declared must not be assigned to outside the local-init-var-decl-stmt.

local-no-init-var-decl-stmt :=
   [annots] [final] type-descriptor variable-name ;

A local variable declared local-no-init-var-decl-stmt must be definitely assigned at each point that the variable is referenced. This means that the compiler must be able to verify that the local variable will have been assigned before that point. If final is specified, then the variable must not be assigned more than once.

7.12 Implicit variable type narrowing

Usually the type of a reference to a variable is determined by the variable's declaration, either explicitly specified by a type descriptor or inferred from the static type of the initializer.

In addition, this section defines cases where a variable is used in certain kinds of boolean expression in a conditional context, and it can be proved at compile time that the value stored in local variable or parameter will, within a particular region of code, always belong to a type that is narrower that the static type of the variable. In these cases, references to the variable within particular regions of code will have a static type that is narrower that the variable type.

Given an expression E with static type boolean, and a variable x with static type Tx, we define how to determine

based on the syntactic form of E as follows.

Narrowed types apply to regions of code as follows:

7.13 Local type definition statement

local-type-defn-stmt :=
   [annots] type identifier type-descriptor ;

A local-type-defn-stmt binds the identifier to a type descriptor within the scope of the current block. The type-descriptor is resolved when the statement is executed.

7.14 XML namespace declaration statement

xmlns-decl-stmt := xmlns-decl
xmlns-decl := xmlns xml-namespace-uri [ as xml-namespace-prefix ] ;
xml-namespace-uri := simple-const-expr
xml-namespace-prefix := identifier

An xmlns-decl is used to declare a XML namespace. If there is an xml-namespace-prefix, then the in-scope namespaces that are used to perform namespace processing on an xml-template-expr will include a binding of that prefix to the specified xml-namespace-uri; otherwise the in-scope namespaces will include a default namespace with the specified xml-namespace-uri.

An xml-namespace-prefix declared by an xmlns-decl is in the same symbol space as a module-prefix declared by an import-decl. This symbol space is distinct from a module's main symbol space used by other declarations. An xmlns-decl in a xmlns-decl-stmt declares the prefix within the scope of the current block.

The prefix xmlns is predeclared as http://www.w3.org/2000/xmlns/ and cannot be redeclared.

The static type of the simple-const-expr must be a subtype of string.

7.15 Assignment

There are three kinds of assignment statement:

The first two of these build on the concept of an lvalue, whereas the last one builds on the concept of a binding pattern.

7.15.1 Lvalues

An lvalue is what the left hand side of an assignment evaluates to. An lvalue refers to a storage location which is one of the following:

  • a variable;
  • a specific named field of an object;
  • the member of a structured value having a specific out-of-line key, which will be either an integer or a string according as the structured value is a list or a mapping.

Note that an lvalue cannot refer to a member of a table.

An lvalue that is both defined and initialized refers to a storage location that holds a value:

  • an lvalue referring to a variable is always defined but may be uninitialized;
  • an lvalue referring to a specific named field of an object is always defined but may not be initialized until the init method returns
  • an lvalue referring to member of a structured value having a specific key is undefined if the structured value does not have a member with that key; if such an lvalue is defined, it is also initialized; note that an lvalue always refers to a structured value that is already constructed.
lvexpr :=
   variable-reference-lvexpr
   | field-access-lvexpr
   | member-access-lvexpr

The left hand side of an assignment is syntactically a restricted type of expression, called an lvexpr. When the evaluation of an lvexpr completes normally, its result is an lvalue. The evaluation of an lvexpr can also complete abruptly, with a panic or check-fail, just as with the evaluation of an expression.

The compiler determines a static type for each lvexpr just as it does for expressions, but the meaning is slightly different. For an lvexpr L to have static type T means that if the runtime evaluation of L completes normally resulting in an lvalue x, then if x is defined and initialized, it refers to a storage location that holds a value belonging to type T. In addition to a type, the compiler determines for each lvexpr whether it is potentially undefined and whether it is potentially uninitialized.

An lvalue supports three operations: store, read and filling-read.

The fundamental operation on an lvalue is to store a value in the storage location it refers to. This operation does not required the lvalue to be defined or initialized; a successful store operation on an undefined lvalue will result in the addition of a member to the structured value; a store on an uninitialized lvalue will initialize it. When an lvalue refers to a variable, it is possible to determine at compile-time whether the store of a value is permissible based on the declaration of the variable and the static type of the value to be stored. However, when the lvalue refers to a member of a structured value, this is not in general possible for three reasons.

  1. The guarantee provided by an lvalue having a static type T is not that the referenced storage location can hold every value that belongs to type T; rather the guarantee is that every value that the referenced storage location can hold belongs to type T. This is because structured types are covariant in their member types. The values that the storage location can actually hold are determined by the inherent type of the structured value.
  2. The member may not be defined and the inherent type of the structured value may not allow a member with that specific key. The permissible keys of a structured value can be constrained by closed record types, fixed-length array types, and tuple types (with any rest descriptor).
  3. The structured value may be immutable. The static type of an lvalue referring to a member of a structured value makes no guaranteees that the structured value is not immutable.

The first of these reasons also applies to lvalues that refer to fields of objects. Accordingly, stores to lvalues other than lvalues that refer to variables must be verified at runtime to ensure that they are not impermissible for any of the above three reasons. An attempt to perform an impermissible store results in a panic at runtime.

List values maintain the invariant that there is a unique integer n, the length of the list, such that a member k of the list is defined if and only if k is a non-negative integer less than n. When a store is performed on an lvalue referring to a member k of a list value and the length of the list is n and k is > n, then each member with index i for each ≤ i < k is filled in, by using the FillMember abstract operation. The FillMember abstract operation may fail; in particular it will fail if the list is a fixed-length array. If the FillMember operation fails, then the attempt to store will panic.

An lvalue also allows a read operation, which is used by the compound assignment statement. Unlike a store operation, a read operation cannot result in a runtime panic. A read operation cannot be performed on an lvalue that is undefined or uninitialized.

Finally, a lvalue supports a filling-read operation, which is used to support chained assignment. A filling-read is performed only an lvalue with a static type that is a structured type. It differs from a read operation only when it is performed on a potentially undefined lvalue. If the lvalue is undefined at runtime, then the filling-read will use the FillMember abstract operation on the member that the lvalue refers to. If the FillMember operation fails, then the filling-read panics. Unlike the read operation, the filling-read operation can be performed on an undefined lvalue; it cannot, however, be performed on an uninitialized lvalue.

The evaluation of an lvexpr is specified in terms of the evaluation of its subexpressions, the evaluation of its sub-lvexprs, and store and filling-read operations on lvalues. If any of these result in a panic, then the evaluation of the lvexpr completes abruptly with a panic.

variable-reference-lvexpr := variable-reference

The result of evaluating variable-reference-lvexpr is an lvalue referring to a variable. Its static type is the declared or inferred type of the variable. The lvexpr is potentially uninitialized if it is possible for execution to have reached this point without initializing the referenced variable.

field-access-lvexpr := lvexpr . field-name

The static type of lvexpr must be either a subtype of the mapping basic type or a subtype of the object basic type.

In the case where the static type of lvexpr is a subtype of the object basic type, the object type must have a field with the specified name, and the resulting lvalue refers to that object field.

In the case where the static type of lvexpr is a subtype of the mapping basic type, the semantics are as follows.

  • The following requirements apply at compile time: the type of lvexpr must include mapping shapes that have the specified field-name; type descriptor for lvexpr must include field-name as an individual-type-descriptor (if lvexpr is a union, then this requirement applies to every member of the union); lvexpr must not be potentially uninitialized, but may be potentially undefined.
  • It is evaluated as follows:
    1. evaluate lvexpr to get lvalue lv;
    2. perform a filling-read operation on lv to get mapping value m;
    3. the result is an lvalue referring to the member of m with the specified field-name.
  • The static type of the field-access-expr is the member type for the key type K in T, where T is the static type of the lvexpr and K is the singleton string type containing the field-name; the field-access-expr is potentially undefined if K is an optional key type for T.
member-access-lvexpr := lvexpr [ expression ]

The static type of lvexpr must be either a subtype of the mapping basic type or a subtype of the list basic type. In the former case, the contextually expected type of expression must be string and it is an error if the static type of expression is not string; in the latter case, the contextually expected type of expression must be int and it is an error if the static type of expression is not int.

It is evaluated as follows:

  1. evaluate expression to get a string or int k;
  2. evaluate lvexpr to get lvalue lv;
  3. perform a filling-read operation on lv to get a list or mapping value v;
  4. the result is an lvalue referring to the member of c with key k.

The static type of the member-access-expr is the member type for the key type K in T, where T is the static type of the lvexpr and K is the static type type of expression; the member-access-expr is potentially undefined if K is an optional key type for T.

7.15.2 Assignment statement

assignment-stmt := lvexpr = action-or-expr ;

The static type of action-or-expr must be a subtype of the static type of lvexpr. The static type of lvexpr provides the contextually expected type for action-or-expr. It is not an error for lvexpr to be potentially undefined or potentially uninitialized.

It is executed at as follows:

  1. execute action-or-expr to get a value v;
  2. evaluate lvexpr to get an lvalue lv;
  3. perform the store operation of lv with value v.

7.15.3 Compound assignment statement

compound-assignment-stmt := lvexpr CompoundAssignmentOperator action-or-expr ;
CompoundAssignmentOperator := BinaryOperator =
BinaryOperator := + | - | * | / | & | | | ^ | << | >> | >>>

It is a compile error if lvexpr is potentially undefined unless the static type of lvexpr is a subtype of the list basic type. It is a compile error if lvexpr is potentially uninitialized.

Let T1 be the static type of lvexpr, and let T2 be the static type of action-expr. Then let T3 be the static type of an expression E1 BinaryOp E2 where E1 has type T1 and E2 has type T2. It is a compile error if T3 is not a subtype of T1.

It is executed as follows:

  1. execute action-or-expr to get a value v2;
  2. evaluate lvexpr to get an lvalue lv;
  3. if lv is undefined, panic (lv must refer to an undefined member of a list)
  4. perform the read operation on lv to get a value v1
  5. perform the operation specified by BinaryOperator on operands v1 and v2, resulting in a value v3
  6. perform the store operation on lv with value v3.

7.15.4 Destructuring assignment statement

destructuring-assignment-stmt :=
   binding-pattern-not-variable-reference = action-or-expr ;
binding-pattern-not-variable-reference :=
   wildcard-binding-pattern
   | list-binding-pattern
   | mapping-binding-pattern
   | functional-binding-pattern

A destructuring assignment is executed by evaluating the action-or-expr resulting in a value v, and then matching the binding pattern to v, causing assignments to the variables occurring in the binding pattern.

The binding-pattern has a static type implied by the static type of the variables occurring in it. The static type of action-or-expr must be a subtype of this type.

7.16 Action statement

action-stmt := action ;

An action-stmt is a statement that is executed by evaluating an action and discarding the resulting value, which must be nil. It is a compile-time error if the static type of an action in an action-stmt is not nil.

7.17 Call statement

call-stmt := call-expr ;
call-expr :=
   function-call-expr
   | method-call-expr
   | checking-keyword call-expr

A call-stmt is executed by evaluating call-expr as an expression and discarding the resulting value, which must be nil. It is a compile-time error if the static type of the call-expr in an call-stmt is not a subtype of nil. Note that never is a subtype of nil.

7.18 Conditional statement

if-else-stmt :=
   if expression block-stmt 
   [ else if expression block-stmt ]* 
   [ else block-stmt ]

The if-else statement is used for conditional execution.

The static type of the expression following if must be boolean. When an expression is true then the corresponding block statement is executed and the if statement completes. If no expression is true then, if the else block is present, the corresponding block statement is executed.

7.19 Match statement

match-stmt := match action-or-expr { match-clause+ }
match-clause :=
  match-pattern-list [match-guard] => block-stmt
match-guard := if expression

A match statement selects a block statement to execute based on which patterns a value matches.

A match-stmt is executed as follows:

  1. the expression is evaluated resulting in some value v;
  2. for each match-clause in order:
    1. a match of match-pattern against v is attempted
    2. if the attempted match fails, the execution of the match-stmt continues to the next match-clause
    3. if the attempted match succeeds, then the variables in match-pattern are created
    4. if there is a match-guard, then the expression in match-guard is executed resulting in a value b
    5. if b is false, then the execution of the match-stmt continues to the next match-clause
    6. otherwise, the block-stmt in the match-clause is executed
    7. execution of the match-stmt completes

The scope of any variables created in a match-pattern-list of a match-clause is both the match-guard, if any, and the block-stmt in that match-clause. The static type of the expression in match-guard must be a subtype of boolean.

match-pattern-list := 
  match-pattern (| match-pattern)*

A match-pattern-list can be matched against a value. An attempted match can succeed or fail. A match-pattern-list is matched against a value by attempting to match each match-pattern until a match succeeds.

All the match-patterns in a given match-pattern-list must bind the same set of variables.

match-pattern :=
  var binding-pattern
   | wildcard-match-pattern
   | const-pattern
   | list-match-pattern
   | mapping-match-pattern
   | functional-match-pattern

A match-pattern combines the destructuring done by a binding-pattern with the ability to match a constant value.

Note that an identifier can be interpreted in two different ways within a match-pattern:

A match-pattern must be linear: a variable that is to be bound cannot occur more than once in a match-pattern.

const-pattern := simple-const-expr

A const-pattern denotes a single value. Matching a const-pattern denoting a value p against a value v succeeds if DeepEquals(p,v) is true. The static type of the simple-const-expr in a const-pattern must be a subtype of anydata. Successfully matching a const-pattern does not cause any variables to be created.

Matching a wildcard-match-pattern against a value succeeds if the value belongs to type any, in other words if the basic type of the value is not error.

wildcard-match-pattern := _
list-match-pattern := [ list-member-match-patterns ]
list-member-match-patterns :=
   match-pattern (, match-pattern)* [, rest-match-pattern]
   | [ rest-match-pattern ]
mapping-match-pattern := { field-match-patterns }
field-match-patterns :=
   field-match-pattern (, field-match-pattern)* [, rest-match-pattern]
   | [ rest-match-pattern ] 
field-match-pattern := field-name : match-pattern
rest-match-pattern := ... var variable-name
functional-match-pattern := functionally-constructible-type-reference ( arg-list-match-pattern )
arg-list-match-pattern :=
   positional-arg-match-patterns [, other-arg-match-patterns]
   | other-arg-match-patterns
positional-arg-match-patterns := positional-arg-match-pattern (, positional-arg-match-pattern)*
positional-arg-match-pattern := match-pattern
other-arg-match-patterns :=
   named-arg-match-patterns [, rest-match-pattern]
   | [rest-match-pattern]
named-arg-match-patterns := named-arg-match-pattern (, named-arg-match-pattern)*
named-arg-match-pattern := arg-name = match-pattern

Matching a mapping-match-pattern against a mapping value succeeds if and only every field-match-pattern matches against a field of the value. The variable in the rest-match-pattern, if specified, is bound to a new mapping that contains just the fields for which that did not match a field-match-pattern.

For a match of a functional-match-pattern against a value to succeed, the type referenced by the functionally-constructible-type-reference must contain the shape of the value; because of the limited mutability of the xml and error basic types, this requirement is equivalent to requiring that the value belong to the referenced type.

7.20 Foreach statement

foreach-stmt :=
   foreach typed-binding-pattern in action-or-expr block-stmt

A foreach statement iterates over an iterable value, executing a block statement once for each value in the iterable value's iteration sequence. The static type of action-or-expr must be an iterable type with an iteration completion type of nil.

The scope of any variables created in typed-binding-pattern is block-stmt. These variables are implicitly final.

In more detail, a foreach statement executes as follows:

  1. evaluate the action-or-expr resulting in a value c
  2. create an iterator object i from c as follows
    1. if c is a basic type that is iterable, then i is the result of calling c.iterator()
    2. if c is an object and c belongs to Iterable<T,()> for some T, then i is the result of calling c.iterator()
  3. call i.next() resulting in a value n
  4. if n is nil, then terminate execution of the foreach statement
  5. match typed-binding-pattern to n.value causing assignments to any variables that were created in typed-binding-pattern
  6. execute block-stmt with the variable bindings from step 5 in scope; in the course of so doing
    1. the execution of a break-stmt terminates execution of the foreach statement
    2. the execution of a continue-stmt causes execution to proceed immediately to step 3
  7. go back to step 3

In step 2, the compiler will give an error if the static type of expression is not suitable for 2a or 2b.

In step 5, the typed-binding-pattern is used unconditionally, and the compiler will check that the static types guarantee that the match will succeed. If the typed-binding-pattern uses var, then the type will be inferred from the type of action-or-expr.

7.21 While statement

while-stmt := while expression block-stmt

A while statement repeatedly executes a block statement so long as a boolean-valued expression evaluates to true.

In more detail, a while statement executes as follows:

  1. evaluate expression;
  2. if expression evaluates to false, terminate execution of the while statement;
  3. execute block-stmt; in the course of so doing
    1. the execution of a break-stmt results in termination of execution of the while statement
    2. the execution of a continue-stmt causes execution to proceed immediately to step 1
  4. go back to step 1.

The static type of expression must be a subtype of boolean.

7.22 Continue statement

continue-stmt := continue ;

A continue statement is only allowed if it is lexically enclosed within a while-stmt or a foreach-stmt. Executing a continue statement causes execution of the nearest enclosing while-stmt or foreach-stmt to jump to the end of the outermost block-stmt in the while-stmt or foreach-stmt.

7.23 Break statement

break-stmt := break ;

A break statement is only allowed if it is lexically enclosed within a while-stmt or a foreach-stmt. Executing a break statement causes the nearest enclosing while-stmt or foreach-stmt to terminate.

7.24 Panic statement

panic-stmt := panic expression ;

A panic statement terminates the current worker abnormally. The result of evaluating expression provides the termination value of the worker.

The static type of expression must be a subtype of error.

7.25 Return statement

return-stmt := return [ action-or-expr ] ;

A return statement terminates the current worker normally.The result of evaluating the action-or-expr provides the termination value of the worker. If action-or-expr is omitted, then the termination value is nil.

7.26 Lock statement

lock-stmt := lock block-stmt

A lock-stmt executing in the context of some strand must execute its block-stmt in such a way that the effect on the state of the program is consistent with the execution of the block-stmt not being interleaved with the execution of a lock-stmt on any other strand.

A naive implementation can simply acquire a single, program-wide, recursive mutex before executing a lock statement, and release the mutex after completing the execution of the lock statement. A more sophisticated implementation can perform compile-time analysis to infer a more fine-grained locking strategy that will have the same effects as the naive implementation.

It is not allowed to start a new strand within a lock. More precisely, when a strand has started but not yet completed the execution of a lock-stmt, the execution of a named-worker-decl or a start-action on that strand will result in a panic. It is a compile-time error for a named-worker-decl or start-action to occur lexically within a lock-stmt. The compiler may also give a compile-time error if a function definition contains a named-worker-decl or start-action, and there is a function call lexically within the lock-stmt that might result directly or indirectly in a call to that defined function.

8. Module-level declarations

Each source part in a Ballerina module must match the production module-part.

The import declarations must come before other declarations; apart from this, the order of the definitions and declarations at the top-level of a module is not constrained.

module-part := import-decl* other-decl*
other-decl :=
   listener-decl
   | service-decl
   | function-defn
   | module-type-defn
   | module-var-decl
   | module-const-decl
   | module-enum-decl
   | module-xmlns-decl
   | annotation-decl

8.1 Import declaration

import-decl := 
   import [org-name /] module-name [version sem-ver] 
   [as import-prefix] ;
import-prefix := module-prefix | _
module-prefix := identifier
org-name := identifier
module-name := identifier (. identifier)*
sem-ver := major-num [. minor-num [. patch-num]]
major-num := DecimalNumber
minor-num := DecimalNumber
patch-num := DecimalNumber

qualified-identifier := module-prefix : identifier

If org-name is omitted, it is defaulted from the organization of the importing module.

A module-prefix is a name that is used locally within the source of a module to refer to another module. A module-prefix in a qualified-identifier must refer to a module-prefix specified in an import-declaration in the same source part.

An import-prefix of _ causes the module to be imported without making its symbols available via a module-prefix. In this case, the effect of importing the module will be just to cause the module to be included in the program and initialized. It is an error for a source-part to import a module using a module-prefix and then not to use that module-prefix.

A module-prefix declared by an import-decl is in the same symbol space as a xmlns-namespace-prefix declared by an xmlns-decl. This symbol space is distinct from a module's main symbol space used by other declarations.

A module prefix of t, where t is one of error, object or xml, is predeclared as referring to the lang.t lang library module, but this can be overridden by an import-decl.

It is an error for a module to directly or indirectly import itself. In other words, the directed graph of module imports must be acyclic.

8.2 Module and program execution

A Ballerina program consists of one or more modules; one of these modules is distinguished as the root module. The source code for a module uses import declarations to identify the modules on which it depends directly. At compile-time, a root module is specified, and the modules comprising the program are inferred to be those that the root module imports directly or indirectly. The directed graph of module imports must be acyclic.

Program execution may terminate successfully or unsuccessfully. Unsuccessful program termination returns an error value. Program execution consists of two consecutive phases: an initialization phase and a listening phase.

Module initialization is performed by calling an initialization function, which is synthesized by the compiler for each module. Module initialization can fail, in which case the initialization function returns an error value. The initialization phase of program execution consists of initializing each of the program's modules. If the initialization of a module is unsuccessful, then program execution immediately terminates unsuccessfully, returning the error value returned by the initialization function.

The initialization of a program's modules is ordered so that a module will not be initialized until all of the modules on which it depends have been initialized. (Such an ordering will always be possible, since the graph of module imports is required to be acyclic.) The order in which modules are initialized follows the order in which modules are imported so far as is consistent with the previous constraint.

A module's initialization function performs expression evaluation so as to initialize the identifiers declared in the module's declarations; if evaluation of an expression completes abruptly, then the module initialization function immediately returns the error value associated with the abrupt completion. If a module defines a function named init, then a module's initialization function will end by calling this function; if it terminates abruptly or returns an error, then the module's initialization function will return an error value. Note that the init function of the root module will be the last function called during a program's initialization phase.

This specification does not define any mechanism for processing the program command-line arguments typically provided by an operating system. The Ballerina standard library provides a function to retrieve these command-line arguments. In addition, the Ballerina platform provides a convenient mechanism for processing these arguments. This works by generating a new command-line processing module from the specified root module. The init function of the generated module retrieves the command-line arguments, parses them, and calls a public function of the specified root module (typically the main function). The parsing of the command-line arguments is controlled by the declared parameter types, annotations and names of the public functions. The generated module, which imports the specified root module, becomes the new root module.

If the initialization phase of program execution completes successfully, then execution proceeds to the listening phase, which is described in the next section. The termination of the listening phase, which may be successful or unsuccessful, terminates the program execution.

8.3 Listeners and services

Services have resource methods, which are the network entry points of a Ballerina program. Listeners provide the interface between the network and services. A listener object receives network messages from a remote process according to some protocol and translates the received messages into calls on the resource methods of service values that have been attached to the listener object. It is up to the listener object to determine how this translation happens; the type of the listener object thus constrains the type of a service that can be attached to the listener. (This constraint is not yet enforced by Ballerina's type system.)

The return type of a resource method represents whether the method successfully handled the protocol message. The listener typically calls a resource method with a client object as a parameter. The remote methods on the client object correspond to the distinct network messages defined by the protocol as possible responses to the message that the resource method is handling. A resource method called by a listener as a result of a network message received from a remote process can then use the client object to send a response message back to that remote process.

The methods defined by the Listener abstract object type allow for the management of the lifecycle of a listener object and its attached services. A listener declaration registers a listener object with a module, so that it can be managed by the module. The runtime state of each module includes a list of listener objects that have been registered with the module. A listener object that has been registered with a module is called a module listener

If at the start of the listening phase of program execution there are no module listeners, then the listening phase immediately terminates successfully. Otherwise, the __start method of each module listener is called; if any of these calls returns an error value, then the listening phase terminates unsuccessfully with this error value as its return value.

The listening phase of program execution continues until either the program explicitly exits, by calling a standard library function, or the user explicitly requests the termination of the program using an implementation-dependent operating system facility (such as a signal on a POSIX system). In the latter case, the __gracefulStop or __immediateStop method of each registered listener will be called before termination.

8.3.1 Listener declaration

listener-decl :=
   metadata
   [public] listener [type-descriptor] variable-name = expression ;

A listener-decl declares a module listener. A module listener declares a variable in a similar way to a final variable declaration, but the type of the variable is always a subtype of the Listener abstract object type, and it has the additional semantic of registering the variable's value with the module as a listener. As with a variable declared in a final variable declaration, the variable can be referenced by a variable-reference, but cannot be assigned to. A module may have multiple multiple listeners.

If the type-descriptor is present it specifies the static type of the variable; if it is not present, the the static type is the static type of expression. In both cases, the static type is constrained to be a subtype of the Listener abstract object type.

When a listener-decl is initialized as part of module initialization, its expression is evaluated. If expression evaluation completes abruptly, then module initialization fails. Otherwise the variable is initialized with the result of the evaluation.

8.3.2 Service declaration

service-decl :=
  metadata
  service [variable-name] on expression-list service-body-block
expression-list := expression (, expression)*

A service-decl creates a service and attaches it to one or more listeners. If a variable-name is specified, it also declares a variable; as with a variable declared in a final variable declaration, the variable can be referenced by a variable-reference, but cannot be assigned to.

The service-body-block has the same semantics as in a service-constructor-expr. The static type of each expression in the expression list must be a subtype of the union of error with the Listener abstract object type. If an implementation is using annotations to provide more precise typing of services, then any annotations on the service type that is the first parameter of the __attach method in the object type of each expression in expression list will constrain the type of the service value constructed by the service-body-block.

A service-decl is initialized as part of module initialization as follows. The service-body-block is evaluated as in a service-constructor resulting in a service value s. If variable-name is specified, then the variable is initialized to s. Then for each expression in expression-list:

  1. the expression is evaluated resulting in a value which is either an error or an object obj that is a subtype of Listener;
  2. if it is an error, module initialization fails;
  3. otherwise, obj is registered as a module listener (registering the same object multiple times is the same as registering it once);
  4. s is then attached to obj using obj's __attach method;
  5. if the call to __attach fails, then module initialization fails.

8.4 Function definition

function-defn := 
   metadata
   [public]
   function identifier function-signature function-defn-body
function-defn-body :=
   block-function-body
   | expr-function-body ;
   | external-function-body ;
external-function-body := = [annots] external 

An external-function-body means that the implementation of the function is not provided in the Ballerina source module. A function-defn-body with a function-signature that is dependently-typed must be an external-function-body.

If a module has a function-defn with an identifier of init, it is called called automatically by the system at the end of the initialization of that module; if this call returns an error, then initialization of the module fails. The following special requirements apply to the init function of a module: it must not be declared public; its return type must both be a subtype of error? and contain (); it must have no parameters.

8.5 Module type definition

module-type-defn :=
   metadata
   [public] type identifier type-descriptor ;

8.6 Module variable declaration

module-var-decl := module-init-var-decl | module-no-init-var-decl
module-init-var-decl := metadata [final] typed-binding-pattern = expression ;

The scope of variables declared in a module-var-decl is the entire module. Note that module variables are not allowed to be public. If final is specified, then it is not allowed to assign to the variable. If the typed-binding-pattern uses var, then the type of the variable is inferred from the static type of expression; if the module-var-decl includes final, the precise type is used, and otherwise the broad type is used. If the typed-binding-pattern specifies a type-descriptor, then that type-descriptor provides the contextually expected type for action-or-expr.

module-no-init-var-decl := metadata [final] type-descriptor variable-name ;

A module variable declared with module-no-init-var-decl must be initialized in the module's init function. It must be definitely assigned at each point that the variable is referenced. If final is specified, then the variable must not be assigned more than once.

8.7 Module constant declaration

module-const-decl :=
   metadata
   [public] const [type-descriptor] identifier = const-expr ;

A module-const-decl declares a compile-time constant. A compile-time constant is an named immutable value, known at compile-time. A compile-time constant can be used like a variable, and can also be referenced in contexts that require a value that is known at compile-time, such as in a type-descriptor or in a match-pattern.

The type of the constant is the intersection of readonly and the singleton type containing just the shape of the value named by the constant. The type of the constant determines the static type of a variable-reference-expr that references this constant.

If type-descriptor is present, then it provides the contextually expected type for the interpretation of const-expr. It is a compile-time error if the static type of const-expr is not a subtype of that type. The type-descriptor must specify a type that is a subtype of anydata. Note that the type-descriptor does not specify the type of the constant, although the type of the constant will all be a subtype of the type specified by the type-descriptor.

8.8 Module enumeration declaration

module-enum-decl :=
   metadata
   [public] enum identifier { enum-member (, enum-member)* }
enum-member := metadata identifier [= const-expr]

A module-enum-decl provides a convenient syntax for declaring a union of string constants.

Each enum-member is defined as compile-time constant in the same way as if it had been defined using a module-const-decl. The result of evaluating the const-expr must be a string. If the const-expr is omitted, it defaults to be the same as the identifier.

The identifier is defined as a type in the same was as if it had been defined by a module-type-defn, with the type-descriptor being the union of the constants defined by the members.

If the module-enum-decl is public, then both the type and the constants are public.

So for example:

public enum Color {
  RED,
  GREEN,
  BLUE
}

is exactly equivalent to:

public const RED = "RED";
public const GREEN = "GREEN";
public const BLUE = "BLUE";
public type Color RED|GREEN|BLUE;

8.9 Module XML namespace declaration

module-xmlns-decl := xmlns-decl

A module-xmlns-decl declares an XML namespace prefix with module scope. It applies only to the source part in which it occurs, as with an import-decl.

The semantics of xmlns-decl are described in the XML namespace declaration statement section.

9. Metadata

Ballerina allows metadata to be attached to a construct by specifying the metadata before the construct.

metadata := [DocumentationString] [annots]

There are two forms of metadata: documentation and annotations.

9.1 Annotations

annots := annotation+
annotation := @ annot-tag-reference annot-value

Annotations provide structured metadata about a particular construct. Multiple annotations can be applied to a single construct. An annotation consists of a tag and a value.

annotation-decl :=
   metadata
   [public] [const] annotation [type-descriptor] annot-tag 
   [on annot-attach-points] ;
annot-tag := identifier

An annotation-decl declares an annotation tag. Annotations tags are in a separate symbol space and cannot conflict with other module level declarations and definitions. The annotation tag symbol space is also distinct from the symbol space used by module prefixes and XML namespace prefixes.

The type-descriptor specifies the type of the annotation tag. The type must be a subtype of one of the following three types: true, map<value:Cloneable>, map<value:Cloneable>[]. If the type-descriptor is omitted, then the type is true.

annot-tag-reference := qualified-identifier | identifier
annot-value := [mapping-constructor-expr]

An annot-tag-reference in an annotation must refer to an annot-tag declared in an annotation declaration. When an annot-tag-reference is a qualified-identifier, then the module-prefix of the qualified-identifier is resolved using import declarations into a reference to a module, and that module must contain an annotation-decl with the same identifier. An annot-tag-reference that is an identifier rather than a qualified-identifier does not refer to an annotation defined within the same module. Rather the compilation environment determines which identifiers can occur as an annotation-tag-reference, and for each such identifier which module defines that annotation tag.

If the annotation includes a mapping-constructor-expr, then the value of the annotation is the mapping value resulting from evaluating the mapping-constructor-expr; otherwise the value is the boolean value true. For every construct that has an annotation with a particular tag, there is an effective value for that annotation tag, which is constructed from the values of all annotations with that tag that were attached to that construct. The effective value must belong to the type of the annotation tag.

The type of the annotation tag constrains both the annotation value and the occurrence of multiple annotations with the same tag on a single construct as follows.

If the annotation-decl for a tag specifies const, then a mapping-constructor-expr in annotations with that tag must be a const-expr and is evaluated at compile-time with the semantics of a const-expr. Otherwise, the mapping-constructor-expr is evaluated when the annotation is evaluated and the ImmutableClone abstract operation is applied to the result.

An annotation applied to a module-level declaration is evaluated when the module is initialized. An annotation applied to a service constructor is evaluated when the service constructor is evaluated. An annotation occurring within a type descriptor is evaluated when the type descriptor is resolved.

annot-attach-points := annot-attach-point (, annot-attach-point)*
annot-attach-point :=
   dual-attach-point
   | source-only-attach-point
dual-attach-point := [source] dual-attach-point-ident
dual-attach-point-ident :=
   [object] type
   | [object|resource] function
   | parameter
   | return
   | service
   | [object|record] field
source-only-attach-point := source source-only-attach-point-ident
source-only-attach-point-ident :=
   annotation
   | external
   | var
   | const
   | listener
   | worker

The annot-attach-points specify the constructs to which an annotation can be attached.

When an attachment point is prefixed with source, then the annotation is attached to a fragment of the source rather than to any runtime value, and thus is not available at runtime. If any of the attachment points specify source, the annotation-decl must specify const.

When an attachment point is not prefixed with source, then the annotation is accessible at runtime by applying the annotation access operator to a typedesc value.

The available attachment points are described in the following table.

Attachment point name Syntactic attachment point(s) Attached to which type descriptor at runtime
type module-type-defn, local-type-defn-stmt, module-enum-decl, type-cast-expr defined type
object type module-type-defn or local-type-defn-stmt, whose type descriptor is a non-abstract object type descriptor defined type (which will be type of objects constructed using this type)
function function-defn, method-decl, method-defn, anonymous-function-expr, service-method-defn type of function
object function method-decl, method-defn type of function
resource function service-method-defn with resource modifier type of function, on service value
return returns-type-descriptor indirectly to type of function
parameter individual-param, rest-param indirectly to type of function
service service-decl, service-constructor-expr type of service
field individual-field-descriptor, object-field-descriptor type of mapping or object
object field object-field-descriptor type of object
record field individual-field-descriptor type of mapping
listener listener-decl none
var module-var-decl, local-var-decl-stmt, let-var-decl none
const module-const-decl, enum-member none
annotation annotation-decl none
external external-function-body none
worker named-worker-decl, start-action none

9.2 Documentation

A documentation string is an item of metadata that can be associated with module-level Ballerina constructs and with method declarations. The purpose of the documentation strings for a module is to enable a programmer to use the module. Information not useful for this purpose should be provided in in comments.

A documentation string has the format of one or more lines each of which has a # optionally preceded by blank space.

The documentation statement is used to document various Ballerina constructs.

DocumentationString := DocumentationLine +
DocumentationLine := BlankSpace* # [Space] DocumentationContent
DocumentationContent := (^ 0xA)* 0xA
BlankSpace := Tab | Space
Space := 0x20
Tab := 0x9

A DocumentationString is recognized only at the beginning of a line. The content of a documentation string is the concatenation of the DocumentationContent of each DocumentationLine in the DocumentationString. Note that a single space following the # is not treated as part of the DocumentationContent.

The content of a DocumentationString is parsed as Ballerina Flavored Markdown (BFM). BFM is also used for a separate per-module documentation file, conventionally called Module.md.

9.3 Ballerina Flavored Markdown

Ballerina Flavored Markdown is GitHub Flavored Markdown, with some additional conventions.

In the documentation string attached to a function or method, there must be documentation for each parameter, and for the return value if the return value is not nil. The documentation for the parameters and a return value must consist of a Markdown list, where each list item must have the form ident - doc, where ident is either the parameter name or return, and doc is the documentation of that parameter or of the return value.

The documentation for an object must contain a list of fields rather than parameters. Private fields should not be included in the list.

BFM also provides conventions for referring to Ballerina-defined names from within documentation strings in a source file. An identifier in backticks `X`, when preceded by one of the following words:

is assumed to be a reference to a Ballerina-defined name of the type indicated by the word. In the case of parameter, the name must be unqualified and be the name of a parameter of the function to which the documentation string is attached. For other cases, if the name is unqualified it must refer to a public name of the appropriate type in the source file's module; if it is a qualified name M:X, then the source file must have imported M, and X must refer to a public name of an appropriate type in M. BFM also recognizes `f()` as an alternative to function `f`. In both cases, f can have any of the following forms (where `m` is a module import, `x` is a function name, `T` is an object type name, and `y` is a method name):

    x()
    m:x()
    T.y()
    m:T.y()

Example

    # Adds parameter `x` and parameter `y`
    # + x - one thing to be added
    # + y - another thing to be added
    # + return - the sum of them
    function add (int x, int y) returns int { return x + y; }

The Ballerina platform may define additional conventions, in particular relating to headings with particular content. For example, a heading with a content of Deprecated can be used to provide information about the deprecation of the name to which the documentation string is attached.

10. Lang library

Modules in the ballerina organization with a module name starting with lang. are reserved for use by this specification. These modules are called the lang library.

The lang library comprises the following modules. With the exception of the lang.value module, each corresponds to a basic type.

For each version of the specification, there is a separate version number for each module in its lang library. The module version numbers for this version of the specification are specified in JSON format.

Modules in the lang library can make use generic typing. Since generic typing has not yet been added to Ballerina, the source code for the modules use an annotation to describe generic typing as follows. When a module type definition has a @typeParam annotation, it means that this type serves as a type parameter when it is used in a function definition: all uses of the type parameter in a function definition refer to the same type; the definition of the type is an upper bound on the type parameter.

Note We plan to provide full support for generic types in a future version of this specification.

10.1 Built-in subtypes

A module in the lang library can provide types that are built-in in the sense that their meaning is defined by this specification. Each such built-in type is a subtype of a single basic type; a built-in type that is a subtype of a basic type B is provided by the module lang.B.

The built-types provided by lang library modules are described in the following table.

Basic type Type name Criteria for v to belong to type
int Unsigned8 0 ≤ v ≤ 255
Signed8 -128 ≤ v ≤ 127
Unsigned16 0 ≤ v ≤ 65,535
Signed16 -32,768 ≤ v ≤ 32,767
Unsigned32 0 ≤ v ≤ 4,294,967,295
Signed32 -2,147,483,648‬ ≤ v ≤ 2,147,483,6487
string Char v has length 1
xml Element v is an element singleton
ProcessingInstruction v is a processing instruction singleton
Comment v is a comment singleton
Text v is either the empty xml value or a text singleton

Each built-in type has a type definition in the module that provides it. The type descriptor of the type definition is the corresponding basic type. The type definition has a @builtinSubtype annotation, which indicates that the meaning of the type name is built-in, as specified in the above table, rather than coming from its type descriptor. It is an error to use the @builtinSubtype annotation except in a lang library module.

So, for example, the lang.int module would include the definition:

@builtinSubtype
type Signed32 int;

Semantically, these types behave like a predefined type that can be referenced by an unqualified name, such as byte. Syntactically, these types are referenced by a type-reference in the same way as if their definitions were not built-in. A built-in type T which is a subtype of basic type B can be referenced by a type-reference M:T, where M is a module-prefix referring to module ballerina/lang.B.

For convenience, this specification refers to the built-in subtype T provided by the module for basic type B as B:T. For example, in this specification int:Signed32 refers to the Signed32 built-in subtype of int, which is provided by the lang.int module.

The int:Unsigned8 type is equivalent to the predefined byte type.

A. References

B. Changes since previous releases

B.1 Summary of changes from 2020R1 to Swan Lake

  1. The readonly type has been added. Fields of records and objects can also be declared as readonly.
  2. Intersection types have been added.
  3. Distinct types have been added; these provide the functionality of nominal types with the framework of a structural type system.
  4. The error type has been revised to take advantage of distinct types. Instead an error value having a reason string for categorizing the error and separate message string in the detail record, an error value has a message string in the error and distinct types are used for categorizing. The cause has also moved from the detail record into the error value.
  5. Language-defined abstract object types now make use of distinct types, instead of using names prefixed with double underscore.
  6. The __init method of object and the __init function of modules have been renamed to init.
  7. Module variables can be initialized in the module's init function.
  8. The table type has been redesigned to be more consistent with other structural types and no longer has preview status.
  9. Enum declarations have been added.
  10. The return type of a function with an external body can depend on the value of a parameter of typedesc type.
  11. The XML item-specific subtypes can be used as constructors, both in expressions and in binding patterns.
  12. Query expressions support some new clauses: join clause, limit clause and on conflict clause.
  13. Raw template expressions have been added.
  14. A value of type xml:Text can be implicitly converted to a string.

B.2 Summary of changes from 2019R3 to 2020R1

  1. Query expressions and query actions have been added. This is the first stage of language-integrated query.
  2. The stream basic type has been added.
  3. Let expressions have been added.
  4. The XML design has been refined and no longer has Preview status.
    • The various kinds of xml item (e.g. element and text) are subtypes of the xml type.
    • The xml type can have a type parameter specifying the item types.
    • Iteration over xml values exposes characters as text items rather than strings
    • Adjacent characters in XML content are chunked into a single text item.
    • The meaning of === for xml has changed.
    • The item of an xml sequence value x with index i can be accessed using an expression x[i].
    • The syntax x@ for accessing the attributes of an XML element has been removed.
  5. The lock statement has been added.
  6. When a list constructor or mapping constructor is used without a contextually expected type, we now infer a tuple or record type rather than an array or map type.
  7. The syntax for Unicode escapes in strings has changed from \u[CodePoint] to \u{CodePoint} so as to align with ECMAScript. Although this is an incompatible change, the previous syntax was not implemented.
  8. The never type has been added.
  9. Lang library modules can now provide built-in subtypes of existing basic types.
  10. There is a lang.boolean lang lib module.

B.3 Summary of changes from 2019R2 to 2019R3

  1. An import declaration can use as _ to include a module in the program without using its symbols.
  2. The specification of experimental features has been moved to a separate document.
  3. A wait-action can result in an error value when applied to a future that is not a named worker.

B.4 Summary of changes from 2019R1 to 2019R2

  1. The concept of a built-in method has been replaced by the concept of a lang library. A method call on a value of non-object type is now treated as a convenient syntax for a call to a function in a module of the lang library. The design of the many of the existing built-in methods has been changed to fit in with this. There are many functions in the lang library that were not previously available as built-in methods.
  2. A mapping value is now iterable as a sequence of its members (like list), rather than as a sequence of key-value pairs. The entries lang library function allows it to be iterated as a sequence of key-value pairs.
  3. The basic type handle has been added.
  4. The table<T> type descriptor shorthand has been brought back.
  5. There is now a variation on check called checkpanic, which panics rather than returns on error.
  6. A range-expr now returns an object belonging to the Iterable abstract object type, rather than a list.
  7. The decimal type now uses a simplified subset of IEEE 754-2008 decimal floating point.
  8. The status of XML-related features has been changed to preview.
  9. The ability to define a method outside the object type has been removed.
  10. The UnfrozenClone operation has been removed.
  11. The Freeze operation has been replaced by the ImmutableClone operation.
  12. The semantics of field access, member access and assignment are now fully specified.
  13. A ?. operator has been added for access to optional fields.
  14. A type-cast-expr can include annotations.
  15. The error detail record must belong to type Detail defined in the lang library.
  16. The compile-time requirement that the inherent type of a variable-length list must allow members to be filled-in has been removed; this is instead caught at run-time.
  17. Parameter names now have public or module-level visibility, which determines when a function call can use the parameter name to specify an argument.
  18. A type descriptor record { } is open to anydata rather than anydata|error.
  19. Calls using start are treated as actions, and so are not allowed within expressions.
  20. There is a new syntax for allowing arbitrary strings as identifiers to replace the old delimited identifier syntax ^"s".

B.5 Summary of changes from 0.990 to 2019R1

The specification has switched to a new versioning scheme. The n-th version of the specification released in year 20xy will be labelled 20xyRn.

  1. Tuples types now use square brackets, rather than parentheses, as do tuple binding patterns and tuple match patterns. Array constructors and tuple constructors are now unified into list constructors, which use square brackets. Tuple types can have zero members or one member, and can use T... syntax allow trailing members of a specified type.
  2. The way that record type descriptors express openness has changed. Instead of the !... syntax, there are two flavours of record type descriptor, which use different delimiters: record {| |} allows any mapping that has exclusively the specified fields, whereas record { } allows any mapping that includes the specified fields; the former can use the T... syntax, whereas the latter cannot. The !... is no longer allowed for record binding patterns and record match patterns.
  3. The syntax for an array with an array length that is inferred has changed from T[!...] to T[*].
  4. A type descriptor of error<*> can be used to specify an error type whose subtype is inferred.
  5. A new expression can no longer be used to create values of structural types; it is only allowed for objects.
  6. Symbolic string literals 'ident have been removed (compile time constants provide a more convenient approach).
  7. untaint expression has been removed (this will be handled by annotations instead).
  8. The syntax for named arguments in a function call has reverted to arg= from arg:, since the latter caused syntactic ambiguities.
  9. The syntax for error constructors specifies fields of the error detail separately as named arguments, rather than specifying the error detail as a single argument; the syntax for binding patterns and match patterns for error values has also changed accordingly.
  10. The error reason argument can be omitted from an error constructor if it can be determined from the contextually expected type.
  11. The syntax for annotation declarations has been revised; the places where annotations are allowed has been revised to match the possible attachment points.
  12. An .@ binary operator has been added for accessing annotations at runtime.
  13. A unary typeof operator has been added.
  14. The typedesc type now takes an optional type parameter.
  15. The type parameters for future and stream are now optional.
  16. The syntax for a function with an external implementation has changed to use =external in place of the curly braces.
  17. A numeric literal can use a suffix of d or f to indicate that it represents a value belonging to the decimal or float type respectively.
  18. Record type descriptors may now specify a default value for fields.
  19. Providing a default value for a parameter no longer affects whether a function call must supply the argument for that parameter positionally or by name. Instead the argument for any parameter can be supplied either positionally or by name. To avoid ambiguity, all arguments specified positionally must be specified before arguments specified by name.
  20. Expressions specifying the default value for function parameters are not compile time constants, and are evaluated each time they are used to supply a missing argument.
  21. In the argument list of a function or method call, positional arguments are now required to be specified before named arguments.
  22. Types may now be defined within a block.

B.6 Summary of changes from 0.980 to 0.990

Structural types and values

  1. Concepts relating to typing of mutable structural values have been changed in order to make type system sound.
  2. The match statement has been redesigned.
  3. The but expression has been removed.
  4. The is expression for dynamic type testing has been added.
  5. The type-cast-expr <T>E now performs unsafe type casts.The only conversions it performs are numeric conversions.
  6. The anydata type has been added, which is a union of simple and structural types.
  7. Records are now by default open to anydata|error, rather than any.
  8. Type parameters for built-in types (map, stream, future), which previously defaulted to any, are now required.
  9. The type parameter for json (e.g. json<T>) is not allowed any more.
  10. Type for table columns are restricted to subtype of anydata|error.
  11. There are now two flavors of equality operator: == and != for deep equality (which is allowed only for anydata), and === and !== for exact equality.
  12. There is a built-in clone operation for performing a deep copy on values of type anydata.
  13. There is a built-in freeze operation for making structural values deeply immutable.
  14. Compile-time constants (which are always a subtype of anydata and frozen) have been added.
  15. Singleton types have been generalized: any compile-time constant can be made into a singleton value.
  16. Variables can be declared final, with a similar semantic to Java.
  17. Errors are now immutable.
  18. Module variables are not allowed to be public: only compile-time constants can be public.

Error handling

  1. The any type no longer includes error.
  2. check is now an expression.
  3. Exceptions have been replaced by panics
    1. the throw statement has been replaced by the panic statement
    2. the try statement has been replaced by the trap expression
  4. Object constructors (which could not return errors) have been replaced by __init methods (which can return errors).

Concurrency

  1. Workers in functions have been redesigned. In particular, workers now have a return value.
  2. The done statement has been removed.
  3. The fork/join statement has been redesigned.
  4. A syntactic category between expression and statement, called action, has been added.
  5. A synchronous message send action has been added.
  6. A flush action has been added to flush asynchronously sent messages.
  7. A wait action has been added to wait for a worker and get its return value.
  8. Futures have been unified with workers. A future<T> represents a value to be returned by a named worker.
  9. Error handling of message send/receive has been redesigned.

Endpoints and services

  1. Client endpoints have been replaced by client objects, and actions on client endpoints have been replaced by remote methods on client objects. Remote methods are called using a remote method call action, which replaces the action invocation statement.
  2. Module endpoint declaration has been replaced by module listener declaration, which uses the Listener built-in object type.
  3. The service type has been added as a new basic type of behavioral value, together with service constructor expressions for creating service values.
  4. Module service definitions have been redesigned.

Miscellaneous changes

  1. Public/private visibility qualifiers must be repeated on an outside method definition.

B.7 Summary of changes from 0.970 to 0.980

  1. The decimal type has been added.
  2. There are no longer any implicit numeric conversions.
  3. The type of a numeric literal can be inferred from the context.
  4. The error type is now a distinct basic type.
  5. The byte type has been added as a predefined subtype of int; blobs have been replaced by arrays of bytes.
  6. The syntax of string templates and xml literals has been revised and harmonized.
  7. The syntax of anonymous functions has been revised to provide two alternative syntaxes: a full syntax similar to normal function definitions and a more convenient arrow syntax for when the function body is an expression.
  8. The cases of a match statement are required to be exhaustive.
  9. The + operator is specified to do string and xml concatenation as well as addition.
  10. Bitwise operators have been added (<<, >>, >>>, &, |, ^, ~) rather than = after the argument name.
  11. In a function call or method call, named arguments have changed to use :
  12. A statement with check always handles an error by returning it, not by throwing it.
  13. check is allowed in compound assignment statements.
  14. Method names are now looked up differently from field names; values of types other than objects can now have built-in methods.
  15. The lengthof unary expression has been removed; the length built-in method can be used instead.
  16. The semantics of <T>expr have been specified.
  17. The value space for tuples and arrays is now unified, in the same way as the value space for records and maps was unified. This means that tuples are now mutable. Array types can now have a length.
  18. The next keyword has been changed to continue.
  19. The syntax and semantics of destructuring is now done in a consistent way for the but expression, the match statement, the foreach statement, destructuring assignment statements and variable declarations.
  20. The implied initial value is not used as a default initializer in variable declarations. A local variable whose declaration omits the initializer must be initialized by an assignment before it is used. A global variable declaration must always have an initializer. A new expression can be used with any reference type that has an implicit initial value.
  21. Postfix increment and decrement statements have been removed.
  22. The ... and ..< operators have been added for creating integer ranges; this replaces the foreach statement's special treatment of integer ranges.
  23. An object type can be declared to be abstract, meaning it cannot be used with new.
  24. By default, a record type now allows extra fields other than those explicitly mentioned; T... requires extra fields to be of type T and !... disallows extra fields.
  25. In a mapping constructor, an expression can be used for the field name by enclosing the expression in square brackets (as in ECMAScript).
  26. Integer arithmetic operations are specified to throw an exception on overflow.
  27. The syntax for documentation strings has changed.
  28. The deprecated construct has been removed (data related to deprecation will be provided by an annotation; documentation related to deprecation will be part of the documentation string).
  29. The order of fields, methods and constructors in object types is no longer constrained.
  30. A function or method can be defined as extern. The native keyword has been removed.

C. Planned future functionality

The vision for the Ballerina language includes a large range of functionality that is not yet included in this specification.

Proposals for new language features relating to this and other functionality are maintained in the specification's GitHub repository.

D. Other contributors

The following contributed to establishing the design principles of the language:

The following also contributed to the language in a variety of ways (in alphabetical order):