Chapter 5. Statements, blocks, and control structures

Function, value, and class bodies contain procedural code that is executed when the function is invoked, the value evaluated, or the class instantiated. The code contains expressions and control directives and is organized using blocks and control structures.

Note: the Ceylon language has a recursive block structure—statements and declarations that are syntactically valid in the body of a toplevel declaration are, in general, also syntactically valid in the body of a nested declaration or of a control structure, and vice-versa.

5.1. Block structure and references

A body is a block, defined in §5.3 Blocks and statements, class body, defined in §4.5 Classes, interface body, defined in §4.4 Interfaces, or comprehension clause, defined in §6.6.6 Comprehensions. Every body (except for a comprehension clause) is list of semicolon-delimited statements, control structures, and declarations, surrounded by braces. Some bodies end in a control directive. Every program element in the list is said to directly occur in the body. A program element directly occurs earlier than a second program element if both program elements directly occur in a body and the first program element occurs (lexically) earlier in the list than the second program element.

A program element (indirectly) occurs in a body if:

  • the program element directly occurs in the body, or

  • the program element indirectly occurs inside the body of a declaration or control structure that occurs directly in the body.

We sometimes say that the body contains the program element if the program element (indirectly) occurs in the body.

A program element (indirectly) occurs earlier than a second program element if:

  • the two program elements both directly occur in the same body, and the second program element occurs after the first program element, or

  • the second program element indirectly occurs inside the body of a declaration or control structure, and the first program element directly occurs earlier than the declaration or control structure.

Then we also say that the second program element (indirectly) occurs later than the first. The set of program elements that occur later than a program element is sometimes called the lexical scope of the program element.

A program element sequentially occurs in a body if:

  • the program element directly occurs in the body, or

  • the program element sequentially occurs inside the body of a control structure or constructor that occurs directly in the body.

A program element sequentially occurs earlier than a second program element if:

  • the two program elements both directly occur in the same body, and the second program element occurs after the first program element, or

  • the second program element sequentially occurs inside the body of a control structure or constructor, and the first program element directly occurs earlier than the control structure or constructor.

If a program element sequentially occurs earlier than a second program element, the sequence of statements from the first program element to the second program element comprises:

  • the sequence of statements that occur directly in the body in which the first program element directly occurs, beginning from the first program element and ending with the second program element, if the second program element occurs directly in the same body as the first program element, or

  • the sequence of statements that occur directly in the body in which the first program element directly occurs, beginning from the first program element and ending with the control structure or constructor in whose body the second program element sequentially occurs, followed by the sequence of statements from the first statement of the declaration whose body contains the second program element to the second program element itself, otherwise.

5.1.1. Declaration name uniqueness

A program element is contained within the namespace of a declaration if either:

  • the declaration is a toplevel declaration, and the program element is a toplevel declaration of the same package,

  • the declaration directly occurs in a body, the program element occurs in the same body, and the declaration sequentially occurs earlier than the program element,

  • the declaration is a parameter or type parameter, and the program element sequentially occurs in the body of the parameterized declaration, or

  • the program element is a control structure variable or iteration variable of a control structure that sequentially occurs in the namespace of the declaration.

The namespace of a declaration may not contain a second declaration with the same name. For example, the following is illegal:

function fun(Float number) {
    if (number<0.0) {
        Float number = 1.0; //error
        ...
    }
    ...
}

As an exception to this rule, the namespace of a declaration annotated native may contain a second declaration with the same name if:

  • the second declaration has exactly the same schema, as defined in §3.2 Types,

  • the second declaration is also annotated native, and

  • the two native annotations have distinct arguments for the backend parameter.

A class or interface may not inherit a declaration with the same name as a declaration it contains unless either:

  • the contained declaration directly or indirectly refines the inherited declaration,

  • the contained declaration is not shared, or

  • the inherited declaration is not shared.

A class or interface may not inherit two declarations with the same name unless either:

  • the class or interface contains a declaration that directly or indirectly refines both the inherited declarations (in which case both the inherited declarations directly or indirectly refine some member of a common supertype, as required by §4.5.6 Member class refinement, §4.8.7 Attribute refinement, and §4.7.8 Method refinement),

  • one of the inherited declarations directly or indirectly refines the other inherited declaration, or

  • at least one of the inherited declarations is not shared.

5.1.2. Scope of a declaration

The scope of a declaration is governed by the body or package in which it occurs. A declaration is in scope at a program element if and only if either:

  • the declaration is a parameter or type parameter of a declaration whose body contains the program element,

  • the declaration is a control structure variable or iteration variable belonging to a block of a control structure that contains the program element,

  • the program element belongs to or is contained in the body of the declaration itself,

  • the program element belongs to or is contained in the body of a class or interface which inherits the declaration,

  • the declaration directly occurs in a body containing the program element,

  • the declaration is imported into the toplevel namespace of the compilation unit containing the program element, or into the local namespace of a body containing the program element, as defined by §4.2 Imports, and is visible to the program element, as defined below in §5.1.3 Visibility, or

  • the declaration is a toplevel declaration in the package containing the program element.

Where:

  • A control structure variable or iteration variable belongs to a block of a control structure if the block immediately follows the declaration of the variable.

  • A program element belongs to a declaration if it occurs in the extends, satisfies, of, or given clause of the declaration.

Furthermore:

  • A condition variable of a condition belonging to a condition list is in scope in any condition of the same condition list that occurs lexically later.

  • A resource expression variable of a try statement is in scope in any resource expression of the same resource expression list that occurs lexically later.

  • An iteration variable or condition variable of a comprehension is in scope in any clause of the comprehension that occurs lexically later, since comprehension clauses are viewed as nested bodies.

And finally, there are special rules for annotation lists, defined in §7.1.1 Annotation lists:

  • An annotation argument list belongs to the annotated declaration.

  • An annotation name is considered to occur directly in the compilation unit containing the program element.

Note: if no reference to an un-shared declaration occurs within the scope of the declaration, a compiler warning is produced.

5.1.3. Visibility

Classes, interfaces, functions, values, aliases, and type parameters have names. Occurrence of a name in code implies a hard dependency from the code in which the name occurs to the schema of the named declaration. We say that a class, interface, value, function, alias, or type parameter is visible to a certain program element if its name may occur in the code that belongs to that program element.

The visibility of a declaration depends upon where it occurs, and upon whether it is annotated shared. A toplevel or member declaration may be annotated shared:

  • If a toplevel declaration is annotated shared, it is visible wherever the package that contains it is visible. Otherwise, a toplevel declaration is visible only to code in the package containing its compilation unit.

  • If a member declaration is annotated shared, it is visible wherever the class or interface that contains it is visible. Otherwise, a declaration that occurs directly inside a class or interface body is visible only inside the class or interface declaration.

