Chapter 4. Declarations

Ceylon is a statically typed language. Classes, interfaces, functions, values and aliases must be declared before use. The declaration of a function or value must include an explicit type, or allow the type to be inferred. Static typing allows the compiler to detect many errors, including:

All declarations follow a general pattern:

Annotations
(keyword | Type) (TypeName | MemberName) TypeParameters? Parameters*
CaseTypes? ExtendedType? SatisfiedTypes?
TypeConstraints?
(Definition | ";")

A type parameter does not need an explicit declaration of this form unless it has constraints. In the case that it does have constraints, the constraint declaration does follow the general pattern.

This consistent pattern for declarations, together with the strict block structure of the language, makes Ceylon a highly regular language.

4.1. Compilation unit structure

A compilation unit is a text file, with the filename extension .ceylon.

Note: it is recommended that source file names contain only characters from the ASCII character set. This minimizes problems when transferring Ceylon source between operating systems.

There are three kinds of compilation unit:

  • A regular compilation unit contains a list of toplevel type, value, or function definitions.

  • A module descriptor, defined in §9.3.12 Module descriptors, contains a module declaration. The file must be named module.ceylon.

  • A package descriptor, defined in §9.3.11 Package descriptors, contains a package declaration. The file must be named package.ceylon.

Any compilation unit may begin with a list of imported types, values, and functions.

Import* (ModuleDescriptor | PackageDescriptor | Declaration*)

4.1.1. Toplevel and nested declarations

A toplevel declaration defines a type—a class or interface—or a type alias, or a function or value.

Declaration: FunctionValueDeclaration | TypeDeclaration | ParameterDeclaration
FunctionValueDeclaration: FunctionDeclaration | ValueDeclaration | SetterDeclaration
TypeDeclaration: ClassDeclaration | ObjectDeclaration | InterfaceDeclaration | TypeAliasDeclaration

A toplevel declaration is not polymorphic and so may not be annotated formal, default, or actual.

Note: in a future release of the language, we might relax this restriction and support package extension with toplevel member refinement. This can be viewed as a regularization of the language. The practical application is that it would make toplevel invocations and instantiations polymorphic, obviating the need for things like dependency injection.

Most toplevel declarations contain nested declarations.

Nested declarations are often mixed together with executable statements.

4.1.2. Packages

Each compilation unit belongs to exactly one package. Every toplevel declaration of the compilation unit also belongs directly to this package. The package is identified by the location of the text file on the file system, relative to a root source directory, as defined in §9.2 Source layout.

A package is a namespace. A full package name is a period-separated list of all-lowercase identifiers.

FullPackageName: PackageName ("." PackageName)*

Note: it is recommended that package names contain only characters from the ASCII character set.

There is also a default package whose name is empty. It is impossible to import declarations from this package.

Every package belongs to exactly one module, as specified in §9.3 Module architecture. The default package belongs to the default module.

4.2. Imports

Code in one compilation unit may refer to a toplevel declaration in another compilation unit in the same package without explicitly importing the declaration. It may refer to a declaration defined in a compilation unit in another package only if it explicitly imports the declaration using the import statement.

Import: "import" FullPackageName "{" ImportElements "}"

For a given package, there may be at most one import statement per compilation unit.

An import statement may import from a package if and only if:

  • the package belongs to the same module as the compilation unit containing the import statement, as specified by §9.2 Source layout, or

  • the package is declared shared in its package descriptor, and the module descriptor of the module to which the compilation unit containing the import statement belongs, as specified by §9.2 Source layout, explicitly or implicitly imports the module containing the package, as defined by §9.3.12 Module descriptors.

Each import statement imports one or more toplevel declarations from the given package, specifying a list of import elements.

ImportElements: (ImportElement ",")* (ImportElement | ImportWildcard)?
ImportElement: ImportTypeElement | ImportFunctionValueElement

An import element is a reference to either:

  • a single toplevel type (a class, interface, or alias) of the package,

  • a single toplevel function or value of the package, or

  • all toplevel declarations of the package.

An import element may not refer to a declaration that is not visible to the compilation unit, as defined by §5.1.3 Visibility.

An import statement may not contain two import elements which refer to the same declaration.

Note that toplevel declarations in the package ceylon.language never need to be explicitly imported. They are implicitly imported by every compilation unit.

An imported function or value may not hide, as defined in §5.1.4 Hidden declarations, any of the modifiers declared in ceylon.language listed in §7.4.1 Declaration modifiers, unless the modifier itself has an alias import in the compilation unit.

Note: an unused import results in a compiler warning.

4.2.1. Type imports

An import element that specifies a type name imports the toplevel type with that name from the given package.

ImportTypeElement: TypeAlias? TypeName ("{" ImportElements? "}")?

A compilation unit may not import two types with the same name.

import java.util { Set, List, Map }

The import element may be followed by a list of nested import elements, which must specify aliases.

Note: as a special exception to the usual language rules, to support interoperation with Java, a nested import element which references a static member of a Java type results in a Ceylon toplevel reference to the static member. In this case, the import element may omit the explicit alias.

4.2.2. Function and value imports

An import element that specifies a function or value name imports the toplevel function or value with that name from the given package.

ImportFunctionValueElement: FunctionValueAlias? MemberName

A compilation unit may not import two methods or attributes with the same name.

import ceylon.math { sqr, sqrt, e, pi }

4.2.3. Alias imports

The optional alias clause in a fully-explicit import allows resolution of cross-namespace declaration name collisions.

TypeAlias: TypeName "="
FunctionValueAlias: MemberName "="

An alias assigns a different name to the imported declaration, or to a member of the imported declaration. This name is visible within the compilation unit in which the import statement occurs.

import java.util { JavaMap = Map }
import my.math { fib = fibonnacciNumber }
import java.lang { 
    Math { sin, cos, ln=log }, 
    System { sysprops=properties },
    Char=Character { upper=toUpperCase, lower=toLowerCase, char=charValue } 
}

4.2.4. Wildcard imports

The elipsis ... acts as a wildcard in import statements. An import statement that specifies a wildcard imports every toplevel declaration of the imported package, except for any declaration whose name collides with the name of a toplevel declaration in the compilation unit in which the import statement appears.

ImportWildcard: "..."

An import statement may specify a list of alias imports followed by a wildcard. In this case, the alias imports are imported with the specified names, and all other toplevel declarations are imported with their declared names.

import ceylon.collection { ... }
import my.math { fib = fibonnacciNumber, ... }

Note: overuse of wildcard imports is discouraged.

4.2.5. Imported name

Inside a compilation unit which imports a declaration, the declaration may be referred to, as specified in §5.1.7 Unqualified reference resolution and §5.1.8 Qualified reference resolution, by its imported name:

  • For an import element with an alias, the imported name is the alias.

  • For an import element with no alias, or for a wildcard import, the imported name is the original name of the declaration.

An import element may not result in an imported name that is the same as the name of a toplevel declaration contained in the compilation unit in which the import element occurs.

Two import elements occurring in the same compilation unit may not result in the same imported name.

4.3. Parameters

A function or class declaration may declare parameters. A parameter is a value or function belonging to the declaration it parameterizes. Parameters are distinguished from other values or functions because they occur in a parameter list. A value or function is a parameter of a class or function if it is:

  • declared inline in a parameter list of the class or function, or

  • declared normally, within the body of the class or function, but named in a parameter list of the class or function.

The following class definitions are semantically identical:

class Person(shared String name, shared variable Integer age=0, Address* addresses) {}
class Person(name, age=0, addresses) {
    shared String name;
    shared variable Integer age;
    Address* addresses;
}

A parameter declaration may only occur in a parameter list, or directly, as defined by §5.1 Block structure and references, in the body of a class or function. A parameter declaration may not occur directly in the body of a getter or in a body of a control structure. Nor may a parameter declaration appear as a toplevel declaration in a compilation unit.

ParameterDeclaration: ValueParameter | CallableParameter | VariadicParameter

Every parameter declaration that occurs outside a parameter list must be named in the parameter list of the class or function in whose body it directly occurs, and its default argument, if any, must be specified in the parameter list.

4.3.1. Parameter lists

A parameter list is a list of parameter declarations and of names of parameters declared in the body of the class or function to which the parameter list belongs. A parameter list may include, optionally:

  • one or more required parameters,

  • one or more defaulted parameters (parameters with default values), and/or

  • a variadic parameter.

In a parameter list, defaulted parameters, if any, must occur after required parameters, if any. The variadic parameter, if any, must occur last.

Parameters: "(" ( (Required ",")* ( Required | (Defaulted ",")* (Defaulted | Variadic) ) )? ")"

Every parameter list has a type, which captures the types of the individual parameters in the list, whether they are defaulted, and whether the last parameter is variadic. This type is always an subtype of Anything[]. The type of an empty parameter list with no parameters is [].

A parameter may not be annotated formal, but it may be annotated default.

4.3.2. Required parameters

A required parameter is a value or callable parameter without a default argument.

A required parameter in a parameter list may be a parameter declaration, or the name of a non-variadic parameter declared in the body of the function or class.

Required: ValueParameter | CallableParameter | MemberName

Required parameters must occur before any other parameters in the parameter list.

4.3.3. Defaulted parameters

A defaulted parameter is a value or callable parameter that specifies an expression that produces a default argument. A defaulted parameter may be either:

  • a non-variadic parameter declaration, together with a default argument expression, or

  • the name of a non-variadic parameter declared in the body of the function or class, together with its default argument expression.

Defaulted: ValueParameter Specifier | CallableParameter LazySpecifier | MemberName Specifier

The = and => specifiers are used throughout the language. In a parameter list they are used to specify a default argument.

Specifier: "=" Expression
LazySpecifier: "=>" Expression

The default argument expression may involve other parameters declared earlier in the parameter list or lists. It may not involve parameters declared later in the parameter list or lists.

The default argument expression may not involve an assignment, compound assignment, increment, or decrement operator.

Defaulted parameters must occur after required parameters in the parameter list.

(Product product, Integer quantity=1, Price pricing(Product p) => p.price)

A parameter of a method or class annotated actual may not specify a default argument. Instead, it inherits the default argument, if any, of the corresponding parameter of the method it refines.

If two parameter lists are almost identical, differing only in that the first parameter of one list is defaulted, and the first parameter of the second list is required, and P is the the type of the second parameter list, then the type of the first parameter list is []|P.

Note: in Ceylon 1.0, for a function with multiple parameter lists, defaulted parameters may only occur in the first parameter list. This restriction will be removed.

TODO: Should we, purely for consistency, let you write f(Float x) => x in a parameter list, when the callable parameter is declared in the body of the function or class?

4.3.4. Value parameters

A value parameter is a reference, as specified in §4.8.1 References, that is named or defined in a parameter list. Like any other value declaration, it has a name, type, and, optionally, annotations.

ValueParameter: Annotations (Type | "dynamic") MemberName

A value parameter may be declared using the keyword dynamic in place of the parameter type. Such a parameter has no type.

If a value parameter x has type X, and a parameter list has type P with the principal instantiation Sequential<Y>, then the type of a new parameter list formed by prepending x to the first parameter list is:

  • Tuple<X|Y,X,P>, or

  • []|Tuple<X|Y,X,P> if x is defaulted.

The default argument expression, if any, for a callable parameter is specified using an ordinary = specifier. The type of the default argument expression must be assignable to the declared type of the value parameter.

(String label, Anything() onClick)
({Value*} values, Comparison(Value,Value) by)

4.3.5. Callable parameters

A callable parameter is a function, as specified in §4.7 Functions, named or defined in a parameter list. Like any other function declaration, it has a name, type, one or more parameter lists, and, optionally, annotations.

CallableParameter: Annotations (Type | "void") MemberName Parameters+

If a callable parameter f has callable type Callable<X,A>, as specified below in §4.7.1 Callable type of a function, and a parameter list has type P with the principal instantiation Sequential<Y>, then the type of a new parameter list formed by prepending f to the first parameter list is:

  • Tuple<Y|Callable<X,A>,Callable<X,A>,P>, or

  • []|Tuple<Y|Callable<X,A>,Callable<X,A>,P> if f is defaulted.

The default argument expression, if any, for a callable parameter is specified using a lazy => specifier. The type of the default argument expression must be assignable to the return type of the callable parameter.

(String label, void onClick())
({Value*} values, Comparison by(Value x, Value y))

4.3.6. Variadic parameters

A variadic parameter is a value parameter that accepts multiple arguments:

  • A variadic parameter declared T* accepts zero or more arguments of type T, and has type [T*].

  • A variadic parameter declared T+ accepts one or more arguments of type T, and has type [T+].

VariadicType: UnionType ("*" | "+")
VariadicParameter: Annotations VariadicType MemberName

A variadic parameter in a parameter list may be a variadic parameter declaration, or the name of a variadic parameter declared in the body of the function or class.

Variadic: VariadicParameter | MemberName