Note: the Ceylon compiler enforces additional visibility restrictions for members of Java classes, since Java's visibility modifiers can express restrictions that cannot be reproduced within Ceylon's visibility model. These restrictions are outside the scope of this specification.

A type parameter or a declaration that occurs directly inside a block (the body of a function, getter, setter, or control structure) may not be annotated shared.

  • A type parameter is visible only inside the declaration to which it belongs.

  • A declaration that occurs directly inside a block is visible only inside the block.

TODO: Should we allow you to limit the effect of the shared annotation by specifying a containing program element or package?

We say that a type is visible to a certain program element if it is formed from references to classes, interfaces, type parameters, and type aliases whose declarations are visible to the program element. For shared declarations:

  • The type of a value must be visible everywhere the value itself is visible.

  • The return type of a function must be visible everywhere the function itself is visible.

  • The satisfied interfaces of a class or interface must be visible everywhere the class or interface itself is visible.

  • The superclass of a class must be visible everywhere the class itself is visible.

  • The aliased type of a class alias, interface alias, or type alias must be visible everywhere the alias itself is visible.

5.1.4. Hidden declarations

If two declarations with the same name or imported name, as defined in §4.2.6 Imported name, are both in scope at a certain program element, then one declaration may hide the other declaration.

  • If an inner body is contained (directly or indirectly) in an outer body, then a declaration that is in scope in the inner body, but is not in scope in the outer body, hides a declaration that is in scope in the outer body. In particular, a declaration inherited by a nested class or interface hides a declaration of the containing body.

  • An un-shared declaration occurring directly in the body of a class containing the program element, or imported into the local namespace of the class body, hides a declaration inherited by the class.

  • An actual declaration hides the declaration it refines.

  • A declaration occurring in a body containing the program element, or imported into the local namespace of a body containing the program element, hides a declaration imported into the toplevel namespace of the compilation unit containing the program element or implicitly imported from the module ceylon.language.

  • A toplevel declaration of the package containing the program element hides a declaration implicitly imported from the module ceylon.language.

  • A declaration imported into the toplevel namespace of the compilation unit containing the program element, or into the local namespace of a body containing the program element, hides a toplevel declaration of the package containing the compilation unit in which the program element occurs.

  • A declaration explicitly imported by name into a namespace containing the program element hides a declaration imported by wildcard into the same namespace.

For example, the following code is legal:

class Person(name) {
    String name;
    shared String lowerCaseName {
        String name = this.name.lowercased;
        return name;
    }
}

As is this code:

class Point(x, y) {
    shared Float x; 
    shared Float y;
}

class Complex(Float x, Float y=0.0) 
        extends Point(x, y) {}

When a member of a class is hidden by a nested declaration, the member may be accessed via the self reference this, defined in §6.3.1 this, or via the outer instance reference outer, defined in §6.3.2 outer.

shared class Item(name) {
    variable String name;
    shared void changeName(String name) {
        this.name = name;
    }
}
class Catalog(name) {
    shared String name;
    class Schema(name) {
        shared String name;
        Catalog catalog => outer;
        String catalogName => outer.name;
        class Table(name) {
            shared String name;
            Schema schema => outer;
            String schemaName => outer.name;
            String catalogName => catalog.name;
        }
    }
}

When a toplevel declaration of a package is hidden by another declaration, the toplevel declaration may be accessed via the containing package reference package, as defined in §5.1.7 Unqualified reference resolution.

Integer n => 0;
Integer f(Integer n) => n+package.n;

5.1.5. References and block structure

A declaration may be in scope at a program element, but not referenceable at the program element. A declaration is referenceable at a program element if the declaration is in scope at the program element and either:

  • the declaration is imported from a different compilation unit,

  • the program element occurs within the lexical scope of the declaration,

  • the declaration is a parameter and the program element occurs within the extends clause of the declaration it parameterizes, or

  • the declaration does not directly occur in a block, nor in the initializer section of a class body.

Note that these rules have very different consequences for:

Declarations that occurs in a block or class initializer section are interspersed with procedural code that initializes references. Therefore, a program element in a block or initializer may not refer to a declaration that occurs later in the block or class body. This restriction does not apply to declarations that occur in an interface body or class declaration section. Nor does it apply to toplevel declarations, which are not considered to have a well-defined order.

The following toplevel function declarations, belonging to the same package, are legal:

Float x => y;
Float y => x;

This code is not legal, since the body of a function is an ordinary block:

Float->Float xy() {
    Float x => y;  //error: y is not referenceable
    Float y => x;
    return x->y;
}

This code is not legal, since all three statements occur in the initializer section of the class body:

class Point() {
    Float x => y;  //error: y is not referenceable
    Float y => x;
    Float->Float xy = x->y;
}

However, this code is legal, since the statements occur in the declaration section of the class body:

class Point() {
    Float x => y;
    Float y => x;
}

Likewise, this code is legal, since the statements occur in an interface body:

interface Point {
    Float x => y;
    Float y => x;
}

If a declaration is annotation restricted, and a program element does not occur in either:

  • the same package as the declaration, or

  • one of the modules specified as arguments to the restricted annotation of the declaration,

then the declaration is not referenceable at the program element.

5.1.6. Type inference and block structure

A value declared using the keyword value or a function declared using the keyword function may be in scope at a program element, but its type may not be inferable, as defined by §3.2.9 Type inference, from the point of view of that program element.

The type of a value or function declared using the keyword value or function is inferable to a program element if the declaration is in scope at the program element and the program element occurs within the lexical scope of the declaration.

Note: the type of a value or function declared using the keyword value or function is not inferable within the body of the value or function itself.

For any other declaration, including any declaration which explicitly specifies its type, the type is considered inferable to a program element if the declaration is in scope at the program element.

The following code is not legal:

interface Point {
    value x => y;  //error: type of y is not inferable
    value y => x;
}

However, this code is legal:

interface Point {
    value x => y;
    Float y => x;
}

5.1.7. Unqualified reference resolution

An unqualified reference is:

  • the type name in an unqualified type declaration or type argument, as defined by §3.2.7 Type expressions, for example String and Sequence in Sequence<String>,

  • the value, function, constructor, or type name in a base expression, as defined by §6.5.1 Base expressions, for example counter in counter.count, entries and people in entries(people*.name), or Entry, name, and item in Entry(name,item), or

  • the type name in an unqualified type in a static expression, as defined by §6.5.6 Static expressions, or constructor expression, as defined by §6.5.3 Constructor expressions, for example Sequence in Sequence.iterator.

If a program element contains an unqualified reference:

  • there must be at least one declaration in scope at the program element with the given name, or aliased to the given name by an import statement, as defined in §4.2.6 Imported name, and

  • if multiple declarations with the given name or aliased to the given name are in scope at the program element where the given name occurs, then it is guaranteed by the type system and §5.1.1 Declaration name uniqueness that there is exactly one such declaration which is not hidden by any other declaration.

There are two exceptions to the above rules.

  • If the expression or type expression begins with the qualifier keyword package, then there must be a toplevel declaration with the given name defined in the package to which the compilation unit belongs.

  • If the expression or type expression occurs in an annotation list, as defined by §7.1.1 Annotation lists, then there must be a toplevel declaration with the given name defined in the package to which the compilation unit belongs, or imported by a toplevel import statement of the compilation unit.

Then the reference is to this unique unhidden declaration, and:

  • the declaration must be referenceable at the program element,

  • the type of the declaration must be inferable to the program element, and

  • if the declaration is forward-declared, it must be definitely initialized at the program element.

As a special exception to the above, if there is no declaration with the given name or imported name in scope at the program element and the program element occurs inside a dynamic block, then the unqualified reference does not refer to any statically typed declaration.

If an unqualified reference refers to a member declaration of a type, then there is a unique inheriting or declaring class or interface for the unqualified reference, that is, the unique class or interface in whose body the unqualified reference occurs, and which declares or inherits the member declaration, and for which the member is not hidden at the program element where the unqualified reference occurs.

5.1.8. Qualified reference resolution

A qualified reference is:

  • the type name in a qualified type declaration or type argument, as defined by §3.2.7 Type expressions, for example Buffer in BufferedReader.Buffer,

  • the value, function, or type name in a member expression, as defined by §6.5.2 Member expressions, for example count in counter.count, split in text.split(), or Buffer in br.Buffer(),

  • the constructor name in a constructor expression, as defined by §6.5.3 Constructor expressions, or

  • the type name in a qualified type in a static expression, as defined by §6.5.6 Static expressions, for example Buffer in BufferedReader.Buffer.size, or the member name in a static expression, for example iterator in Sequence.iterator, or size in BufferedReader.Buffer.size.

Every qualified reference has a qualifying type:

  • For a type declaration, the qualifying type is the full qualified type the qualifies the type name.

  • For a value reference or callable reference, the qualifying type is the type of the receiver expression.

  • For a constructor reference, the qualifying type is the type of the qualifying base or member expression.

  • For a static reference, the qualifying type is the full qualified type the qualifies the type or member name.

A qualified reference may not have Nothing as the qualifying type.

If a program element contains a qualified reference:

  • the qualifying type must have or inherit at least one member or nested type with the given name or aliased to the given imported name, as defined in §4.2.6 Imported name, which is visible at the program element, and

  • if there are multiple visible members with the given name or imported name, then it is guaranteed by the type system and §5.1.1 Declaration name uniqueness that there is exactly one such member which is not refined by another member, except

  • if the qualifying type inherits a class or interface that contains the program element, and an un-shared declaration contained directly in the body of this class or interface has the same name as a shared member of the qualifying type, in which case the un-shared declaration hides the shared member, or

  • if the qualifying type is an intersection type, in which case there may be multiple members which are not refined by another member, but where there is exactly one such member that is refined by each of these members, but is not refined by another member that is refined by all of these members, except

  • in the case of certain pathological intersection types, where two of the intersected types declare distinct members with the same name, that do not refine any member of a common supertype (in which case what we actually have are disjoint types that are nevertheless not considered provably disjoint within the rules of the typesystem), and in this case the qualified reference is considered illegal.

Then the reference is to the unique member or nested class. If the program element is contained in the body of a class or interface, and the member declaration directly occurs in the body of the class or interface, and the qualified reference is a value reference or callable reference, and the receiver expression is a self reference to the instance being initialized, then:

  • the member declaration must be referenceable at the program element,

  • the type of the member must be inferable to the program element, and

  • if the member declaration is forward-declared, it must be definitely initialized at the program element.

As a special exception to the above, if the program element occurs inside a dynamic block, and the the receiver expression has no type, then the qualified reference does not refer to any statically typed declaration.

5.2. Patterns and variables

Destructuring statements, assertions, and some control structures allow inline declaration of a variables, which often occur as part of a more complex pattern.

Note: the use of the term variable here does not imply any connection to the variable annotation for values. A variable in a destructuring statement, assertion, or control structure may not be assigned using a specification or assignment statement.

5.2.1. Variables

A variable is a streamlined form of reference declaration, as defined by §4.8.1 References.

TypedVariable: Type MemberName

In most cases, the explicit type be omitted.

Variable: (Type | "value")? MemberName

If the explicit type is missing from the declaration, the type of the variable is inferred, according to rules that depend upon the control structure to which the variable belongs.

A variable declared by a destructuring statement is a reference scoped to the body in which the destructuring statement occurs.

A variable declared by an assertion is a reference scoped to the body in which the assert statement occurs.

A variable declared by a control structure is a reference scoped to the block that immediately follows the variable declaration:

  • For a variable in an if condition, the scope of the variable is the if block.

  • For a variable in a while condition, the scope of the variable is the while block.

  • For a variable in a for iterator, the scope of the variable is the for block.

  • For a variable in a try clause, the scope of the variable is the try block.

  • For a variable in a catch clause, the scope of the variable is the catch block.

  • For a variable in an assert statement, the scope of the variable is the body containing the assert statement.

5.2.2. Patterns

An expression whose type is an instantiation of Sequential, Sequence, Tuple, or Entry may be assigned to a pattern. The type of an expression assigned to a pattern is called the patterned type.

TODO: actually, the following section does not do justice to the compiler, which can actually handle subtypes of these types, including type parameters upper bounded by these types.

Patterns are formed from:

  • pattern variables,

  • tuple patterns, and

  • entry patterns.

Pattern: Variable | TuplePattern | EntryPattern

Note: in a future release of the language, we might introduce a more general pattern matching system, allowing pattern matching against arbitrary classes.

5.2.3. Pattern variables

A pattern variable is just a variable, as defined above, that occurs in a pattern.

If the variable has an explicit type, then the patterned type must be assignable to this type. Otherwise, the type of the variable is inferred to be the patterned type.

A variadic pattern variable is indicated with an asterisk.

VariadicVariable: UnionType? "*" MemberName

Variadic pattern variables only occur in tuple patterns.

5.2.4. Tuple patterns

A tuple pattern comprises a list of element patterns, ending in, optionally, a variadic pattern variable called a variadic element pattern. Tuple patterns are enclosed in brackets.