The variadic parameter must be the last parameter in a parameter list. A variadic parameter may not have a default argument. A variadic parameter declared T+ may not occur in a parameter list with defaulted parameters.

(Name name, Organization? org=null, Address* addresses)
(Float+ floats)

The type of a parameter list containing just a variadic parameter of type T* is [T*] The type of a parameter list containing just a variadic parameter of type T+ is [T+].

Note: in Ceylon 1.0, for a function with multiple parameter lists, a variadic parameters may only occur in the first parameter list. This restriction will be removed.

4.4. Interfaces

An interface is a type schema, together with implementation details for some members of the type. Interfaces may not be directly instantiated.

InterfaceDeclaration: Annotations InterfaceHeader (InterfaceBody | TypeSpecifier ";")

An interface declaration may optionally specify a list of type parameters. An interface declaration may also have a list of interfaces is satisfies, a self type or an enumerated list of cases, and/or a list of type constraints.

InterfaceHeader: "interface" TypeName TypeParameters? InterfaceInheritance TypeConstraints?
InterfaceInheritance: CaseTypes? SatisfiedTypes?

To obtain a concrete instance of an interface, it is necessary to define and instantiate a class that satisfies the interface, or define an anonymous class that satisfies the interface.

The body of an interface contains:

  • member (method, attribute, and member class) declarations, and

  • nested interface, type alias, and abstract class declarations.

InterfaceBody: "{" Declaration* "}"

Unlike the body of a class, method, or attribute, the body of an interface is not executable, and does not directly contain procedural code.

shared interface Comparable<Other> {
    shared formal Comparison compare(Other other);
    shared Boolean largerThan(Other other) => compare(other)==larger;
    shared Boolean smallerThan(Other other) => compare(other)==smaller;
}

An interface may declare formal methods, attributes, and member classes, and concrete methods, getters, setters, and member classes. A reference declaration, as defined in §4.8.1 References, or anonymous class declaration, as defined in §4.5.7 Anonymous classes, may not directly occur in the body of an interface.

A non-abstract nested class declaration is called a member class of the interface. A nested interface or abstract class declaration is not part of the schema of the interface type, and is therefore not considered a member of the interface.

4.4.1. Interface bodies

The body of an interface consists purely of declarations. The following constructs may not occur sequentially in the body of an interface:

  • a statement or control structure,

  • a reference declaration,

  • a forward-declared method or attribute declaration, or

  • an object declaration.

Within an interface body, a super reference is any occurrence of the expression super, unless it also occurs in the body of a nested class or interface declaration. A statement or declaration contained in the interface body may not:

  • pass a super reference as an argument of an instantiation, method invocation, or extends clause expression or as the value of a value assignment or specification,

  • use a super reference as an operand of any operator except the member selection operator, or the of operator as specified in §6.3.3 super,

  • return a super reference, or

  • narrow the type of a super reference using the if (is ...) construct or case (is ...).

4.4.2. Interface inheritance

An interface may satisfy any number of other interfaces.

shared interface List<Element>
        satisfies Collection<Element> & Correspondence<Integer,Element>
        given Element satisfies Object {
    ...
}

Every type listed in the satisfies clause must be an interface. An interface may not satisfy the same interface twice (not even with distinct type arguments).

Note: this second restriction is not strictly necessary. In fact, satisfies List<One>&List<Two> means the same thing as satisfies List<One&Two>, and the compiler already needs to be able to figure that out when it comes to multiple instantiations of the same interface inherited indirectly. Still, the restriction seems harmless enough.

The interface is a subtype of every type listed in the satisfies clause. The interface is also a subtype of the type Object defined in ceylon.language.

An interface inherits all members (methods, attributes and member types) of every supertype. That is, every member of every supertype of the interface is also a member of the interface. Furthermore, the interface inherits all nested types (interfaces and abstract classes) of every supertype.

The schema of the inherited members is formed by substituting type arguments specified in the satisfies clause.

An interface that satisfies a nested interface must be a member of the type that declares the nested interface or of a subtype of the type that declares the nested interface.

A user-defined interface may not satisfy the interface Callable defined in ceylon.language.

4.4.3. Interfaces with enumerated cases

An interface declaration may enumerate a list of cases of the interface.

shared interface Node<Element> 
            of Root<Element> | Branch<Element> | Leaf<Element> { ... }

The cases may be interfaces, classes, or toplevel anonymous classes. A case may be an abstract class. Each case must be a subtype of the interface type. An interface may not be a case of itself. An interface declaration may not list the same case twice.

If an interface has an of clause, then every interface or class which is a subtype of the interface must be subtype of exactly one of the enumerated cases.

4.4.4. Interface aliases

An interface declaration which specifies a reference to another interface type defines an interface alias of the specified interface type.

TypeSpecifier: "=>" Type

The specified type must be an interface type, that is, a reference to an interface with no type parameters, or an instantiation of a generic interface. An interface alias simply assigns an alternative name to the original interface type. A reference to the alias may occur anywhere a reference to an interface may occur.

shared interface PeopleByName => Map<String,Person>;
interface Compare<Value> => Comparison(Value,Value);

If the aliased interface is a parameterized type, the aliased type must explicitly specify type arguments.

A class or interface may satisfy an interface alias, in which case, the class or interface inherits the aliased interface type.

Interface aliases are not reified types. The metamodel reference for an interface alias type—for example, PeopleByName—returns the metamodel object for the aliased interface—in this case, Map<String,Person>, as specified in §8.1.2 Type argument reification.

4.5. Classes

A class is a stateful, instantiable type. It is a type schema, together with implementation details of the members of the type.

ClassDeclaration: Annotations ClassHeader (ClassBody | ClassSpecifier ";")

An ordinary class declaration specifies a list of parameters required to instantiate the type, and, optionally a list of type parameters. A class declaration may have a superclass, a list of interfaces it satisfies, a self type or an enumerated list of cases, and/or a list of type constraints.

ClassHeader: "class" TypeName TypeParameters? Parameters ClassInheritance TypeConstraints?
ClassInheritance: CaseTypes? ExtendedType? SatisfiedTypes?

To obtain an instance of a class, it is necessary to instantiate the class, or a subclass of the class.

The body of a class contains:

  • member (method, attribute, and member class) declarations,

  • nested interface, type alias, and abstract class declarations, and

  • instance initialization code.

ClassBody: "{" (Declaration | Statement)* "}"

The body of a class may contain executable code.

shared class Counter(Integer initialCount=0) {
    
    variable Integer n = initialCount;
    
    print("Initial count: ``n``");
    
    shared Integer count => n;
    
    shared void increment() {
        n++;
        print("Count: ``n``");
    }
    
}