TuplePattern: "[" (Pattern ",")* (Pattern | VariadicVariable) "]"

The patterned type must be an instantiation of the type Tuple, Sequential, or Sequence in ceylon.language. Then:

  • If the tuple pattern has only one element pattern, and it is variadic, then the patterned type of this variadic element pattern is just the patterned type of the surrounding tuple pattern.

  • Or, if the tuple pattern has only one element pattern, and it is not variadic, then the patterned type must be a single-element instantiation [T] of Tuple, and the patterned type of the element pattern is T.

  • Otherwise, if the patterned type is an instantiation Tuple<T,F,R> of Tuple, then the patterned type of the first element pattern is F, and the patterned types of the remaining element patterns, if any, are determined by forming a new tuple pattern with patterned type R by removing the first element pattern from the list of element patterns.

  • Or, if the patterned type is an instantiation [T+] of Sequence, then there must be exactly two element patterns, and the second element pattern must be variadic. Then the patterned type of the first element pattern is T, and the patterned type of the second element pattern is [T*].

value [x, y, z] = [1.0, 2.0, 0.0];
value [first, *rest] = sequence;

Note: Ceylon does not support parallel assignment statements of form value x, y, z = 1.0, 2.0, 0.0; since the infix = symbol has a higher precedence than the comma , throughout the language.

5.2.5. Entry patterns

An entry pattern comprises a key pattern, followed by an item pattern.

EntryPattern: KeyOrItemPattern "->" KeyOrItemPattern
KeyOrItemPattern: Variable | TuplePattern

The patterned type must be an instantiation K->V of the type Entry in ceylon.language. Then:

  • the patterned type of the key pattern is K, and

  • the patterned type of the item pattern is V.

value name->[lat,long] = observatory;

5.3. Blocks and statements

A block is list of semicolon-delimited statements, control structures, and declarations, surrounded by braces. A block may begin with a list of local import statements, as defined in §4.2 Imports.

Block: "{" Import* (Declaration | Statement)* "}"

A statement is an assignment or specification, an invocation of a method, an instantiation of a class, a destructuring statement, a control structure, a control directive, or an assertion.

Statement: ExpressionStatement | Specification | Destructure | Directive | ControlStructure | Dynamic

A statement or declaration contained in a block may not evaluate a value, invoke a function, instantiate a class, or extend a class whose declaration occurs later in the block.

5.3.1. Expression statements

Only certain expressions are valid statements:

  • assignment,

  • prefix or postfix increment or decrement,

  • invocation of a method,

  • instantiation of a class.

ExpressionStatement: ( Assignment | IncrementOrDecrement | Invocation ) ";"

For example:

x += 1;
x++;
print("Hello");
Main(process.arguments);

5.3.2. Control directives

A control directive statement ends execution of the current block and forces the flow of execution to resume in some outer scope. They may only occur as the lexically last statement of a block.

There are four control directives:

  • the return directive—to return a value from a getter or non-void function or terminate execution of a setter, class initializer, or void method,
  • the break directive—to terminate a loop,
  • the continue directive—to jump to the next iteration of a loop, and
  • the throw directive—to raise an exception.
Directive: (Return | Throw | Break | Continue) ";"

For example:

throw Exception();
return x+y;
break;
continue;

The return directive must sequentially occur in the body of a function, getter, setter, or class initializer. In the case of a setter, class initializer, or void function, no expression may be specified. In the case of a getter or non-void function, an expression must be specified. The expression type must be assignable to the return type of the function or the type of the value. When the directive is executed, the expression is evaluated to determine the return value of the function or getter.

Return: "return" Expression?

If the specified expression has no type, or if the function or getter has no type, and the directive occurs within a dynamic block, then the directive is not type-checked at compile time.

Note: a return statement returns only from the innermost function, getter, setter, or class initializer, even in the case of a nested or anonymous function. There are no "non-local returns" in the language.

The break directive must sequentially occur in the body of a loop.

Break: "break"

The continue directive must sequentially occur in the body of a loop.

Continue: "continue"

A throw directive may appear anywhere and may specify an expression, whose type must be a subtype of type Throwable defined in ceylon.language. When the directive is executed, the expression is evaluated and the resulting exception is thrown. If no expression is specified, the directive is equivalent to throw Exception().

Throw: "throw" Expression?

If the specified expression has no type, and the directive occurs within a dynamic block, then the directive is not type-checked at compile time.

5.3.3. Specification statements

A specification statement may specify or initialize the persistent value of a forward-declared reference, or specify the implementation of a forward-declared getter or function.

Specification: ValueSpecification | LazySpecification

The persistent value of a forward-declared reference or the implementation of a forward-declared function may be specified by a value specification statement. The value specification statement consists of an unqualified value reference, or a qualified value reference where the receiver expression is this, and an ordinary = specifier. The value reference must refer to a declaration which sequentially occurs earlier in the body in which the specification statement occurs.

ValueSpecification: ("this" ".")? MemberName Specifier ";"

The type of the specified expression must be assignable to the type of the reference, or to the callable type of the function.

If the specified expression has no type, or if the reference or function has no type, and the specification occurs within a dynamic block, then the specification is not type-checked at compile time.

String greeting;
if (exists name) {
    greeting = "hello ``name``";
}
else {
    greeting = "hello world";
}
String process(String input);
if (normalize) {
    process = String.normalized;
}
else {
    process = (String s) => s;
}

Note: there is an apparent ambiguity here. Is the statement x=1; a value specification statement, or an assignment expression statement? The language resolves this ambiguity by favoring the interpretation as a specification statement whenever that interpretation is viable. This is a transparent solution, since it accepts strictly more code than the alternative interpretation, and for ambiguous cases the actual semantics are identical between the two interpretations.

The implementation of forward-declared getter or function may be specified using a lazy specification statement. The specification statement consists of either:

  • an unqualified value reference, or a qualified value reference where the receiver expression is `this`, and a lazy => specifier, or

  • a unqualified callable reference, or a qualified value reference where the receiver expression is `this`, one or more parameter lists, and a lazy specifier.

The value reference or callable reference must refer to a declaration which sequentially occurs earlier in the body in which the specification statement occurs.

A callable reference followed by a parameter list is itself considered a callable reference, called a parameterized reference. If the parameter list has type P then the callable reference must have the exact type R(*P) for some type R. Then the type of the parameterized reference is R.

ParameterizedReference: ("this" ".")? MemberName Parameters+

Thus, the specification statement consists of a parameterized reference followed by a lazy specifier.

LazySpecification: (MemberName | ParameterizedReference) LazySpecifier ";"