A non-abstract nested class declaration is called a member class of the class. A nested interface or abstract class declaration is not part of the schema of the class type, and is therefore not considered a member of the class.

Ceylon classes do not have seperate nested constructor declarations. Instead, the body of the class declares initializer parameters. An initializer parameter may be used anywhere in the class body, including in method and attribute definitions.

shared class Key(Lock lock) {
    shared void lock() {
        lock.engage(this);
        print("Locked.");
    }
    shared void unlock() {
        lock.disengage(this);
        print("Unlocked.");
    }
    shared Boolean locked => lock.engaged;
}

An initializer parameter may be shared.

shared class Point(shared Float x, shared Float y) { ... }
shared class Counter(count=0) {
    shared variable Integer count;
    shared void increment() => count++;
}

4.5.1. Callable type of a class

The callable type of a class captures the type and parameter types of the class. The callable type is Callable<T,P>, where T is the class and P is the type of the initializer parameter list of the class.

An abstract class is not callable, except from the extends clause of a subclass, or the class specifier of a class alias.

4.5.2. Initializer section

The initial part of the body of a class is called the initializer of the class and contains a mix of declarations, statements and control structures. The initializer is executed every time the class is instantiated.

A class initializer is responsible for initializing the state of the new instance of the class, before a reference to the new instance is available to clients.

shared abstract class Point() {
    shared formal Float x;
    shared formal Float y;
}
shared class DiagonalPoint(Float distance) 
        extends Point() {
    
    value d = distance / 2^0.5;
    x => d;
    y => d;
    
    "must have correct distance from origin" 
    assert (x^2 + y^2 == distance^2);
    
}
shared object origin 
        extends Point() {
    x => 0.0;
    y => 0.0;
}

Within a class initializer, a self reference to the instance being initialized is:

  • any occurrence of the expression this or super, unless it also occurs in the body of a nested class or interface declaration, or

  • any occurrence of the expression outer in the body of a class or interface declaration immediately contained by the class.

A statement or declaration contained in the initializer of a class may not evaluate an attribute, invoke a method, or instantiate a member class upon the instance being initialized, including upon a self reference to the instance being initialized, if the attribute, method, or member class:

  • occurs later in the body of the class,

  • is annotated formal or default, or

  • is inherited from an interface or superclass, and is not refined by a declaration occurring earlier in the body of the class.

A member class contained in the initializer of a class may not extend a member or nested class of an interface or superclass of the class.

Furthermore, a statement or declaration contained in the initializer of a class may not:

  • pass a self reference to the instance being initialized as an argument of an instantiation, method invocation, or extends clause expression or as the value of a value assignment or specification,

  • use a self reference to the instance being initialized as an operand of any operator except the member selection operator, or the of operator,

  • return a self reference to the instance being initialized, or

  • attempt to narrow the type of a self reference to the instance being initialized using the if (is ...) construct or case (is ...).

Nor may the class pass a self reference to the instance being initialized as an argument of its own extends clause expression, if any.

As a special exception to these rules, a statement contained in an initializer may assign a self-reference to the instance being initialized to a reference annotated late.

For example, the following code fragments are not legal:

class Graph() {
    OpenList<Node> nodes = ArrayList<Node>();
    class Node() {
        nodes.add(this);    //compiler error (this reference in initializer)
    }
}
class Graph() {
    class Node() {}
    Node createNode() {
        Node node = Node();
        nodes.add(node);    //compiler error (forward reference in initializer)
        return node;
    }
    OpenList<Node> nodes = ArrayList<Node>();
}

But this code fragment is legal:

class Graph() {
    OpenList<Node> nodes = ArrayList<Node>();
    Node createNode() {
        Node node = Node();
        nodes.add(node);
        return node;
    }
    class Node() {}
}

4.5.3. Declaration section

The remainder of the body of the class consists purely of declarations, similar to the body of an interface. The following constructs may not occur sequentially in the declaration section:

  • a statement or control structure,

  • a reference declaration,

  • a forward-declared method or attribute declaration not annotated late,

  • an object declaration with a non-empty initializer section, or

  • an object declaration that directly extends a class other than Object or Basic in ceylon.language.

However, the declarations in this second section may freely use this and super, and may invoke any method, evaluate any attribute, or instantiate any member class of the class or its superclasses.

Within the declaration section of a class body, a super reference is any occurrence of the expression super, unless it also occurs in the body of a nested class or interface declaration. A statement or declaration contained in the declaration section of a class body may not:

  • pass a super reference as an argument of an instantiation, method invocation, or extends clause expression or as the value of a value assignment or specification,

  • use a super reference as an operand of any operator except the member selection operator, or the of operator as specified in §6.3.3 super,

  • return a super reference, or

  • narrow the type of a super reference using the if (is ...) construct or case (is ...).

4.5.4. Class inheritance

A class may extend another class.

shared class Customer(Name name, Organization? org = null) 
        extends Person(name, org) { 
    ... 
}

The class is a subtype of the type specified by the extends clause. If a class does not explicitly specify a superclass using extends, its superclass is the class Basic defined in ceylon.language.

A class may satisfy any number of interfaces.

class Token() 
        extends Datetime()
        satisfies Comparable<Token> & Identifier {
    ... 
}

The class is a subtype of every type listed in the satisfies clause. A class may not satisfy the same interface twice (not even with distinct type arguments).

A class inherits all members (methods, attributes, and member types) of every supertype. That is, every member of every supertype of the class is also a member of the class. Furthermore, the class inherits all nested types (interfaces and abstract classes) of every supertype.

Unless the class is declared abstract or formal, the class:

  • must declare or inherit a member that refines each formal member of every interface it satisfies directly or indirectly, and

  • must declare or inherit a member that refines each formal member of its superclass.

The schema of the inherited members is formed by substituting type arguments specified in the extends or satisfies clause.

A subclass must pass values to each superclass initialization parameter in the extends clause.

shared class SpecialKey1()
        extends Key( SpecialLock() ) {
    ...
}
shared class SpecialKey2(Lock lock) 
        extends Key(lock) {
    ...
}

A subclass of a nested class must be a member of the type that declares the nested class or of a subtype of the type that declares the nested class. A class that satisfies a nested interface must be a member of the type that declares the nested interface or of a subtype of the type that declares the nested interface.

A user-defined class may not satisfy the interface Callable defined in ceylon.language.

4.5.5. Abstract, final, formal, and default classes

A toplevel or nested class may be annotated abstract and is called an abstract class.

A toplevel or nested class may be annotated final and is called an final class.