The type of the specified expression must be assignable to the type of the parameterized reference, or to the type of the value reference.

String greeting;
if (exists name) {
    greeting => "hello ``name``";
}
else {
    greeting => "hello world";
}
String process(String input);
if (normalize) {
    process(String input) => input.normalized;
}
else {
    process(String s) => s;
}

5.3.4. Destructuring statements

A destructuring statement assigns an expression to a pattern, as defined above in §5.2.2 Patterns.

Destructure: "value" (TuplePattern | EntryPattern) Specifier ";"

The type of the specified expression is the patterned type of the tuple or entry pattern.

5.3.5. Dynamic blocks

A dynamic block allows interoperation with dynamically typed native code.

Dynamic: "dynamic" Block

Inside a dynamic block an expression may have no type, as specified in Chapter 6, Expressions.

An expression with no type:

Furthermore:

These situations result in dynamic type checking, as defined in §8.3.6 Dynamic type checking, since the usual static type checks are impossible.

Note: within a dynamic block, Ceylon behaves like a language with optional static typing, performing static type checks where possible, and dynamic type checking where necessary.

5.3.6. Definite return

A sequence of statements may definitely return.

  • A sequence of statements definitely returns if it ends in a return or throw directive, or in a control structure that definitely returns, or contains an assertion with a condition list that is never satisfied.

  • A body definitely returns if it contains a list of statements that definitely returns.

  • An if conditional definitely returns if it has an else block and both the if and else blocks definitely return, or if its condition list is always satisfied and the if block definitely returns, or if its condition list is never satisfied and it has an else block that definitely returns.

  • A switch conditional definitely returns if all case blocks definitely return and the else block, if any, definitely returns.

  • A for loop definitely returns if it has an else block that definitely returns, and there is no break directive in the for block, or if the iterated expression type is a nonempty type, and the for block definitely returns.

  • A while loop definitely returns if its condition list is always satisfied and the while block definitely returns.

  • A try/catch exception manager definitely returns if the try block definitely returns and all catch blocks definitely return or if the finally block definitely returns.

The body of a non-void method or getter must definitely return.

A body may not contain an additional statement, control structure, or declaration following a sequence of statements that definitely returns. Such a statement, control structure, or declaration is considered unreachable.

5.3.7. Definite initialization

A sequence of statements may definitely initialize a forward-declared declaration.

  • A sequence of statements definitely initializes a declaration if one of the statements is a specification statement or assigment expression for the declaration or a control structure that definitely initializes the declaration, or if the sequence of statements ends in a return or throw directive, or contains an assertion with a condition list that is never satisfied.

  • An if conditional definitely initializes a declaration if it has an else block and both the if and else blocks definitely initialize the declaration, of if its condition list is always satisfied and the if block definitely initializes the declaration, of if its condition list is never satisfied and it has an else block that definitely initializes the declaration.

  • A switch conditional definitely initializes a declaration if all case blocks definitely initialize the declaration and the else block, if any, definitely initializes the declaration.

  • A for loop definitely initializes a declaration if it has an else block that definitely initializes the declaration, and there is no break directive in the for block, or if the iterated expression type is a nonempty type, and the for block definitely initializes the declaration.

  • A while loop definitely initializes a declaration if its condition list is always satisfied and the while block definitely initializes the declaration.

  • A try/catch exception manager definitely initializes a declaration if the try block definitely initializes the declaration and all catch blocks definitely initialize the declaration or if the finally block definitely initializes the declaration.

  • A constructor of a class definitely initializes a declaration if the body of the constructor definitely initializes the declaration, or if the constructor delegates to a constructor which definitely initializes the declaration.

  • The constructors of a class definitely initialize a declaration if every non-partial constructor of the class definitely initializes the declaration.

TODO: an assignment expression occurring within a containing expression may or may not definitely initialize a value. Specify this!

If a function or value declaration is referenceable at a certain statement or declaration, it may additionally be considered definitely initialized at that statement or declaration.

If a function declaration is definitely initialized at a certain statement or declaration if it is referenceable at that statement or declaration and:

  • it is a parameter,

  • it is not forward-declared, or

  • it is forward-declared and is definitely initialized by the sequence of statements from its declaration to the given statement or declaration.

As an exception, a member of a class is not considered definitely initialized within the extends clause of the class or of any of its constructors.

If a value declaration is definitely initialized at a certain statement or declaration if it is referenceable at that statement or declaration and:

  • it is a parameter,

  • it is not forward-declared and the given statement or declaration is not the value declaration itself, and does not occur within the body of the value declaration, or

  • it is forward-declared and is definitely initialized by the sequence of statements from its declaration to the given statement or declaration.

A function or value declaration must be definitely initialized wherever any value reference or callable reference to it occurs as an expression within the body in which it is declared.

A shared forward-declared declaration belonging to a class and not annotated late must be definitely initialized:

  • at every return statement of the initializer of the containing class, and

  • at the end of the very last expression statement, directive statement, constructor, or specification statement of the initializer of the containing class.

A specification statement for a method or non-variable reference, getter, or function may not (indirectly) occur in a for or while block unless the declaration itself occurs within the same for or while block.

TODO: Furthermore, the typechecker does some tricky analysis to determine that code like the following can be accepted:

Boolean minors;
for (p in people) {
    if (p.age<18) {
        minors = true;
        break;
    }
}
else {
    minors = false;
}

5.3.8. Definite uninitialization

A sequence of statements may possibly initialize a forward-declared declaration.

  • A sequence of statements possibly initializes a declaration if one of the statements is a specification statement for the declaration or a control structure that possibly initializes the declaration.

  • An if conditional possibly initializes a declaration if either the if block possibly initializes the declaration and the condition list is not never satisfied, or if the else block, if any, possibly initializes the declaration and the condition list is not always satisfied.

  • A switch conditional possibly initializes a declaration if one of the case blocks possibly initializes the declaration or the else block, if any, possibly initializes the declaration.

  • A for loop possibly initializes a declaration if the for block possibly initializes the declaration or if it has an else block that possibly initializes the declaration.

  • A while loop possibly initializes a declaration if the while block possibly initializes the declaration and the condition list is not never satisfied.

  • A try/catch exception manager possibly initializes a declaration if the try block possibly initializes the declaration, if one of the catch blocks possibly initializes the declaration, or if the finally block possibly initializes the declaration.

  • A constructor of a class possibly initializes a declaration if the body of the constructor possibly initializes the declaration, or if the constructor delegates to a constructor which possibly initializes the declaration.

  • The constructors of a class possibly initialize a declaration if at least one constructor of the class possibly initializes the declaration.