If a class annotated shared is a member of a containing class or interface, then the class may be annotated formal and is called a formal member class, or, sometimes, an abstract member class.

An abstract class or formal member class may have formal members.

An abstract class may not be instantiated.

A formal member class may be instantiated.

A class which is not annotated formal or abstract is called a concrete class.

A concrete class may not have formal members.

A class annotated final must be a concrete class.

A class annotated final may not have default members.

If a concrete class annotated shared is a member of a containing class or interface, then the class may be annotated default and is called a default member class.

A toplevel class may not be annotated formal or default.

An un-shared class may not be annotated formal or default.

Note: a formal member class would be a reasonably syntax for declaring virtual types. We think we don't need virtual types because they don't offer much that type parameters don't already provide. For example:

shared formal class Buffer(Character...) 
        satisfies Sequence<Character>;

4.5.6. Member class refinement

Member class refinement is a unique feature of Ceylon, akin to the "factory method" pattern of many other languages.

  • A member class annotated formal or default may be refined by any class or interface which is a subtype of the class or interface which declares the member class.

  • A member class annotated formal must be refined by every concrete class which is a subtype of the class or interface that declares the member class, unless the class inherits a concrete member class from a superclass that refines the formal member class.

A member class of a subtype refines a member class of a supertype if the member class of the supertype is shared and the two classes have the same name. The first class is called the refining class, and the second class is called the refined class.

Then, given the refined realization of the class it refines, as defined in §3.7.6 Realizations, and, after substituting the type parameters of the refined class for the type parameters of the refining class in the schema of the refining class, the refining class must:

  • have the same number of type parameters as the refined schema, and for each type parameter the intersection of its upper bounds must be a supertype of the intersection of the upper bounds of the corresponding type parameter of the realization,

  • have a parameter list with the same signature as the realization, and

  • directly or indirectly extend the class it refines.

Furthermore:

  • the refining class must be annotated actual, and

  • the refined class must be annotated formal or default.

If a member class is annotated actual, it must refine some member class of a supertype.

A member class may not, directly or indirectly, refine two different member classes not themselves annotated actual.

Then instantiation of the member class is polymorphic, and the actual subtype instantiated depends upon the concrete type of the containing class instance.

shared abstract class Reader() {
    shared formal class Buffer(Character* chars) 
            satisfies Character[] {}
    ...
}
shared class FileReader(File file) 
        extends Reader() {
    shared actual class Buffer(Character* chars) 
            extends Reader.Buffer(chars) {
        ...
    }
    ...
}

All of the above rules apply equally to member classes which are aliases.

shared abstract class Reader() {
    shared formal class Buffer(Character* chars) => AbstractBuffer(*chars);
    ...
}
shared class FileReader(File file) 
        extends Reader() {
    shared actual class Buffer(Character* chars) => FileBuffer(*chars);
    ...
}

4.5.7. Anonymous classes

An object declaration makes it possible to define a class, instantiate the class, and declare an attribute referring to the resulting class instance in a single declaration.

ObjectDeclaration: Annotations ObjectHeader ClassBody

An object has an initial lowercase identifier. An object declaration does not specify parameters or type parameters.

ObjectHeader: "object" MemberName ObjectInheritance
ObjectInheritance: ExtendedType? SatisfiedTypes?

An object declaration specifies the name of the attribute and the schema, supertypes, and implementation of the class. It does not specify a type name. Instead, the type has a name assigned internally by the compiler that is not available at compilation time.

An object class:

  • is implicitly final,

  • may not be extended by another class,

  • may not be abstract or formal, and

  • may not declare default members.

If the object is annotated shared, the class is shared.

This class never appears in types inferred by local declaration type inference or generic type argument inference. Instead, occurrences of the class are replaced with the intersection of the extended type with all satisfied types.

An object attribute:

  • is non-variable, and

  • may not be refined or declared default.

If the object is annotated shared, the attribute is shared. If the object is annotated actual, it refines an attribute of a supertype.

The following declaration:

shared object red extends Color('FF0000') {
     string => "Red";
}

Is exactly equivalent to:

shared final class \Ired() extends Color('FF0000') {
     string => "Red";
}

shared \Ired red = \Ired();

Where \Ired is a name generated by the compiler. The algorithm for generating this name is not specified here.

Note that a member of an anonymous class that is not annotated actual may only be accessed from within the body of the anonymous class or by directly invoking the object attribute.

shared object sql {
    shared String escape(String string) { ... }
}

...

String escapedSearchString = sql.escape(searchString);

4.5.8. Classes with enumerated cases

A class declaration may enumerate a list of cases of the class.

shared abstract class Boolean() 
        of true | false {}
        
shared object true extends Boolean() { string => "true"; }
shared object false extends Boolean() { string => "false"; }
shared abstract class Node<Element>(String name) 
        of Branch<Element> | Leaf<Element> { ... }
        
shared class Leaf<Element>(String name, Element element) 
        extends Node<Element>(name) { ... }
        
shared class Branch<Element>(String name, Node<Element> left, Node<Element> right) 
        extends Node<Element>(name) { ... }

The cases may be classes or toplevel anonymous classes. A case may be an abstract class. Each case must be a subclass of the class. A class may not be a case of itself. A class declaration may not list the same case twice.

If a class has an of clause, then every class that directly extends the class must be of one of the enumerated cases of the class.

A non-abstract class may not have an of clause.

Note: in a future release of the language, we will introduce an abbreviated syntax like:

shared abstract class Boolean(shared actual String string) 
        of object true ("true") | 
           object false ("false") {}

4.5.9. Class aliases

A class declaration which specifies a reference to another class type defines a class alias of the specified class type.

ClassSpecifier: "=>" ("super" ".")? TypeNameWithArguments PositionalArguments

The specified type must be a class type, that is, a reference to a class with no type parameters, or an instantiation of a generic class. A class alias simply assigns an alternative name to the original class type. A reference to the alias may occur anywhere a reference to a class may occur.

shared class People(Person* people) => ArrayList<Person>(*people);
class Named<Value>(String name, Value val) => Entry<String,Value>(name, val);

Arguments to the initializer parameters of the aliased class must be specified.

If the aliased class is a parameterized type, the aliased type must explicitly specify type arguments.

The type arguments may not be inferred from the initializer arguments.

Note: currently the compiler imposes a restriction that the callable type of the aliased class must be assignable to the callable type of the class alias. This restriction will be removed in future.

If a toplevel class alias or un-shared class alias aliases an abstract class, the alias must be annotated abstract, and it may not be directly instantiated.