A forward-declared declaration is considered definitely uninitialized at a certain statement or declaration if:

  • it is not possibly initialized by the sequence of statements from its declaration to the given statement or declaration,

  • the statement does not (indirectly) occur in the for block or else block of a for loop with a for block that possibly initializes it,

  • the statement does not (indirectly) occur in the while block of a while loop with a while block that possibly initializes it,

  • the statement does not (indirectly) occur in a catch block of a try/catch exception manager with a try block that possibly initializes it, and

  • the statement does not (indirectly) occur in the finally block of a try/catch exception manager with a try block or catch block that possibly initializes it.

A function or non-variable value declaration must be definitely uninitialized wherever any value reference or callable reference to it occurs as a specification statement within the body in which it is declared.

5.4. Conditions

Assertions and certain control structures have a condition list. A condition list has one or more conditions.

ConditionList: "(" Condition ("," Condition)* ")"

Any condition in the list may refer to a variable defined in a condition that occurs earlier in the list.

A condition list is considered to be always satisfied if every condition in the list is always satisfied. A condition list is considered to be never satisfied if some condition in the list is never satisfied.

There are four kinds of condition:

  • a boolean condition is satisfied when a boolean expression evaluates to true,

  • an assignabilty condition is satisfied when an expression evaluates to an instance of a specified type,

  • an existence condition is satisfied when an expression evaluates to a non-null value, and

  • a nonemptiness condition is satisfied when an expression evaluates to a non-null, non-empty value.

Condition: BooleanCondition | IsCondition | ExistsOrNonemptyCondition

TODO: are we going to support satisfies conditions on type parameters, for example, if (Element satisfies Object), to allow refinement of its upper bounds?

5.4.1. Boolean conditions

A boolean condition is just an expression.

BooleanCondition: Expression

The expression must be of type Boolean.

A boolean condition is considered to be always satisfied if it is a value reference to true. A boolean condition is considered to be never satisfied if it is a value reference to false.

TODO: Should we do some more sophisticated static analysis to determine if a condition is always/never satisfied?

5.4.2. Assignability conditions

An assignability condition may contain either:

  • an unqualified value reference to a non-variable, non-default reference, or

  • an inline variable declaration together with an expression.

IsCondition: "!"? "is" (TypedVariable Specifier | Type MemberName)

A negated assignability condition is one which starts with !.

Note: the prefix form is Type val reads a little unnaturally in English. But for a condition with a specifier, the form is Type val = expression is much less ambiguous than val = expression is Type, which looks like an assignment of a boolean value.

TODO: are we going to allow is Type this and is Type outer to narrow the type of a self reference?

The type of the value reference or expression in an assignability condition must be:

  • in the case of a condition which is not negated, a type which is not a subtype of the specified type, but whose intersection with the specified type is not exactly Nothing, except

  • in the case of a negated condition, a type whose intersection with the specified type is not exactly Nothing, and which is not a supertype of the specified type, or

Note: an assignability condition may narrow to an intersection or union type.

if (is Printable&Identifiable obj) { ... }
if (is Integer|Float num) { ... }

For an assignability condition with a conditional expression of type T and specified type X:

  • if the condition contains a value reference, the value will be treated by the compiler as having type T&X inside the block or expression that immediately follows the condition, and, if this is the only condition in the condition list, as having type T~X inside the following else block or expression if any, unless

  • it is a !is negated assignability condition, in which case the value will be treated by the compiler as having type T~X inside the block or expression that immediately follows the condition, and, if this is the only condition in the condition list, as having type T&X inside the following else block or expression if any.

Where, for any given types T and X, the type T~X is determined as follows:

  • if X covers T, as defined by §3.4.1 Coverage, then T~X is Nothing,

  • if T is an intersection type, then T~X is the intersection of all U~X for every type U in the intersection,

  • if T is a union type, then T~X is the union of all U~X for every type U in the union,

  • if T is a type parameter, then T~X is T&<U~X> when U is the intersection of all upper bounds on T, or Anything if T has no declared upper bounds,

  • if T is an enumerated type or an instantiation of a generic enumerated type, then T~X is the union of all C~X for every case C of T, or,

  • otherwise, T~X is T.

If you prefer, you can think of the following:

Transaction tx = ...
if (is Usable tx) { ... }

As an abbreviation of:

if (is Transaction&Usable tx = tx) { ... }

Where the tx declared by the condition hides the outer declaration of tx inside the block that follows.

As a special exception to the above, if a condition occurs in a dynamic block, and the conditional expression has no type, and the condition contains a value reference, then:

  • if the condition is not negated, the value will be treated by the compiler as having type X where X is the specified type, inside the block or expression that immediately follows the condition, or, otherwise

  • if the condition is negated, the value will be treated by the compiler as having no type.

5.4.3. Existence and nonemptiness conditions

An existence or nonemptiness condition may contain either:

  • an unqualified value reference to a non-variable, non-default reference, or

  • a pattern together with an expression.

ExistsOrNonemptyCondition: "!"? ("exists" | "nonempty") (Pattern Specifier | MemberName)

A negated condition is one which starts with !.

The type of the value reference or expression must be:

  • in the case of an existence condition or negated existence condition, a type whose intersection with Null is not exactly Nothing and whose intersection with Object is not exactly Nothing, or

  • in the case of a nonemptiness condition or a negated nonemptiness condition, a subtype of Anything[]? whose intersection with [] is not exactly Nothing, and whose intersection with [Nothing+] is not exactly Nothing.

Every existence or nonemptiness condition is equivalent to—and may be considered an abbreviation of—an assignability condition:

  • exists x is equivalent to is Object x, and

  • !exists x is equivalent to is Null x,

  • nonempty x is equivalent to is [E+] x where x is an expression whose type has the principal instantiation E[]?, and

  • !nonempty x is equivalent to is [] x.

For an existence condition which is not negated:

  • if the condition has a pattern, the patterned type is T&Object, where the specifier expression is of type T, and, if the pattern is a pattern variable, the declared type of the variable, if any, must be a subtype of Object, or

  • if the condition contains a value reference, the value will be treated by the compiler as having type T&Object inside the block or expression that immediately follows the condition, where the conditional expression is of type T, and, if this is the only condition in the condition list, as having the type T&Null inside the following else block or expression if any.

For a negated existence condition:

  • if the condition has a pattern, it must be a pattern variable, and the patterned type is T&Null, where the specifier expression is of type T, and the declared type of the variable, if any, must be Null, or

  • if the condition contains a value reference, the value will be treated by the compiler as having type T&Null inside the block or expression that immediately follows the condition, where the conditional expression is of type T, and, if this is the only condition in the condition list, as having the type T&Object inside the following else block or expression if any.