If a shared class alias nested inside the body of a class or interface aliases an abstract class, the alias must be annotated abstract or formal. If it is annotated formal, it is considered a member class of the containing class or interface. If it is annotated abstract, it is considered an abstract nested class of the containing class or interface.

A class or interface may extend a class alias, in which case, the class inherits the aliased class type.

Class aliases are not reified types. The metamodel reference for a class alias type—for example, People—returns the metamodel object for the aliased class—in this case, ArrayList<Person>, as specified in §8.1.2 Type argument reification.

4.6. Type aliases

A type alias declaration assigns a name to an arbitrary type expression, usually involving a union and/or intersection of types.

TypeAliasDeclaration: Annotations AliasHeader TypeSpecifier
AliasHeader: "alias" TypeName TypeParameters? TypeConstraints?

The specified type may be any kind of type. A reference to the alias may be used anywhere a union or intersection type may be used. The alias may not appear in an extends or satisfies clause. The alias may not be instantiated.

shared alias Number => Integer|Float|Decimal|Whole;
alias ListLike<Value> => List<Value>|Map<Integer,Value>;
alias Numbered<Num,Value> given Num satisfies Ordinal<Num> 
        => Correspondence<Num,Value>;

Note: class, interface, and type aliases use a "fat arrow" lazy specifier => instead of = because the type parameters declared on the left of the specifier are in scope on the right of the specifier. An alias is in general a type constructor.

A class or interface may not extend or satisfy a type alias.

Type aliases are not reified types. The metamodel reference for a type alias type—for example, Number—returns the metamodel object for the aliased type—in this case, Integer|Float|Decimal|Whole, as specified in §8.1.2 Type argument reification.

4.7. Functions

A function is a callable block of code. A function may have parameters and may return a value. If a function belongs to a type, it is called a method.

FunctionDeclaration: Annotations FunctionHeader (Block | LazySpecifier? ";")

All function declarations specify the function name, one or more parameter lists, and, optionally, a list of type parameters. A function declaration may specify a type, called the return type, to which the values the method returns are assignable, or it may specify that the function is a void function—a function which does not return a useful value, and only useful for its effect. A generic function declaration may have a list of type constraints.

FunctionHeader: (Type | "function" | "dynamic" | "void") MemberName TypeParameters? Parameters+ TypeConstraints?

A function may be declared using the keyword dynamic in place of its return type. Such a function has no return type.

A function implementation may be specified using either:

  • a block of code, or

  • a lazy specifier.

If a function is a parameter, it must not specify any implementation.

The return type of a void function is considered to be Anything defined in ceylon.language.

Note: a void function with a concrete implementation returns the value null. However, since a void function may be a reference to a non-void function, or a method refined by a non-void function, this behavior can not be depended upon and is not implied by the semantics of void.

4.7.1. Callable type of a function

The callable type of a function captures the return type and parameter types of the function.

  • The callable type of a function with a single parameter list is Callable<R,P> where R is the return type of the method, or Anything if the function is void, and P is the type of the parameter list.

  • The callable type of a function with multiple parameter lists is Callable<O,P>, where O is the callable type of a method produced by eliminating the first parameter list, and P is the type of the first parameter list of the function.

Note: the identification of void with Anything instead of Null or some other unit type will probably be contraversial. This approach allows a non-void method to refine a void method or a non-void function to be assigned to a void functional parameter. Thus, we avoid rejecting perfectly well-typed code.

4.7.2. Functions with blocks

A function implementation may be a block.

  • If the function is declared void, the block may not contain a return directive that specifies an expression.

  • Otherwise, every conditional execution path of the block must end in a return directive that specifies an expression assignable to the return type of the function, or in a throw directive, as specified in §5.2.4 Definite return.

shared Integer add(Integer x, Integer y) {
    return x + y;
}
shared void printAll(Object* objects) {
    for (obj in objects) {
        print(obj);
    }
}
shared void addEntry(Key->Item entry) {
    map.put(entry.key,entry.item);
}
shared Set<Element> singleton<Element>(Element element) 
        given Element satisfies Comparable<Element> {
    return TreeSet { element };
}

4.7.3. Functions with specifiers

Alternatively, a function implementation may be a lazy specifier, that is, an expression specified using =>. The type of the specified expression must be assignable to the return type of the function. In the case of a function declared void, the expression must be a legal statement.

shared Integer add(Integer x, Integer y) => x + y;
shared void addEntry(Key->Item entry) => map.put(entry.key,entry.item);
shared Set<Element> singleton<Element>(Element element) 
            given Element satisfies Comparable<Element>
        => TreeSet { element };

4.7.4. Function return type inference

A non-void, un-shared function with a block or lazy specifier may be declared using the keyword function in place of the explicit return type declaration. Then the function return type is inferred:

  • if the function implementation is a lazy specifier, then the return type of the function is the type of the specified expression,

  • if the function implementation is a block, and the function contains no return directive, then the return type of the method is Bottom (this is the case where the method always terminates in a throw directive), or,

  • otherwise, the return type of the function is the union of all returned expression types of return directives of the method body.

This function has inferred return type Integer.

function add(Integer x, Integer y) => x + y;

This function has inferred return type Float|Integer.

function unit(Boolean floating) {
    if (floating) {
        return 1.0;
    }
    else {
        return 1;
    }
}

This function has inferred return type Bottom.

function die() {
    throw;
}

4.7.5. Forward declaration of functions

The declaration of a function may be separated from the specification of its implementation. If a function declaration does not have a lazy specifier, or a block, and is not annotated formal, and is not a parameter, it is a forward-declared function.

A forward-declared function may later be specified using a specification statement, as defined in §5.2.3 Specification statements. The specification statement for a forward-declared function may be:

  • a lazy specification statement with parameter lists of exactly the same types as the function, and a specified expression assignable to the declared type of the function, or

  • an ordinary specification statement with a specified expression assignable to the callable type of the function.

Comparison order(String x, String y);
if (reverseOrder) {
    order(String x, String y) => y<=>x;
}
else {
    order(String x, String y) => x<=>y;
}
Comparison format(Integer x);
switch (base)
case (decimal) {
    format = (Integer i) => i.string; 
}
case (binary) {
    format = formatBin;
}
case (hexadecimal) {
    format = formatHex;
}

Every forward-declared function must explicitly specify a type. It may not be declared using the keyword function.

A toplevel function may not be forward-declared. A method of an interface may not be forward-declared.

If a shared method is forward-declared, its implementation must be definitely specified by all conditional paths in the class initializer.

4.7.6. Functions with multiple parameter lists