For a nonemptiness condition which is not negated:

  • if the condition has a pattern, the patterned type is T&[E+], where the specifier expression is of type T and T has the principal instantiation E[]?, and, if the pattern is a pattern variable, the declared type of the variable, if any, must be a subtype of [Anything+], or

  • if the condition contains a value reference, the value will be treated by the compiler as having type T&[E+] inside the block or expression that immediately follows the condition, where the conditional expression is of type T and T has the principal instantiation E[]?, and, if this is the only condition in the condition list, as having the type T&[] inside the following else block or expression if any.

For a negated nonemptiness condition:

  • if the condition has a pattern, it must be a pattern variable, and the patterned type is T&[], where the specifier expression is of type T and T has the principal instantiation E[]?, and the declared type of the variable, if any, must be [], or

  • if the condition contains a value reference, the value will be treated by the compiler as having type T&[] inside the block or expression that immediately follows the condition, where the conditional expression is of type T and T has the principal instantiation E[]?, and, if this is the only condition in the condition list, as having the type T&[E+] inside the following else block or expression if any.

If you prefer, you can think of the following:

if (exists name) { ... }

As an abbreviation of:

if (exists String name = name) { ... }

Where the name declared by the condition hides the outer declaration of name inside the block that follows.

As a special exception to the above, if a condition occurs in a dynamic block, and the conditional expression has no type, and the condition contains a value reference, then the value will be treated by the compiler as having no type.

5.4.4. Case conditions

The branches of a switch conditional each belong to a case condition. There are three kinds of case condition:

  • a value case—a list of constant values,

  • a type case—an assignability condition of form is V for some type V, and

  • a pattern case—a tuple or entry pattern, as defined above in §5.2.2 Patterns, where all pattern variables declare explicit types.

CaseCondition: "(" (ValueCase | TypeCase | PatternCase) ")"
ValueCase: Constant ("|" Constant)*
TypeCase: "is" Type
PatternCase: TuplePattern | EntryPattern

A constant value is:

  • a value reference, as defined in §6.5.4 Value references, to

    • a toplevel anonymous class, or

    • a value constructor of a toplevel class that is a subtype of Identifiable,

  • a string literal, character literal, integer literal, or negated integer literal, or

  • a tuple enumeration, as defined in §6.6.12 Iterable and tuple enumeration, of constant values.

Constant: BaseExpression | ConstructorExpression | LiteralConstant | TupleConstant
LiteralConstant: "-"? IntegerLiteral | CharacterLiteral | StringLiteral | VerbatimStringLiteral
TupleConstant: "[" (Constant (", " Constant))? "]"

Every case condition has a type:

  • for a value case, the type is the union of the types of the values,

  • for a type case, the type is the specified type, and

  • for a pattern case, the type is formed from the explicit types declared by the pattern variables:

    • the type of a tuple pattern with one element pattern is [T], where T is the type of its element pattern,

    • the type of a tuple pattern with more than one element pattern is Tuple<T,F,R>, where F is the type of its first element pattern, R is the type of the tuple pattern formed by removing its first element pattern, and T is the union of the types of all its element patterns,

    • the type of an entry pattern is K->V where K is the type of its key pattern, and K is the type of its item pattern, and

    • the type of a pattern variable is its explicitly declared type.

Note: to each value constructor, the compiler assigns an internal type which is a subtype of the type of the class to which the constructor belongs. The union of all internal value constructor types for value constructors listed in the of clause of the class declaration covers the class type.

For a case of type U of a switch with switched type V, as defined below in §5.5.2 switch/case/else:

  • the intersection type V&U must not be exactly Nothing, and

  • if the case is a type case, the switch variable, or, if there is no inline variable declared by the switch, the value referred by the switch expression, will be treated by the compiler as having the type V&U inside the case block.

As a special exception to the above, if a switch occurs in a dynamic block, and there is no switched type, the switch variable, or the value referred by the switch expression will be treated by the compiler as having the type V inside the case block.

Note: a type case may narrow to an intersection or union type.

case (is Persistent & Serializable) { ... }
case (is Integer | Float) { ... }

5.5. Control structures and assertions

Control of execution flow may be achieved using control directives and control structures. Control structures include conditionals, loops, and exception management.

Ceylon provides the following control structures:

  • the if/else conditional—for controlling execution based on a boolean condition, type condition, or check for a non-null or non-empty value,

  • the switch/case/else conditional—for controlling execution using an enumerated list of values or types,

  • the while loop—for loops which terminate based on a boolean condition, type condition, or check for a non-null or non-empty value,

  • the for/else loop—for looping over elements of an iterable object, and

  • the try/catch/finally exception manager—for managing exceptions and controlling the lifecycle of objects which require explicit destruction.

ControlStructure: IfElse | SwitchCaseElse | While | ForFail | TryCatchFinally | Assertion

Control structures are not considered to be expressions, and therefore do not evaluate to a value. However, comprehensions, specified in §6.6.6 Comprehensions, and conditional expressions, specified in §6.7 Conditional expressions, let expressions, and anonymous class expressions are part of the expression syntax and share much of the syntax and semantics of the control structures they resemble.

Assertions are runtime checks upon program invariants, or function preconditions and postconditions. An assertion failure represents a bug in the program, and is not considered recoverable. Therefore, assertions should not be used to control "normal" execution flow.

Note: of course, in certain circumstances, it is appropriate to handle the exception that results from an assertion failure, for example, to display a message to the user, or in a testing framework to aggregate and report the failures that occurred in test assertions. A test failure may be considered "normal" occurrence from the point of view of a testing framework, but it's not "normal" in the sense intended above.

5.5.1. if/else

The if/else conditional has the following form:

IfElse: If Else?
If: "if" ConditionList Block
Else: "else" (Block | IfElse)

Every if/else conditional construct has an if clause. The construct may optionally include:

  • a chain of an arbitrary number of child else if clauses, and/or

  • an else clause.

if (payment.amount <= account.balance) {
    account.balance -= payment.amount;
    payment.paid = true;
}
else {
    throw NotEnoughMoneyException();
}
shared void welcome(User? user) {
    if (exists user) {
        print("Welcome back, ``user.name``!");
    }
    else {
        print("Welcome to Ceylon!");
    }
}
if (is CardPayment p = order.payment, 
        !p.paid) {
    p.card.charge(total);
}

5.5.2. switch/case/else

The switch/case/else conditional has the following form:

SwitchCaseElse: Switch Case+ Else?

Every switch conditional has a switch clause.

Switch: "switch" "(" SwitchVariableOrExpression ")"