A function may declare multiple lists of parameters. A function with more than one parameter list returns instances of Callable in ceylon.language when invoked. Every function with multiple parameter lists is exactly equivalent to a function with a single parameter list that returns an anonymous function.

This function declaration:

Boolean greaterThan<Element>(Element val)(Element element)
        given Element satisfies Comparable<Element> => 
                element>val;

is equivalent to the following:

Boolean(Element) greaterThan<Element>(Element val)
        given Element satisfies Comparable<Element> => 
                (Element element) => element>val;

For a function with n parameter lists, there are n-1 inferred anonymous functions. The ith inferred function:

  • has a callable type formed by eliminating the first i parameter lists of the original declared function,

  • has the i+1th parameter list of the original declared function, and

  • if i<n, returns the i+1th inferred function, or

  • otherwise, if i==n, has the implementation of the original declared function.

Then the original function returns the first inferred anonymous function.

This method declaration:

function fullName(String firstName)(String middleName)(String lastName)
        => firstName + " " + middleName + " " + lastName;

Is equivalent to:

function fullName(String firstName) =>
        (String middleName) =>
                (String lastName) =>
                        firstName + " " + middleName + " " + lastName;

4.7.7. Formal and default methods

If a function declaration does not have a lazy specifier, or a block, and is annotated shared, and is a method of either:

  • an interface, or

  • a class annotated abstract or formal,

then the function declaration may be annotated formal, and is called a formal method, or, sometimes, an abstract method.

shared formal Item? get(Key key);

A method which is not annotated formal is called a concrete method.

If a concrete method is annotated shared, and is a member of a class or interface, then it may be annotated default and is called a default method.

shared default void writeLine(String line) {
    write(line);
    write("\n");
}

A method annotated formal may not specify an implementation (a lazy specifier, or a block).

A method annotated default may specify an implementation (a lazy specifier, or a block), or may be forward-declared.

Every formal method must explicitly specify a type. It may not be declared using the keyword function.

A toplevel method may not be annotated formal or default.

An un-shared method may not be annotated formal or default.

4.7.8. Method refinement

Methods may be refined, just like in other object-oriented languages.

  • A class or interface may refine any formal or default method it inherits, unless it inherits a non-formal non-default method that refines the method.

  • A concrete class must refine every formal method it inherits, unless it inherits a non-formal method that refines the method.

A method of a subtype refines a method of a supertype if the method of the supertype is shared and the two methods have the same name. The first method is called the refining method, and the second method is called the refined method.

Then, given the refined realization of the method it refines, as defined in §3.7.6 Realizations, and, after substituting the type parameters of the refined method for the type parameters of the refining method in the schema of the refining method, the refining method must:

  • have the same number of type parameters as the refined schema, and for each type parameter the intersection of its upper bounds must be a supertype of the intersection of the upper bounds of the corresponding type parameter of the realization,

  • have the same number of parameter lists, with the same signatures, as the realization, and

  • have a return type that is assignable to the return type of the realization, or

  • if it has no return type, the refined method must also have no return type.

Note: in a future release of the language, we would like to support contravariant refinement of method parameter types.

Furthermore:

  • the refining method must be annotated actual, and

  • the refined method must be annotated formal or default.

If a method is annotated actual, it must refine some method defined by a supertype.

A method may not, directly or indirectly, refine two different methods not themselves annotated actual.

Then invocation of the method is polymorphic, and the actual method invoked depends upon the concrete type of the class instance.

shared abstract class AbstractSquareRooter() {
    shared formal Float squareRoot(Float x);
}
class ConcreteSquareRooter()
        extends AbstractSquareRooter() {
    shared actual Float squareRoot(Float x) => x^0.5;
}

Alternatively, a subtype may refine a method using a specification statement, as defined in §5.2.3 Specification statements. The specification statement must satisfy the requirements of §4.7.5 Forward declaration of functions above for specification of a forward-declared function.

class ConcreteSquareRooter()
        extends AbstractSquareRooter() {
    squareRoot(Float x) => x^0.5;
}

4.8. Values

There are two basic kinds of value:

  • A reference defines state. It has a persistent value, determined at the moment it is specified or assigned.

  • A getter defines how a value is evaluated. It is defined using a block or lazy specifier, which is executed every time the value is evaluated. A getter may have a matching setter.

If a value belongs to a type, it is called an attribute.

ValueDeclaration: Annotations ValueHeader (Block | (Specifier | LazySpecifier)? ";")

All value declarations specify the value name. A value declaration may specify a type.

ValueHeader: (Type | "value" | "dynamic") MemberName

A value may be declared using the keyword dynamic in place of its type. Such a value has no type.

Note: syntactically a value declaration looks like a function declaration with zero parameter lists. It is often helpful, in thinking about the syntax and semantics of Ceylon, to take the perspective that a value is a function with zero parameter lists, or, alternatively, that a function is a value of type Callable.

A value may be variable, in which case it may be freely assigned using the assignment and compound assignment operators defined in §6.8 Operators. This is the case for a reference annotated variable, or for a getter with a matching setter.

4.8.1. References

The lifecycle and scope of the persistent value of a reference depends upon where the reference declaration occurs:

  • A toplevel reference represents global state associated with the lifecyle of a module, as defined by §8.2.10 Initialization of toplevel references.

  • A reference declared directly inside the body of a class represents a persistent value associated with every instance of the class, as defined by §8.2.3 Current instance of a class or interface. Repeated evaluation of the attribute of a particular instance of the class produces the same result until the attribute of the instance is assigned a new value.

  • A reference declared inside a block represents state associated with a frame, that is, with a particular execution of the containing block of code, as defined in §8.2.4 Current frame of a block.

The persistent value of a reference may be specified or initialized as part of the declaration of the reference, or via a later specification statement, as defined in §5.2.3 Specification statements, or assignment expression, as defined in §6.8 Operators, or, if it is a parameter, by an argument to an invocation expression, as defined in §6.6 Invocation expressions.

A reference annotated variable has a persistent value that can be assigned multiple times. A reference not annotated variable has a persistent value that can be specified exactly once and not subsequently modified.

variable Integer count = 0;
shared Decimal pi = calculatePi();
shared Integer[] evenDigits = [0,2,4,6,8];

A reference declaration may have a specifier which specifies its persistent value or, in the case of a variable reference, its initial persistent value. The type of the specified expression must be assignable to the type of the reference.

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

If a reference is a parameter, it must not specify a persistent value.