The switch clause has a switched expression, either:

  • an expression, or

  • an inline variable declaration together with a specified expression.

SwitchVariableOrExpression: Expression | Variable Specifier

The switched type is the type of the expression or inline variable.

Note: there is an ambiguity here between assignment expressions and inline variable declarations. This ambiguity is resolved in favor of interpreting the switched expression as a variable declaration. Therefore, a switched expression in a switch clause may not be an assignment expression.

If a switch has a type case condition, and does not declare an inline variable, then the switched expression must be an unqualified value reference to a non-variable, non-default reference.

In addition, every switch conditional must include:

  • a chain of one or more child case and else case clauses, and,

  • optionally, a chain of an arbitrary number of child else if clauses, and/or

  • optionally, an else clause.

Case: "else"? "case" CaseCondition Block

Two cases are said to be disjoint if:

  • the intersection of the types of their case conditions is exactly Nothing, as defined by §3.4.4 Disjoint types, or

  • if they are both value cases with no literal value or anonymous class value reference in common.

In every switch statement, every pair of cases must be disjoint, unless one is an else case.

A switch is exhaustive if there are no literal values in its cases, and the union type formed by the types of the case conditions of the switch covers the switched type, as defined by §3.4.1 Coverage. If no else block is specified, the switch must be exhaustive.

Note: On the other hand, even if the switch is exhaustive, an else block may be specified, in order to allow a switch that accommodates additional cases without resulting in a compilation error.

As a special exception to the above, if a switch occurs in a dynamic block, and the switched expression has no type, the cases are not statically type-checked for exhaustion.

If an else block is specified, then the switch variable or, if there is no inline variable declared by the switch, the value referred by the switch expression, will be treated by the compiler as having the type V~U inside the else block, where V is the switched type, and U is the union type formed by the types of the case conditions of the switch.

Boolean? maybe = ... ;
switch (maybe) 
case (null | false) {
    return false;
}
case (true) { 
    return true;
}
Integer|Float number = ... ;
switch (number)
case (is Integer) { 
    return sqrt(number.float);
} 
case (is Float) { 
    return sqrt(number);
}

5.5.3. for/else

The for/else loop has the following form:

ForFail: For Fail?
For: "for" ForIterator Block
Fail: "else" Block

Every for/else conditional construct has an for clause. The construct may optionally include an else clause, as specified in §8.3.4 Execution of loops.

The for iterator has an iterator pattern and an iterated expression that contains the range of values to be iterated.

ForIterator: "(" Pattern "in" Expression ")"

The type of the iterated expression must have some principal supertype instantiation {T*} or {T+} of Iterable in ceylon.language. Then the patterned type of the iterator pattern is T.

As a special exception to the above, if a for occurs in a dynamic block, and the iterated expression has no type, the iterator is not statically type-checked. If the iteration variable does not declare an explicit type, the iteration variable has no type.

for (p in people) { 
    print(p.name);
}
variable Float sum = 0.0;
for (i in -10..10) {
    sum += x[i] else 0.0;
}
for (word -> freq in wordFrequencyMap) { 
    print("The frequency of ``word`` is ``freq``."); 
}
for (p in group) {
    if (p.age >= 18) {
        log.info("Found an adult: ``p.name``.");
        break;
    }
}
else {
    log.info("No adult in group.");
}

5.5.4. while

The while loop has the form:

While: LoopCondition Block

The loop condition list determines when the loop terminates.

LoopCondition: "while" ConditionList

TODO: does while need an else block? Python has it, but what is the real usecase?

variable Integer n=0;
variable [Integer*] seq = [];
while (n<=max) {
    seq=seq.withTrailing(n);
    n+=step(n);
}

5.5.5. try/catch/finally

The try/catch/finally exception manager has the form:

TryCatchFinally: Try Catch* Finally?
Try: "try" ResourceList? Block
ResourceList: "(" Resource ("," Resource)* ")"
Catch: "catch" "(" Variable ")" Block
Finally: "finally" Block

Every try conditional construct has a try clause. The construct may optionally include:

Each catch block defines a variable. The type of the variable must be assignable to Throwable in ceylon.language. If no type is explicitly specified, the type is inferred to be Exception.

Note: a catch block type may be a union or intersection type:

catch (NotFoundException|DeletedException e) { ... }

If there are multiple catch blocks in a certain control structure, then:

  • The type of a catch variable may not be a subtype of any catch variable of an earlier catch block belonging to the same control structure.

  • If the type of a catch variable is a union type E1|E2|...|En then no member Ei of the union may be a subtype of any catch variable of an earlier catch block belonging to the same control structure.

The try block may have a list of resource expressions, each of which may produce either:

  • a destroyable resource, or

  • an obtainable resource.

Resource: Expression | Variable Specifier

Note: there is an ambiguity here between assignment expressions and inline variable declarations. This ambiguity is resolved in favor of interpreting the resource expression as a variable declaration. Therefore, a resource expression in a try clause may not be an assignment expression.

A destroyable resource expression is:

  • an instantiation expression, as defined in §6.6.1 Direct invocations, or

  • an inline variable declaration together with an instantiation expression.

The instantiation expression must be of type assignable to Destroyable in ceylon.language.

An obtainable resource expression is:

  • an expression, or

  • an inline variable declaration together with an expression.

The expression must be of type assignable to Obtainable in ceylon.language.

If no type is explicitly specified for a resource variable, the type of the variable is inferred to be the type of the expression.

try (File(path).lock) {
    file.open(write);
    ...
}
catch (FileNotFoundException fnfe) {
    print("file not found: ``path``");
}
catch (FileReadException fre) {
    print("could not read from file: ``path``");
}
finally {
    assert (file.closed);
}
try (Transaction(), s = Session()) {
    return s.get(Person, id);
}
catch (NotFoundException|DeletedException e) {
    return null;
}

5.5.6. Assertions

An assertion has an asserted condition list and, optionally, a failure message.

Assertion: AssertionMessage? "assert" ConditionList ";"
AssertionMessage: StringLiteral | VerbatimStringLiteral | StringTemplate

The message carried by the assertion failure may be specified using a string literal or interpolated string template, as defined in §6.2 String templates.

"total must be less than well-defined bound"
assert (exists bound, total<bound);

If the assertion contains an assignability, existence, or nonemptiness condition containing a value reference then the compiler treats the referenced value as having a narrowed type at program elements that occur in the lexical scope of the assertion.

{Element*} elements = ... ;
assert (nonempty elements);
Element first = elements.first;

TODO: how can we support interpolation in the assertion failure message?

assert (total<bound) 
else "total must be less than ``bound``";