A reference belonging to a class may be annotated late, in which case the initializer of the class is not required to initialize its persistent value. Furthermore, a self-reference to an instance being initialized may be assigned to the reference. If the reference is evaluated before it is initialized, or before its value has been completely initialized, an exception is thrown.

If a class declares or inherits a variable reference, it must (directly or indirectly) extend the class Basic defined in ceylon.language.

4.8.2. Getters

A getter implementation may be a block.

shared Float total {
    variable Float sum = 0.0;
    for (li in lineItems) {
        sum += li.amount;
    }
    return sum;
}

Every conditional execution path of the block must end in a return directive that specifies an expression assignable to the type of the value, or in a throw directive, as specified in §5.2.4 Definite return.

Alternatively, a getter implementation may be a lazy specifier, that is, an expression specified using =>. The type of the specified expression must be assignable to the type of the value.

Name name => Name(firstName, initial, lastName);

4.8.3. Setters

A setter defines how the value of a getter is assigned.

SetterDeclaration: "assign" MemberName (Block | LazySpecifier)

The name specified in a setter declaration must be the name of a matching getter that directly occurs earlier in the body containing the setter declaration. If a getter has a setter, we say that the value is variable.

Within the body of the setter, a value reference to the getter evaluates to the value being assigned.

A setter implementation may be a block. The block may not contain a return directive that specifies an expression.

shared String name { return join(firstName, lastName); }
assign name { firstName=first(name); lastName=last(name); }

Alternatively, a setter implementation may be a lazy specifier. The specified expression must be a legal statement.

shared String name => join(n[0], n[1]);
assign name => n = [first(name), last(name)];

A setter may not be annotated shared, default or actual. The visibility and refinement modifiers of an attribute with a setter are specified by annotating the matching getter.

4.8.4. Value type inference

An un-shared value with a block, specifier, or lazy specifier may be declared using the keyword value in place of the explicit type declaration. Then the value's type is inferred:

  • if the value is a reference with a specifier, then the type of the value is the type of the specified expression,

  • if the value is a getter, and the getter implementation is a lazy specifier, then the type of the value is the type of the specified expression,

  • if the value is a getter, and the getter implementation is a block, and the getter contains no return directive, then the type of the value is Bottom (this is the case where the getter always terminates in a throw directive), or

  • otherwise, the type of the value is the union of all returned expression types of return directives of the getter body.

value names = List<String>();
variable value count = 0;
value name => Name(firstName, initial, lastName);

4.8.5. Forward declaration of values

The declaration of a reference may be separated from the specification or initialization of its persistent value. The declaration of a getter may be separated from the specification of its implementation. If a value declaration does not have a specifier, lazy specifier, or a block, and is not annotated formal, it is a forward-declared value.

A forward-declared value may later be specified using a specification statement, as defined in §5.2.3 Specification statements.

  • The specification statement for a forward-declared getter is a lazy specification statement with no parameter list, and a specified expression assignable to the type of the value.

  • The specification statement for a forward-declared reference is an ordinary specification statement with a specified expression assignable to the type of the value.

String greeting;
switch (language)
case (en) {
    greeting = "Hello";
}
case (es) {
    greeting = "Hola";
}
else {
    throw LanguageNotSupported();
}
print(greeting);

Every forward-declared value must explicitly specify a type. It may not be declared using the keyword value.

A toplevel value may not be forward-declared. An attribute of an interface may not be forward-declared.

A forward-declared getter may not have a setter.

If a shared value is forward-declared, its implementation must be definitely specified by all conditional paths in the class initializer.

4.8.6. Formal and default attributes

If a value declaration does not have a specifier, lazy specifier, or a block, and is annotated shared, and is a member of either:

  • an interface, or

  • a class annotated abstract or formal,

then the value declaration may be annotated formal, and is called a formal attribute, or, sometimes, an abstract attribute.

shared formal variable String firstName;

An attribute which is not annotated formal is called a concrete attribute.

If a concrete attribute is annotated shared, and is a member of a class or interface, then it may be annotated default and is called a default attribute.

shared default String greeting = "Hello";

An attribute annotated formal may not specify an implementation (a specifier, lazy specifier, or a block). Nor may there be a setter for a formal attribute.

An attribute annotated default may specify an implementation (a specifier, lazy specifier, or a block), or may be forward-declared.

Every formal attribute must explicitly specify a type. It may not be declared using the keyword function.

A toplevel attribute may not be annotated formal or default.

An un-shared attribute may not be annotated formal or default.

4.8.7. Attribute refinement

Ceylon allows attributes to be refined, just like methods. This helps eliminate the need for Java-style getter and setter methods.

  • A class or interface may refine any formal or default attribute it inherits, unless it inherits a non-formal non-default attribute that refines the attribute.

  • A concrete class must refine every formal attribute it inherits, unless it inherits a non-formal attribute that refines the attribute.

Any non-variable attribute may be refined by a reference or getter. A variable attribute may be refined by a variable refernce or by a getter and setter pair.

TODO: are you allowed to refine a getter or setter without also refining its matching setter or getter?

An attribute of a subtype refines an attribute of a supertype if the attribute of the supertype is shared and the two attributes have the same name. The first attribute is called the refining attribute, and the second attribute is called the refined attribute.

Then, given the refined realization of the attribute it refines, as defined in §3.7.6 Realizations, the refining attribute must:

  • be variable, if the attribute it refines is variable, and

  • have exactly the same type as the realization, if the attribute it refines is variable,

  • have a type that is assignable to the type of the refined schema, if the attribute it refines is not variable, or

  • if it has no type, the refined attribute must also have no type.

Furthermore:

  • the refining attribute must be annotated actual, and

  • the refined attribute must be annotated formal or default.

If an attribute is annotated actual, it must refine some attribute defined by a supertype.

An attribute may not, directly or indirectly, refine two different attributes not themselves annotated actual.

A non-variable attribute may be refined by a variable attribute.

TODO: Is that really allowed? It could break the superclass. Should we say that you are allowed to do it when you refine an interface attribute, but not when you refine a superclass attribute?

Then evaluation and assignment of the attribute is polymorphic, and the actual attribute evaluated or assigned depends upon the concrete type of the class instance.

shared abstract class AbstractPi() {
    shared formal Float pi;
}
class ConcretePi() 
        extends AbstractPi() {
    shared actual Float pi = calculatePi();
}

Alternatively, a subtype may refine an attribute using a specification statement, as defined in §5.2.3 Specification statements. The specification statement must satisfy the requirements of §4.8.5 Forward declaration of values above for specification of a forward-declared attribute.

class ConcretePi() 
        extends AbstractPi() {
    pi = calculatePi();
}