Google Dart Language Specification III – Interfaces and expressions

< PREV: Google Dart Language Specification II

Google Dart Language Specification III – Interfaces and expressions

[The current version (0.05) was released November 14, 2011. See the specification’s change log (section 1.2) for a list of differences between versions

Interfaces

An interface defines how one may interact with an object. An interface has methods, getters, setters and constructors, and a set of superinterfaces.

interfaceDefinition:
interface identifier typeParameters? superinterfaces?
factorySpecification? ‘{‘ (interfaceMemberDefinition)* ‘}’
;

interfaceMemberDefinition:
static final type? initializedIdentifierList ‘;’
| functionSignature ‘;’
| constantConstructorSignature ‘;’
| namedConstructorSignature ‘;’
| getterSignature ‘;’
| setterSignature ‘;’
| operatorSignature ‘;’
| variableDeclaration ‘;’
;

It is a compile-time error if any default values are specified in the signature of an interface method, getter, setter or constructor.

Methods

An interface method declaration specifies a method signature but no body.

It is a compile-time error if an interface method m1 overrides an interface member m2 and m1 has a different number of required parameters than m2. It is a compile-time error if an interface method m1 overrides an interface member m2 and m1 does not declare all the named parameters declared by m2 in the same order.

It is a static warning if an interface method m1 overrides an interface method m2 and the type of m1 is not a subtype of the type of m2.

Operators

Operators are instance methods with special names. Some, but not all, operators may be defined by user code, as described below.

Getters and Setters

An interface may contain getter and/or setter signatures. These are subject to the same compile-time and static checking rules as getters and setters in classes.

Factories and Constructors

An interface may specify a factory class, which is a class that will be used to provide instances when constructors are invoked via the interface.

factorySpecification:
factory qualified typeParameters?
;

An interface can specify the signatures of constructors that are used to provide objects that conform to the interface. It is a compile-time error if an interface declares a constructor without declaring a factory class.

Let I be an interface named NI with factory class F, and let NF be the name of F. If F implements I then it is a compile-time error if I and F do not have the same number of type parameters.

A constructor kI of interface I with name NI corresponds to a constructor kF of its factory class F with name NF iff either:

  • F does not implement I and kI and kF have the same name, OR
  • F implements I and either
    • kI is named NI and kF is named NF, OR
    • kI is named NI.id and kF is named NF.id.

It is a compile-time error if an interface I declares a constructor kI and there is no constructor kF in the factory class F such that kI corresponds to kF.
Let kI be a constructor declared in an interface I, and let kF be its corresponding constructor. Then:

  • It is a compile-time error if kI and kF do not have the same number of required parameters.
  • It is a compile-time error if kI and kF do not have identically named optional parameters, declared in the same order.
  • It is a compile-time error if kI and kF do not have identical type parameters.
  • It is a static type warning if the type of the nth required formal parameter of kI is not identical to the type of the nth required formal parameter of kF.
  • It is a static type warning if the types of named optional parameters with the same name differ between kI and kF.

If F implements I, and F is generic, then the factory clause of I must include a list of type parameters that is identical to the type parameters given in the type declaration of F, or a compile-time error occurs.

As an example, consider

class HashMapImplementation<K extends Hashable, V> {…}

interface Map<K, V> factory HashMapImplementation<K, V> { … } // illegal

interface Map<K, V> factory HashMapImplementation<K extends Hashable, V> { … }
// legal

But what about:

interface I<T,S> factory F<A,B> …
class F<A,B> implements I<int, String> ..

This conforms to the spec above but makes no sense.

Superinterfaces

An interface has a set of direct superinterfaces. This set consists of the interfaces specified in the extends clause of the interface.

superinterfaces:
extends typeList
;

An interface J is a superinterface of an interface I iff either J is a direct superinterface of I or J is a superinterface of a direct superinterface of I.

It is a compile-time error if the extends clause of an interface I includes a type expression that does not denote a class or interface available in the lexical scope of I.
It is a compile-time error if the extends clause of an interface includes type Dynamic.
It is a compile-time error if an interface is a superinterface of itself.

Inheritance and Overriding

An interface I inherits any members of its superinterfaces that are not overridden by members declared in I.

However, if there are multiple members m1, …, mk with the same name n that would be inherited (because identically named members existed in several superinterfaces) then at most one member is inherited. If the static types T1, …, Tk of the members m1, …, mk are not identical, then there must be a member mx such that Tx <: Ti, 1 <= x <= k for all i, 1 <= i <= k, or a static type warning occurs. The member that is inherited is mx, if it exists; otherwise:

  • If all of m1, …, mk have the same number r of required parameters and the same set of named parameters s, then I has a method named n, with r required parameters of type Dynamic, named parameters s of type Dynamic and return type Dynamic.
  • Otherwise none of the members m1, …, mk is inherited.

The only situation where the runtime would be concerned with this would be during reflection if a mirror attempted to obtain the signature of an interface member.

The current solution is a tad complex, but is robust in the face of type annotation changes. Alternatives: (a) No member is inherited in case of conflict. (b) The first m is selected (based on order of superinterface list) (c) Inherited member chosen at random.

(a) means that the presence of an inherited member of an interface varies depending on type signatures. (b) is sensitive to irrelevant details of the declaration and (c) is liable to give unpredictable results between implementations or even between different compilation sessions.

An interface may override instance members that would otherwise have been inherited from its superinterfaces.

Let I be an interface declared in library L with superinterface S, and let I declare an instance member m, and assume S declares an instance member m’ with the same name as m. Then m overrides m’ iff m is accessibleto L and one of the following holds:

  • m is an instance method.
  • m is a getter and m’ is a getter or a method.
  • m is a setter and m’ is a setter or a method.

Whether an override is legal or not is described elsewhere in this specification.

Generics

A class or interface declaration G may be generic, that is, G may have formal type parameters declared. A generic declaration induces a family of declarations, one for each set of actual type parameters provided in the program.

typeParameter:
identifier (extends type)?
;

typeParameters:
‘<‘ typeParameter (‘,’ typeParameter)* ‘>’
;

A type parameter T may be suffixed with an extends clause that specifies the upper bound for T. If no extends clause is present, the upper bound is Object. It is a static type warning if a type variable is supertype of its upper bound.

The type parameters of a generic declaration G are in scope in the bounds of all of the type parameters of G, in the extends and implements clauses of G (if these exist) and in the non-static members of G.

Because type parameters are in scope in their bounds, we support F-bounded quantification (if you don’t know what that is, don’t ask).

Even where type parameters are in scope there are numerous restrictions at this time:

The normative versions of these are given in the appropriate sections of this specification. Some of these restrictions may be lifted in the future.

Expressions

An expression is a fragment of Dart code that can be evaluated at run time to yield a value, which is always an object. Every expression has an associated static type. Every value has an associated dynamic type.

expression:
assignableExpression assignmentOperator expression
| conditionalExpression
;

expressionList:
expression (‘,’ expression)*
;

primary:
thisExpression
| super assignableSelector
| functionExpression
| literal
| identifier
| newExpression
| constantObjectExpression
| ‘(‘ expression ‘)’
;

Constants

A constant expression is an expression whose value can never change, and that can be evaluated entirely at compile time.

A constant expression is one of the following:

  • A literal number.
  • A literal boolean.
  • A literal string that does not involve string interpolation. It would be tempting to allow string interpolation where the interpolated value is a compile-time constant. However, this would require running the toString() method for constant objects, which could contain arbitrary code.
  • null.
  • A reference to a static final variable or top-level variable.
  • A constant constructor invocation.
  • A constant list literal.
  • A constant map literal.
  • An expression of one of the forms e1 ==e2, e1 != e2, e1 === e2 or e1 !== e2 ,where e1 and e2 are constant expressions that evaluate to a numeric, string or boolean value.
  • An expression of one of the forms e1 && e2 or e1 || e2, where e1 and e2 are constant expressions that evaluate to a boolean value.
  • An expression of one of the forms ~ e, e1 ~/ e2, e1 ^ e2, e1 & e2, e1 | e2, e1 >> e2 or e1 << e2, where e1 and e2 are constant expressions that evaluate to an integer value.
  • An expression of one of the forms e1 + e2, e1 – e2, e1 * e2, e1 / e2, e1 >e2, e1 < e2, e1 >= e2, e1 <= e2 or e1 % e2, where e1 and e2 are constant expressions that evaluate to a numeric value.

It is a compile-time error if the value of a compile-time constant expression depends on itself.

As an example, consider:

class CircularConsts{ // Illegal program – mutually recursive compile-time constants
static final i = j; // a compile-time constant
static final j = i; // a compile-time constant
}

literal:
nullLiteral
| booleanLiteral
| numericLiteral
| stringLiteral
| mapLiteral
| listLiteral
;

Null

The reserved word null denotes the null object.

nullLiteral:
null
;

The null object is the sole instance of the built-in class Null. Attempting to instantiate Null causes a runtime error. It is a compile-time error for a class or interface attempt to extend or implement Null. Invoking a method on null yields a NullPointerException unless the method is explicitly implemented by class Null.

The static type of null is bottom.

The decision to use bottom instead of Null allows null to be be assigned everywhere without complaint by the static checker.

Here is one way in which one might implement class Null:

class Null {
factory Null._() { throw “cannot be instantiated”; }
noSuchMethod(InvocationMirror msg) {
throw new NullPointerException();
}

/* other methods, such as == */
}

Numbers

A numeric literal is either a decimal or hexadecimal integer of arbitrary size, or a decimal double.

numericLiteral:
NUMBER
| HEX_NUMBER
;

NUMBER:
‘+’? DIGIT+ (‘.’ DIGIT+)? EXPONENT?
| ‘+’? ‘.’ DIGIT+ EXPONENT?
;

EXPONENT:
(‘e’ | ‘E’) (‘+’ | ‘-‘)? DIGIT+
;

HEX_NUMBER:
‘0x’ HEX_DIGIT+
| ‘0X’ HEX_DIGIT+
;

HEX_DIGIT:
‘a’..’f’
| ‘A’..’F’
| DIGIT
;

If a numeric literal begins with the prefix ‘0x’, it is a hexadecimal integer literal, which denotes the hexadecimal integer represented by the part of the literal following ‘0x’. Otherwise, if the numeric literal does not include a decimal point denotes an it is a decimal integer literal, which denotes a decimal integer. Otherwise, the numeric literal is a literal double which denotes a 64 bit double precision floating point number as specified by the IEEE 754 standard.

An integer literal or a literal double may optionally be prefixed by a plus sign (+). This ha snos semantic effect.

There is no unary plus operator in Dart. However, we allow a leading plus in decimal numeric literals for clarity and to provide some compatibility with Javascript.

Integers are not restricted to a fixed range. Dart integers are true integers, not 32 bit or 64 bit or any other fixed range representation. Their size is limited only by the memory available to the implementation.

It is a compile-time error for a class or interface to attempt to extend or implement int. It is a compile-time error for a class or interface to attempt to extend or implement double. It is a compile-time error for any type other than the types int and double to attempt to extend or implement num.

An integer literal is either a hexadecimal integer literals or a decimal integer literal.
The static type of an integer literal is int. A literal double is a numeric literal that is not an integer literal. The static type of a literal double is double.

Booleans

The reserved words true and false denote objects that represent the boolean values true and false respectively. They are the boolean literals.

booleanLiteral:
true
| false
;

Both true and false are implement the built-in interface bool. They are the only two instances of bool. It is a compile-time error for a class or interface to attempt to extend or implement bool.

It follows that the two boolean literals are the only two instances of bool.

The static type of a boolean literal is bool.

Boolean Conversion

Boolean conversion maps any object o into a boolean defined as

(bool v){
assert(null != v);
return true === v;
}(o)

Boolean conversion is used as part of control-flow constructs and boolean expressions. Ideally, one would simply insist that control-flow decisions be based exclusively on booleans. This is straightforward in a statically typed setting. In a dynamically typed language, it requires a dynamic check. Sophisticated virtual machines can minimize the penalty involved. Alas, Dart must be compiled into Javascript. Boolean conversion allows this to be done efficiently.

At the same time, this formulation differs radically from Javascript, where most numbers and objects are interpreted as true. Dart’s approach prevents usages such if (a-b) … ; because it does not agree with the low level conventions whereby non-null objects or non-zero numbers are treated as true. Indeed, there is no way to derive true from a non-boolean object via boolean conversion, so this kind of low level hackery is nipped in the bud.

Dart also avoids the strange behaviors that can arise due to the interaction of boolean conversion with autoboxing in Javascript. A notorious example is the situation where false can be interpreted as true. In Javascript, booleans are not objects, and instead are autoboxed into objects where “needed”. If false gets autoboxed into an object, that object can be coerced into true (as it is a non-null object).

Strings

A string is a sequence of valid unicode code points.

stringLiteral:
‘@’? MULTI_LINE_STRING
| SINGLE_LINE_STRING
;

A string can be either a single line string or a multiline string.

SINGLE_LINE_STRING:
‘ ” ‘ STRING_CONTENT_DQ* ‘ ” ‘
| ‘ ‘ ‘ STRING_CONTENT_SQ* ‘ ‘ ‘
| ‘@’ ‘ ‘ ‘ (~( ‘ ‘ ‘ | NEWLINE ))* ‘ ‘ ‘
| ‘@’ ‘ ” ‘ (~( ‘ ” ‘ | NEWLINE ))* ‘ ” ‘
;

A single line string may not span more than one line of source code. A single line string is delimited by either matching single quotes or matching double quotes.

Hence, ‘abc’ and “abc” are both legal strings, as are ‘He said “To be or not to be” did he not?’ and “He said ‘To be or not to be’ didn’t he?”. However “This ‘ is not a valid string, nor is ‘this”.

MULTI_LINE_STRING:
: ‘”””‘ (~ ‘”””‘)* ‘”””‘
| ””’ (~ ””’)* ””’
;

ESCAPE_SEQUENCE:
“\n”
| “\r”
| “\f”
| “\b”
| “\t”
| “\v”
| “\x” HEX_DIGIT HEX_DIGIT
| “\u” HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
| “\u{“ HEX_DIGIT_SEQUENCE “}”
:

HEX_DIGIT_SEQUENCE:
HEX_DIGIT HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? HEX_DIGIT?
;

Multiline strings are delimited by either matching triples of single quotes or matching triples of double quotes.

Strings support escape sequences for special characters. The escapes are:

  • \n for newline, equivalent to \x0A.
  • \r for carriage return, equivalent to \x0D.
  • \f for form feed, equivalent to \x0C.
  • \b for backspace, equivalent to \x08.
  • \t for tab, equivalent to \x09.
  • \v for vertical tab, equivalent to \x0B.
  • \xHEX_DIGIT1 HEX_DIGIT2, equivalent to \u{ HEX_DIGIT1 HEX_DIGIT2}.
  • \uHEX_DIGIT1 HEX_DIGIT2 HEX_DIGIT3 HEX_DIGIT4, equivalent to \u{ HEX_DIGIT1 HEX_DIGIT2 HEX_DIGIT3 HEX_DIGIT4}.
  • \u{HEX_DIGIT_SEQUENCE} is the unicode scalar value represented by the HEX_DIGIT_SEQUENCE. It is a compile-time error if the value of the HEX_DIGIT_SEQUENCE is not a valid unicode scalar value.
  • $ indicating the beginning of an interpolated expression.
  • Otherwise, \k indicates the character k for any k not in {n, r, f, b, t, v, x, u}.

It is a compile-time error if a string literal to contains a character sequence of the form \x that is not followed by a sequence of two hexadecimal digits. It is a compile-time error if a string literal to contains a character sequence of the form \u that is not followed by either a sequence of four hexadecimal digits, or by curly brace delimited sequence of hexadecimal digits.

However, any string may be prefixed with the character @, indicating that it is a raw string, in which case no escapes are recognized.

STRING_CONTENT_DQ:
~( ‘\\’ | ‘ ” ‘ | ‘$’ | NEWLINE )
| ‘\\’ ~( NEWLINE )
| STRING_INTERPOLATION
;

STRING_CONTENT_SQ:
~( ‘\\’ | ‘\” | ‘$’ | NEWLINE )
| ‘\\’ ~( NEWLINE )
| STRING_INTERPOLATION
;

NEWLINE:
‘\n’
| ‘\r’
;

All string literals implement the built-in interface String. It is a compile-time error for a class or interface to attempt to extend or implement String. The static type of a string literal is String.

String Interpolation

It is possible to embed expressions within string literals, such that the these expressions are evaluated, and the resulting values are converted into strings and concatenated with the enclosing string. This process is known as string interpolation.

STRING_INTERPOLATION:
‘$’ IDENTIFIER_NO_DOLLAR
| ‘$’ ‘{‘ Expression ‘}’
;

The reader will note that the expression inside the interpolation could itself include strings, which could again be interpolated recursively.

An unescaped $ character in a string signifies the beginning of an interpolated expression. The $ sign may be followed by either:

  • A single identifier id that must not contain the $ character.
  • An expression e delimited by curly braces.

The form $id is equivalent to the form ${id}. An interpolated string ‘s1${e}s2’ is equivalent to ‘s1’ + e.toString() + ‘s2’. Likewise an interpolated string “s1${e}s2’” is equivalent to “s1” + e.toString() + “s2”, assuming + is the string concatenation operator.

The string interpolation syntax is designed to be familiar and easy to use, if somewhat awkward to parse. The intent is to encourage its use over alternatives such as s1 + s2. In a dynamically typed language, the use of the + operator requires dynamic dispatch. In contrast, in the case of string interpolation we can statically determine that the string concatenation operation is required, making the operation more efficient. Even more importantly, it helps the system to determine if other uses of + are numeric, helping the implementation speed up those operations. This is especially crucial for a language that must be efficiently compiled into Javascript.

Lists

A list literal denotes a list, which is an integer indexed collection of objects.

listLiteral:
const? typeArguments? ‘[‘ (expressionList ‘,’?)? ‘]’
;

A list may contain zero or more objects. The number of elements in a list is its size. A list has an associated set of indices. An empty list has an empty set of indices. A non-empty list has the index set {0 … n -1} where n is the size of the list. It is a runtime error to attempt to access a list using an index that is not a member of its set of indices.

If a list literal begins with the reserved word const, it is a constant list literal and it is computed at compile-time. Otherwise, it is a runtime list literal and it is evaluated at runtime.

It is a compile time error if an element of a constant list literal is not a compile-time constant. It is a compile time error if the type argument of a constant list literal includes a type variable.
The binding of a type variable is not known at compile-time, so we cannot use type variables inside compile-time constants.

The value of a constant list literal const <E>[e1… en] is an object a that implements the built-in interface List<E>. The ith element of a is vi+1, where vi is the value of the compile time expression ei. The value of a constant list literal const [e1… en] is defined as the value of a constant list literal const <Dynamic>[e1… en]. It is a run-time error to attempt to modify a constant list literal.

Let list1 = const <V>[e11… e1n] and list2 = const <U>[e21… e2n] be two constant list literals and let the elements of list1 and list2 evaluate to o11… o1n and o21… o2n respectively. Iff o1i === o2i for 1 <= i <= n and V = U then list1 === list2.

In other words, constant list literals are canonicalized.

A runtime list literal <E>[e1… en] is evaluated as follows:

  • First, the expressions e1… en are evaluated in left to right order, yielding objects o1… on.
  • A fresh instance a that implements the built-in interface List<E> is allocated.
  • The ith element of a is set to oi+1, 0 <= i <= n.
  • The result of the evaluation is a.

Note that this specification does not specify an order in which the elements are set. This allows for parallel assignments into the list if an implementation so desires. The order can only be observed in checked mode: if element i is not a subtype of the element type of the list, a dynamic type error will occur when a[i] is assigned oi-1.

A runtime list literal [e1… en] is evaluated as <Dynamic>[e1… en].

There is no restriction precluding nesting of list literals. It follows from the rules above that
<List<int>>[[1, 2, 3] [4, 5, 6]] is a list with type parameter List<int>, containing two lists with type parameter Dynamic.

The static type of a list literal of the form const <E>[e1… en] or the form <E>[e1… en] is List<E>. The static type a list literal of the form const [e1… en] or the form [e1… en] is List<Dynamic>.

It is tempting to assume that the type of the list literal would be computed based on the types of its elements. However, for mutable lists this may be unwarranted. Even for constant lists, we found this behavior to be problematic. Since compile-time is often actually runtime, the runtime system must be able to perform a complex least upper bound computation to determine a reasonably precise type. It is better to leave this task to a tool in the IDE. It is also much more uniform (and therefore predictable and understandable) to insist that whenever types are unspecified they are assumed to be the unknown type Dynamic.

Maps

A map literal denotes a map from strings to objects.

mapLiteral:
const? typeArguments? ‘{‘ (mapLiteralEntry (‘,’ mapLiteralEntry)* ‘,’?)? ‘}’
;

mapLiteralEntry:
identifier ‘:’ expression
| stringLiteral ‘:’ expression
;

A map literal consists of zero or more entries. Each entry has a key, which is a string, and a value, which is an object. The key of an entry may be specified via an identifier or via a compile-time constant string. If the key is specified via an identifier id, the specification is interpreted as if it was the string ‘id’.

The use of identifiers as keys to literal maps is not implemented at the time of this writing.

If a map literal begins with the reserved word const, it is a constant map literal and it is computed at compile-time. Otherwise, it is a run-time map literal and it is evaluated at run-time.

It is a compile time error if either a key or a value of an entry in a constant map literal is not a compile-time constant. It is a compile time error if either type argument of a constant map literal includes a type variable.

The value of a constant map literal const <V>{k1:e1… kn :en} is an object m that implements the built-in interface Map<String, V>. The entries of m are ui:vi, 1 <= i <= n, where ui is the value of the compile time expression ki and vi is the value of the compile time expression ei. The value of a constant map literal const {k1:e1… kn :en} is defined as the value of a constant map literal const <Dynamic>{k1:e1… kn :en}. It is a run-time error to attempt to modify a constant map literal.

As specified, a typed map literal takes only one type parameter. If we generalize literal maps so they can have keys that are not strings, we would need two parameters. The implementation currently insists on two parameters.

Let map1 = const <V>{k11:e11… k1n :e1n} and map2 = const <U>{k21:e21… k2n :e2n} be two constant map literals. Let the keys of map1 and map2 evaluate to s11… s1n and s21… s2n respectively, and let the elements of map1 and map2 evaluate to o11… o1n and o21… o2n respectively. Iff o1i === o2i and s1i === s2i for 1 <= i <= n, and V = U then map1 === map2.

In other words, constant map literals are canonicalized.

A runtime map literal <V>{k1:e1… kn :en} is evaluated as follows:

  • First, the expressions e1…en are evaluated in left to right order, yielding objects o1… on.
  • A fresh instance m that implements the built-in interface Map<String, V> is allocated.
  • Let ui be the value of the compile-time constant string specified by ki. An entry with key ui and value oi is added to m, 0 <= i <= n.
  • The result of the evaluation is m.

A runtime map literal {k1:e1… kn :en} is evaluated as <Dynamic>{k1:e1… kn :en}.

It is a static warning if the values of any two keys in a map literal are equal.

A map literal is ordered: iterating over the keys and/or values of the maps always happens in the order the keys appeared in the source code.

Of course, if a key repeats, the order is defined by first occurrence, but the value is defined by the last.

The static type of a map literal of the form const <V>{k1:e1… kn :en} or the form <V>{k1:e1… kn :en} is Map<String, V>. The static type a map literal of the form const {k1:e1… kn :en} or the form {k1:e1… kn :en} is Map<String, Dynamic>.

Function Expressions

A function literal is an object that encapsulates an executable unit of code.

functionExpression:
(returnType? identifier)? formalParameterList functionExpressionBody
;

functionExpressionBody:
‘=>’ expression
| block
;

A function literal implements the built-in interface Function.

The static type of a function literal of the form (T1 a1, …, Tn an, [Tn+1 xn+1 = d1, … ,Tn+k xn+k = dk]) => e or the form id(T1 a1, …, Tn an, [Tn+1 xn+1 = d1, …, Tn+k xn+k = dk]) => e is (T1, …, Tn, [Tn+1 xn+1, .., Tn+k xn+k]) →T0, where T0 is the static type of e. In any case where Ti ,1 <= i <= n, is not specified, it is considered to have been specified as Dynamic.

The static type of a function literal of the form T0 id(T1 a1, …, Tn an, [Tn+1 xn+1 = d1, …, Tn+k xn+k = dk]){s} or the form T0 id(T1 a1, …, Tn an, [Tn+1 xn+1 = d1, …, Tn+k xn+k = dk]) => e is (T1, …, Tn, [Tn+1 xn+1, .., Tn+k xn+k]) →T0. In any case where Ti ,1 <= i <= n, is not specified, it is considered to have been specified as Dynamic.

The static type of a function literal of the form id(T1 a1, …, Tn an, [Tn+1 xn+1 = d1,… Tn+k xn+k = dk]){s} or the form (T1 a1, …, Tn an, [Tn+1 xn+1 = d1, …, Tn+k xn+k = dk]) {s} is (T1, …, Tn, [Tn+1 xn+1, .., Tn+k xn+k]) → Dynamic. In any case where Ti ,1 <= i <= n, is not specified, it is considered to have been specified as Dynamic.

This

The reserved word this denotes the target of the current instance member invocation.

thisExpression:
this
;

The static type of this is the interface of the immediately enclosing class.

We do not support self-types at this point.

Instance Creation

Instance creation expressions invoke constructors to produce instances.

It is a static warning to instantiate an abstract class.

It is a compile-time error if any of the type arguments to a constructor of a generic type invoked by a new expression or a constant object expression do not denote types in the enclosing lexical scope. It is a compile-time error if a constructor of a non-generic type invoked by a new expression or a constant object expression is passed any type arguments. It is a compile-time error if a constructor of a generic type with n type parameters invoked by a new expression or a constant object expression is passed m type arguments where m != n.

It is a static type warning if any of the type arguments to a constructor of a generic type invoked by a new expression or a constant object expression are not subtypes of the bounds of the corresponding formal type parameters.

New

The new expression invokes a constructor.

newExpression:

new type (‘.’ identifier)? arguments

;

Let e be a new expression of the form new T.id(a1, .., an, xn+1: an+1, …, xn+k: an+k) or the form new T(a1, .., an, xn+1: an+1, …, xn+k: an+k). It is a compile-time error if T is not a class or interface accessible in the current scope, optionally followed by type arguments.

If e is of the form new T.id(a1, .., an, xn+1: an+1, …, xn+k: an+k) it is a compile-time error if T is not a class or interface accessible in the current scope, optionally followed by type arguments. It is a compile-time error if T.id is not the name of a constructor declared by the type T. If e of the form new T(a1, .., an, xn+1: an+1, …, xn+k: an+k) it is a compile-time error if the type T does not declare a constructor with the same name as the declaration of T.

If T is a parameterized type S<U1, ,.., Um>, let R = S. It is a compile time error if S is not a generic type with m type parameters. If T is not a parameterized type, let R = T.
If R is an interface, let C be the factory class of R. Otherwise let C = R. Furthermore, if e is of the form new T.id(a1, .., an, xn+1: an+1, …, xn+k: an+k) then let q be the constructor of C that corresponds to the constructor T.id, otherwise let q be the constructor of C that corresponds to the constructor T. Finally, if C is generic but T is not a parameterized type, then for 1 <= i <= m, let Vi = Dynamic, otherwise let Vi = Ui.

Evaluation of e proceeds as follows:

First, if q is a generative constructor, then:

Let Ti be the type parameters of C (if any) and let Bi be the bounds of Ti, 1 <= i <= m. It is a dynamic type error if, in checked mode, Vi is not a subtype of [V1, …, Vm/T1, …, Tm]Bi, 1 <= i <= m.

A fresh instance, i, of class C is allocated. For each instance variable f of i, if the variable declaration of f has an initializer, then f is bound to that value (which is necessarily a compile-time constant). Otherwise f is bound to null.

Next, the argument list (a1, …, an, xn+1: an+1, …, xn+k: an+k) is evaluated. Then, the initializer list of q is executed with respect to the bindings that resulted from the evaluation of the argument list, and with this bound to i and the type parameters (if any) of C bound to the actual type arguments V1, ,.., Vm. Finally, the body of q is executed with respect to the bindings that resulted from the evaluation of the argument list. The result of the evaluation of e is i.

Otherwise, if q is a redirecting constructor, then:

The argument list (a1, …, an, xn+1: an+1, …, xn+k: an+k) is evaluated. Then, the redirect clause of q is executed with respect to the bindings that resulted from the evaluation of the argument list and the type parameters (if any) of C bound to the actual type arguments V1, ,.., Vm. resulting in an object i that is necessarily the result of another constructor call. The result of the evaluation of e is i.

Otherwise, q is a factory constructor. Then:

Let Ti be the type parameters of q (if any) and let Bi be the bounds of Ti, 1 <= i <= m. In checked mode, it is a dynamic type error if Vi is not a subtype of [V1, …, Vm/T1, …, Tm]Bi, 1 <= i <= m.

The argument list (a1, …, an, xn+1: an+1, …, xn+k: an+k) is evaluated Then, the body of k is executed with respect to the bindings that resulted from the evaluation of the argument list and the type parameters (if any) of q bound to the actual type arguments V1, ,.., Vm resulting in an object i. The result of the evaluation of e is i.

The static type of a new expression of either the form new T.id(a1, .., an) or the form new T(a1, .., an) is T. It is a static warning if the static type of ai, 1 <= i <= n+ k may not be assigned to the type of the corresponding formal parameter of the constructor T.id (respectively T).

Const

A constant object expression invokes a constant constructor.

constObjectExpression:

const type (‘.’ identifier)? arguments

;

Let e be a constant object expression of the form const T.id(a1, .., an, xn+1: an+1, …, xn+k: an+k) or the form const T(a1, .., an, xn+1: an+1, …, xn+k: an+k). It is a compile-time error if T is not a class or interface accessible in the current scope, optionally followed by type arguments. It is a compile-time error if T includes any type variables.

If e is of the form const T.id(a1, .., an, xn+1: an+1, …, xn+k: an+k) it is a compile-time error if T is not a class or interface accessible in the current scope, optionally followed by type arguments. It is a compile-time error if T.id is not the name of a constant constructor declared by the type T. If e of the form const T(a1, .., an, xn+1: an+1, …, xn+k: an+k) it is a compile-time error if the type T does not declare a constant constructor with the same name as the declaration of T.

In all of the above cases, it is a compile-time error if ai, 1 < = i <= n + k, is not a compile-time constant expression.

If T is a parameterized type S<U1, ,.., Um>, let R = S; It is a compile time error if S is not a generic type with m type parameters. If T is not a parameterized type, let R = T.
If R is an interface, let C be the factory class of R. Otherwise let C = R. Furthermore, if e is of the form const T.id(a1, .., an, xn+1: an+1, …, xn+k: an+k) then let k be the constructor C.id, otherwise let k be the constructor C. Finally, if C is generic but T is not a parameterized type, then for 1 <= i <= m, let Vi = Dynamic, otherwise let Vi = Ui.

Evaluation of e proceeds as follows:

First, if e is of the form const T.id(a1, .., an, xn+1: an+1, …, xn+k: an+k) then let i be the value of the expression new T.id(a1, .., an, xn+1: an+1, …, xn+k: an+k). Otherwise, e must be of the form const T(a1, .., an, xn+1: an+1, …, xn+k: an+k), in which case let i be the result of evaluating new T(a1, .., an, xn+1: an+1, …, xn+k: an+k) . Then:

  • If during execution of the program, a constant object expression has already evaluated to an instance j of class C with type arguments Vi 1 <= i <= m, then:
    • For each instance variable f of i, let vif be the value of the f in i, and let vjf be the value of the field f in j. If vif === vjf for all fields f in i, then the value of e is j, otherwise the value of e is i.
  • Otherwise the value of e is i.

In other words, constant objects are canonicalized. In order to determine if an object is actually new, one has to compute it; then it can be compared to any cached instances. If an equivalent object exists in the cache, we throw away the newly created object and use the cached one. Objects are equivalent if they have identical fields and identical type arguments. Since the constructor cannot induce any side effects, the execution of the constructor is unobservable. The constructor need only be executed once per call site, at compile-time.

The static type of a constant object expression of either the form const T.id(a1, .., an) or the form const T(a1, .., an) is T. It is a static warning if the static type of ai, 1 <= i <= n+ k may not be assigned to the type of the corresponding formal parameter of the constructor T.id (respectively T).

It is a compile-time error if evaluation of a constant object results in an uncaught exception being thrown.

To see how such situations might arise, consider the following examples:

class A {
static final x;
const A(var p): p = x * 10;
}

const A(“x”); //compile-time error
const A(5); // legal

class IntPair {
const IntPair(this.x, this.y);
final int x;
final int y;
operator *(v) => new IntPair(x*v, y*v);
}

const A(const IntPair(1, 2)); // compile-time error: illegal in a subtler way

Due to the rules governing constant constructors, evaluating the constructor A() with the argument “x” or the argument const IntPair(1, 2) would cause it to throw an exception, resulting in a compile-time error.

Spawning an Isolate

Spawning an isolate is accomplished via what is syntactically an ordinary library call, invoking the instance method spawn() defined in class Isolate. However, such calls have the semantic effect of creating a new isolate with its own memory and thread of control.

Property Extraction

Property extraction allows for a member of an object to be concisely extracted from the object.
If o is an object, and if m is the name of a method member of o, then o.m is defined to be equivalent to (r1, .., rn, [p1 = d1, …, pk = dk]){return o.m(r1, .., rn, p1, .., pk);} if m has required parameters r1, …, rn, and named parameters p1 .. pk with defaults d1, …, dk. Otherwise, if m is the name of a getter member of o (declared implicitly or explicitly) then o.m evaluates to the result of invoking the getter.
Observations:

  1. One cannot extract a getter or a setter.
  2. One can tell whether one implemented a property via a method or via field/getter, which means that one has to plan ahead as to what construct to use, and that choice is reflected in the interface of the class.

Function Invocation

Function invocation occurs in the following cases: when a function expression is invoked, when a method is invoked or when a constructor is invoked (either via instance creation , constructor redirectionor super initialization). The various kinds of function invocation differ as to how the function to be invoked, f, is determined as well as whether this is bound. Once f has been determined, the formal parameters of f are bound to the corresponding actual arguments. The body of f is then executed with the aforementioned bindings. Execution of the body terminates when the first of the following occurs;

  • An uncaught exception is thrown
  • A return statement immediately nested in the body of f is executed.
  • The last statement of the body completes execution.

Actual Argument List Evaluation

Function invocation involve evaluation of the list of actual arguments to the function and binding of the results to the function’s formal parameters.

arguments:
‘(‘ argumentList? ‘)’
;

argumentList:
namedArgument (‘,’ namedArgument)*
| expressionList (‘,’ namedArgument)*
;

namedArgument:
label expression
;

Evaluation of an actual argument list of the form (a1 .. am, q1: am+1, …, ql: am+l) proceeds as follows:

The arguments a1, …, am+l are evaluated in the order they appear in the program, yielding objects o1 .. om+l.

Simply stated, an argument list consisting of m positional arguments and l named arguments is evaluated from left to right.

Binding Actuals to Formals

Let f be the function, let p1, …, pn be the positional parameters of f and let pn+1, …, pn+k be the named parameters declared by f.

An evaluated actual argument list (o1, …, om+l) derived from an actual argument list of the form (a1 .. am, q1: am+1, …, ql: am+l) is bound to the formal parameters of f as follows:

Again, we have an argument list consisting of m positional arguments and l named arguments. We have a function with n required parameters and k named parameters. The number of positional arguments must be at least as large as the number of required parameters. All named arguments must have a corresponding named parameter. You may not provide the same parameter as both a positional and a named argument. If an optional parameter has no corresponding argument, it gets its default value. In checked mode, all arguments must belong to subtypes of the type of their corresponding formal.

If m < n, a run-time error occurs. Furthermore, each qi, 1 <= i <= l, must be a member of the set {pm+1, …, pm+k} or a run time error occurs. Then pi is bound to the value of oi, 1 <= i <= m, and qj is bound to the value of om+j, 1 <= j <= l. All remaining formal parameters of f are bound to their default values.

All of these remaining parameters are necessarily optional and thus have default values.

In checked mode, it is a dynamic type error if oi is not null and the actual type of pi is not a supertype of the type of oi, 1 <= i <= m. It is a dynamic type error if, in checked mode, om+j is not null and the actual type of qj is not a supertype of the type of om+j, 1 <= j <= l.

It is a compile-time error if qi = qj for any i != j.

Unqualified Invocation

An unqualified function invocation i has the form id(a1, …, an, xn+1: an+1, …, xn+k: an+k), where id is an identifier.

If there exists a lexically visible declaration named id, let fid be the innermost such declaration. Then:

  • If fid is a local function, a library function, a library or static getter or a variable then i is interpreted as a function expression invocation.
  • Otherwise, If fid is a static method of the enclosing class C, i is equivalent to the static method invocation C.id(a1, …, an, xn+1: an+1, …, xn+k: an+k).

Otherwise, if there is an accessible static method named id declared in a superclass S of the immediately enclosing class C then i is equivalent to the static method invocation S.id(a1, …, an, xn+1: an+1, …, xn+k: an+k).

Unqualified access to static methods of superclasses is inconsistent with the idea that static methods are not inherited. It is not particularly necessary and may be restricted in future versions.

Otherwise, i is equivalent to the ordinary method invocation this.id(a1, …, an, xn+1: an+1, …, xn+k: an+k).

Function Expression Invocation

A function expression invocation i has the form ef(a1, …, an, xn+1: an+1, …, xn+k: an+k), where ef is an expression. If ef is an identifier id, then id must necessarily denote a local function, a library function, a library or static getter or a variable as described above, or i is not considered a function expression invocation. If ef is a property access expression, then i is treated as an ordinary method invocation. Otherwise:

Evaluation of a function expression invocation i of the form ef(a1, …, an, xn+1: an+1, …, xn+k: an+k) proceeds as follows:

First, ef is evaluated to a value vf of type Tf. If Tf is not a function type, an ObjectNotAClosure is thrown. Next, the argument list (a1, …, an, xn+1: an+1, …, xn+k: an+k) is evaluated. The body of vf is then executed with respect to the bindings that resulted from the evaluation of the argument list. The value of i is the value returned after vf is executed.

It is a static type warning if the static type F of ef may not be assigned to a function type. If F is not a function type, the static type of i is Dynamic. Otherwise:

  • The static type of i is the declared return type of F.
  • Let Ti be the static type of ai, 1 <= i <= n+k. It is a static warning if F is not a supertype of (T1, …, Tn, [Tn+1 xn+1, …, Tn+k xn+k]) → bottom.

Method Invocation

Method invocation can take several forms as specified below.

Ordinary Invocation

An ordinary method invocation i has the form o.m(a1, …, an, xn+1: an+1, …, xn+k: an+k).

The result of looking up a method m in object o with respect to library L is the result of looking up method m in class C with respect to library L, where C is the class of o.

The result of looking up method m in class C with respect to library L is:

  • If C declares an instance method named m that is accessible to L, then that method is the result of the lookup. Otherwise, if C has a superclass S, then the result of the lookup is the result of looking up method m in S with respect to L. Otherwise, we say that the lookup has failed.

Evaluation of an ordinary method invocation i of the form o.m(a1, …, an, xn+1: an+1, …, xn+k: an+k) proceeds as follows:

First, the expression o is evaluated to a value vo. Next, the argument list (a1, …, an, xn+1: an+1, …, xn+k: an+k) is evaluated yielding actual objects o1, …, on+k. Let f be the result of looking up method m in vo with respect to the current library L. If the method lookup succeeded, the body of f is executed with respect to the bindings that resulted from the evaluation of the argument list, and with this bound to vo. The value of i is the value returned after f is executed.

If the method lookup has failed, then let g be the result of looking up getter m in vo with respect to L. If the getter lookup succeeded, let vg be the value of the getter invocation o.m. If vg is a function then it is executed with respect to the bindings of the evaluated argument list. The value of i is the value returned after vg is executed.

If vg is not a function then an ObjectNotAClosure is thrown.

It is quite possible that this behavior will change. We could invoke apply() on the result and let noSuchMethod take its course. That would allow anyone to implement and emulate Function.

If getter lookup has also failed, then a new instance im of the predefined interface InvocationMirror is created by calling its factory constructor with arguments ‘m’, this, [o1, …, on] and {xn+1:on+1, …, xn+k : on+k}. Then the method noSuchMethod() is looked up in o and invoked with argument im, and the result of this invocation is the result of evaluating i.

Notice that the wording carefully avoids re-evaluating the receiver o and the arguments ai.

Let T be the static type of o. It is a static type warning if T does not have an accessible instance member named m. If T.m exists, it is a static warning if the type F of T.m may not be assigned to a function type. If T.m does not exist, or if F is not a function type, the static type of i is Dynamic. Otherwise:

  • The static type of i is the declared return type of F.
  • Let Ti be the static type of ai, 1 <= i <= n+k. It is a static warning if F is not a supertype of (T1, …, Tn, [Tn+1 xn+1, …, Tn+k xn+k]) → bottom.

Static Invocation

A static method invocation i has the form C.m(a1, …, an, xn+1: an+1, …, xn+k: an+k). It is a compile-time error if C does not denote a class in the current scope. It is a compile-time error if C does not declare a static method or getter m.

Note the requirement that C declare the method. This means that static methods declared in superclasses of C cannot be invoked via C.

Evaluation of i proceeds as follows:

First, if the member m declared by C is a getter, then let f be the result of evaluating the getter invocation C.m. If f is not a function then an ObjectNotAClosure is thrown. Otherwise, let f be the method m declared in class C. Next, the argument list (a1, …, an, xn+1: an+1, …, xn+k: an+k) is evaluated.

The body of f is then executed with respect to the bindings that resulted from the evaluation of the argument list. The value of i is the value returned after the body of f is executed.

It is a static type warning if the type F of C.m may not be assigned to a function type. If F is not a function type, the static type of i is Dynamic. Otherwise:

  • The static type of i is the declared return type of F.
  • Let Ti be the static type of ai, 1 <= i <= n+k. It is a static warning if F is not a supertype of (T1, …, Tn, [Tn+1 xn+1, …, Tn+k xn+k]) → bottom.

Super Invocation

A super method invocation has the form super.m(a1, …, an, xn+1: an+1, …, xn+k: an+k).

Evaluation of a super method invocation i of the form super.m(a1, …, an, xn+1: an+1, …, xn+k: an+k) proceeds as follows:

First, the argument list (a1, …, an, xn+1: an+1, …, xn+k: an+k) is evaluated yielding actual objects o1, …, on+k. Let S be the superclass of the class of this and let f be the result of looking up method m in S with respect to the current library L. If the method lookup succeeded, the body of f is executed with respect to the bindings that resulted from the evaluation of the argument list, and with this bound to the current value of this. The value of i is the value returned after f is executed.

If the method lookup has failed, then let g be the result of looking up getter m in S with respect to L. If the getter lookup succeeded, let vg be the value of the getter invocation super.m. If vg is a function then it is called with the evaluated argument list. The value of i is the value returned after vg is executed.

If vg is not a function then an ObjectNotAClosure is thrown.

If the getter lookup has also failed, then a new instance im of the predefined interface InvocationMirror is created by calling its factory constructor with arguments ‘m’, this, [e1, …, en] and {xn+1:en+1, …, xn+k : en+k}. Then the method noSuchMethod() is looked up in S and invoked with argument im, and the result of this invocation is the result of evaluating i.

It is a compile-time error if a super method invocation occurs in a top-level function or variable initializer, in class Object, or in a static method or variable initializer.

It is a static type warning if S does not have an accessibleinstance member named m. If S.m exists, it is a static warning if the type F of S.m may not be assigned to a function type. If S.m does not exist, or if F is not a function type, the static type of i is Dynamic. Otherwise:

  • The static type of i is the declared return type of F.
  • Let Ti be the static type of ai, 1 <= i <= n+k. It is a static warning if F is not a supertype of (T1, …, Tn, [Tn+1 xn+1, …, Tn+k xn+k]) → bottom.

Sending Messages

Messages are the sole means of communication among isolates. Messages are sent by invoking specific methods in the Dart libraries; there is no specific syntax for sending a message.

In other words, the methods supporting sending messages embody primitives of Dart that are not accessible to ordinary code, much like the methods that spawn isolates.

Getter Invocation

A getter invocation provides access to the value of a property.

The result of looking up a getter (respectively setter) m in object o with respect to library L is the result of looking up getter (respectively setter) m in class C with respect to L, where C is the class of o.

The result of looking up getter (respectively setter) m in class C with respect to library L is:

  • If C declares an instance getter (respectively setter) named m that is accessible to L, then that getter (respectively setter) is the result of the lookup. Otherwise, if C has a superclass S, then the result of the lookup is the result of looking up getter (respectively setter) m in S with respect to L. Otherwise, we say that the lookup has failed.

Evaluation of a getter invocation i of the form e.m proceeds as follows:

First, the expression e is evaluated to an object o. Then, the getter function m is looked up in o with respect to the current library, and its body is executed with this bound to o. The value of the getter invocation expression is the result returned by the call to the getter function.

If the getter lookup has failed, then a new instance im of the predefined interface InvocationMirror is created by calling its factory constructor with arguments ‘get m’, o, [] and {}. Then the method noSuchMethod() is looked up in o and invoked with argument im, and the result of this invocation is the result of evaluating i.

Let T be the static type of e. It is a static type warning if T does not have a getter named m. The static type of i is the declared return type of T.m, if T.m exists; otherwise the static type of i is Dynamic.

Evaluation of a getter invocation i of the form C.m proceeds as follows:

The getter function C.m is invoked. The value of i is the result returned by the call to the getter function.

It is a compile-time error if there is no class C in the enclosing lexical scope of i, or if C does not declare, implicitly or explicitly, a getter named m. The static type of i is the declared return type of C.m.

Evaluation of a top-level getter invocation i of the form m, where m is an identifier, proceeds as follows:

The getter function m is invoked. The value of i is the result returned by the call to the getter function.

The static type of i is the declared return type of m.

Assignment

An assignment changes the value associated with a mutable variable or property.

assignmentOperator:
‘=’
| compoundAssignmentOperator
;

Evaluation of an assignment of the form v = e proceeds as follows:

If there is no declaration d with name v in the lexical scope enclosing the assignment, then the assignment is equivalent to the assignment this.v = e. Otherwise, let d be the innermost declaration whose name is v, if it exists.

If d is the declaration of a local or library variable, the expression e is evaluated to an object o. Then, the variable v is bound to o. The value of the assignment expression is o.

Otherwise, if d is the declaration of a static variable in class C, then the assignment is equivalent to the assignment C.v = e.

Otherwise, the assignment is equivalent to the assignment this.v = e.

In checked mode, it is a dynamic type error if o is not null and the interface induced by the class of o is not a subtype of the actual type of v.

It is a static type warning if the static type of e may not be assigned to the static type of v.

Evaluation of an assignment of the form C.v = e proceeds as follows:

The expression e is evaluated to an object o. Then, the setter C.v is invoked with its formal parameter bound to o. The value of the assignment expression is o.

It is a compile-time error if there is no class C in the enclosing lexical scope of assignment, or if C does not declare, implicitly or explicitly, a setter v.

In checked mode, it is a dynamic type error if o is not null and the interface induced by the class of o is not a subtype of the static type of C.v.

It is a static type warning if the static type of e may not be assigned to the static type of C.v.

Evaluation of an assignment of the form e1.v = e2 proceeds as follows:

The expression e1 is evaluated to an object o1. Then, the expression e2 is evaluated to an object o2. Then, the setter v is looked up in o1 with respect to the current library, and its body is executed with its formal parameter bound to o2 and this bound to o2. If the setter lookup has failed, then a new instance im of the predefined interface InvocationMirror is created by calling its factory constructor with arguments ‘set v’, o1, [o2] and {}. Then the method noSuchMethod() is looked up in o1 with argument im. The value of the assignment expression is o2 irrespective of whether setter lookup has failed or succeeded.

In checked mode, it is a dynamic type error if o2 is not null and the interface induced by the class of o2 is not a subtype of the actual type of e1.v.

It is a static type warning if the static type of e2 may not be assigned to the static type of e1.v.

Compound Assignment

A compound assignment of the form v op= e is equivalent to v = v op e. A compound assignment of the form C.v op= e is equivalent to C.v = C.v op e. A compound assignment of the form e1.v op= e2 is equivalent to ((x) => x.v = x.v op e2)(e1) where x is a variable that is not used in e2.

compoundAssignmentOperator:
‘*=’
| ‘/=’
| ‘~/=’
| ‘%=’
| ‘+=’
| ‘-=’
| ‘<<=’
| ‘>>>=’
| ‘>>=’
| ‘&=’
| ‘^=’
| ‘|=’
;

Conditional

A conditional expression evaluates one of two expressions based on a boolean condition.

conditionalExpression:
logicalOrExpression (‘?’ expression ‘:’ expression)?
;

Evaluation of a conditional expression c of the form e1 ? e2 : e3 proceeds as follows:

First, e1 is evaluated to an object o1. In checked mode, it is a dynamic type error if o1 is not of type bool. Otherwise o1 is then subjected to boolean conversion producing an object r. If r is true, then the value of c is the result of evaluating the expression e2. Otherwise the value of c is the result of evaluating the expression e3.

It is a static type warning if the type of e1 may not be assigned to bool. The static type of c is the least upper bound of the static type of e2 and the static type of e3.

Logical Boolean Expressions

The logical boolean expressions combine boolean objects using the boolean conjunction and disjunction operators.

logicalOrExpression:
logicalAndExpression (‘||’ logicalAndExpression)*
;

logicalAndExpression:
bitwiseOrExpression (‘&&’ bitwiseOrExpression)*
;

A logical boolean expression is either a bitwise expression, or an invocation of a logical boolean operator on an expression e1 with argument e2.

Evaluation of a logical boolean expression b of the form e1 || e2 causes the evaluation of e1; if e1 evaluates to true, the result of evaluating b is true, otherwise e2 is evaluated to an object o, which is then subjected to boolean conversion producing a an object r, which is the value of b.

Evaluation a logical boolean expression b of the form e1 && e2 causes the evaluation e1; if e1 does not evaluate to true, the result of evaluating b is false, otherwise e2 is evaluated to an object o, which is then subjected to boolean conversion producing an object r, which is the value of b.

The static type of a logical boolean expression is bool.

Bitwise Expressions

Bitwise expressions invoke the bitwise operators on objects.

bitwiseOrExpression:
bitwiseXorExpression (‘|’ bitwiseXorExpression)*
| super (‘|’ bitwiseXorExpression)+
;

bitwiseXorExpression:
bitwiseAndExpression (‘^’ bitwiseAndExpression)*
| super (‘^’ bitwiseAndExpression)+
;

bitwiseAndExpression:
equalityExpression (‘&’ equalityExpression)*
| super (‘&’ equalityExpression)+

bitwiseOperator:
‘&’
| ‘^’
| ‘|’
;

A bitwise expression is either an equality expression, or an invocation of a bitwise operator on either super or an expression e1, with argument e2.

A bitwise expression of the form e1 op e2 is equivalent to the method invocation e1.op(e2).
A bitwise expression of the form super op e2 is equivalent to the method invocation super.op(e2).

It should be obvious that the static type rules for these expressions are defined by the equivalence above – ergo, by the type rules for method invocation and the signatures of the operators on the type e1. The same holds in similar situations throughout this specification.

Equality

Equality expressions test objects for identity or equality.

equalityExpression:
relationalExpression (equalityOperator relationalExpression)?
| super equalityOperator relationalExpression
;

equalityOperator:
‘==’
| ‘!=’
| ‘===’
| ‘!==’
;

An equality expression is either a relational expression, or an invocation of a equality operator on on either super or an expression e1, with argument e2.

An equality expression of the form e1 == e2 is equivalent to the method invocation e1.==(e2).
An equality expression of the form super == e is equivalent to the method invocation super.==(e).

An equality expression of the form e1 != e2 is equivalent to the expression !(e1 == e2 ). An equality expression of the form super != e is equivalent to the expression !(super == e)).

Evaluation of an equality expression ee of the form e1 === e2 proceeds as follows:
The expression e1 is evaluated to an object o1; then the expression e2 is evaluated to an object o2. Next, if o1 and o2 are the same object, then ee evaluates to true, otherwise ee evaluates to false.

An equality expression of the form super === e is equivalent to the expression this === e.

An equality expression of the form e1 !== e2 is equivalent to the expression !(e1 === e2 ). An equality expression of the form super !== e is equivalent to the expression !(super === e).

The static type of an equality expression of the form e1 === e2 is bool.

The static types of other equality expressions follow from the definitions above. The forms e1 != e2, e1 !== e2 , super != e and super !== e are negations and have static type bool. The expression e1 == e2 is typed as a method invocation so its static type depends on the operator method declaration. It had better be bool.

Relational Expressions

Relational expressions invoke the relational operators on objects.

relationalExpression:
shiftExpression (isOperator type | relationalOperator shiftExpression)?
| super relationalOperator shiftExpression
;

relationalOperator:
‘>=’
| ‘>’
| ‘<=’
| ‘<‘
;

A relational expression is either a shift expression, or an invocation of a relational operator on on either super or an expression e1, with argument e2.

A relational expression of the form e1 op e2 is equivalent to the method invocation e1.op(e2).
A relational expression of the form super op e2 is equivalent to the method invocation super.op(e2).

Shift

Shift expressions invoke the shift operators on objects.

shiftExpression:
additiveExpression (shiftOperator additiveExpression)*
| super (shiftOperator additiveExpression)+
;

shiftOperator:
‘<<‘
| ‘>>>’
| ‘>>’
;

A shift expression is either an additive expression, or an invocation of a shift operator on on either super or an expression e1, with argument e2.

A shift expression of the form e1 op e2 is equivalent to the method invocation e1.op(e2).
A shift expression of the form super op e2 is equivalent to the method invocation super.op(e2).

Note that this definition implies left-to-right evaluation order among shift expressions:

e1 << e2 << e3

is evaluated as (e1 << e2 ).<< (e3) which is equivalent to (e1 << e2) << e3.
The same holds for additive and multiplicative expressions.

Additive Expressions

Additive expressions invoke the addition operators on objects.

additiveExpression:
multiplicativeExpression (additiveOperator multiplicativeExpression)*
| super (additiveOperator multiplicativeExpression)+
;

additiveOperator:
‘+’
| ‘-‘
;

An additive expression is either a multiplicative expression, or an invocation of an additive operator on on either super or an expression e1, with argument e2.

An additive expression of the form e1 op e2 is equivalent to the method invocation e1.op(e2).
An additive expression of the form super op e2 is equivalent to the method invocation super.op(e2).

Multiplicative Expressions

Multiplicative expressions invoke the multiplication operators on objects.

multiplicativeExpression:
unaryExpression (multiplicativeOperator unaryExpression)*
| super (multiplicativeOperator unaryExpression)+
;

multiplicativeOperator:
‘*’
| ‘/’
| ‘%’
| ‘~/’
;

A multiplicative expression is either a unary expression, or an invocation of a multiplicative operator on either super or an expression e1, with argument e2.

A multiplicative expression of the form e1 op e2 is equivalent to the method invocation e1.op(e2). A multiplicative expression of the form super op e2 is equivalent to the method invocation super.op(e2).

Unary Expressions

Unary expressions invoke unary operators on objects.

unaryExpression:
prefixOperator unaryExpression
| postfixExpression
| unaryOperator super
| ‘-‘ super
| incrementOperator assignableExpression
;

A unary expression is either a postfix expression, an invocation of a prefix operator on an expression e, or an invocation of a unary operator on either super or an expression e.

The expression !e is equivalent to the expression e? false: true.
Evaluation of an expression of the form ++e is equivalent to e += 1. Evaluation of an expression of the form –e is equivalent to e -= 1.

The expression -e is equivalent to the method invocation e.negate(). The expression –super is equivalent to the method invocation super.negate().

A unary expression u of the form op e is equivalent to a method invocation expression e.op().
An expression of the form op super is equivalent to the method invocation super.op().

Postfix Expressions

Postfix expressions invoke the postfix operators on objects.

postfixExpression:
assignableExpression postfixOperator
| primary selector*
;

postfixOperator:
incrementOperator
;

incrementOperator:
‘++’
| ‘–‘
;

A postfix expression is either a primary expression, a function, method or getter invocation, or an invocation of a postfix operator on an expression e.

Evaluation of a postfix expression of the form v ++, where v is an identifier, is equivalent to (){var r = v; v = r + 1; return r}().

The above ensures that if v is a field, the getter gets called exactly once. Likewise in the cases below.

Evaluation of a postfix expression of the form C.v ++ is equivalent to (){var r = C.v; C.v = r + 1; return r}().

A postfix expression of the form e1.v++ is equivalent to (x){var r = x.v; x.v = r + 1; return r}(e1).

Evaluation of a postfix expression of the form e– is equivalent to e ++ (-1).

Assignable Expressions

Assignable expressions are expressions that can appear on the left hand side of an assignment.

Of course, if they always appeared as the left hand side, one would have no need for their value, and the rules for evaluating them would be unnecessary. However, assignable expressions can be subexpressions of other expressions and therefore must be evaluated.

assignableExpression:
primary (arguments* assignableSelector)+
| super assignableSelector
| identifier
;

selector:
assignableSelector
| arguments
;

assignableSelector:
‘[‘ expression ‘]’
| ‘.’ identifier
;

An assignable expression is either:

  • An identifier.
  • An invocation of a method, getter or list access operator on an expression e.
  • An invocation of a getter or list access operator on super.

An assignable expression of the form id is evaluated as an identifier expression.

An assignable expression of the form e.id(a1, …, an) is evaluated as a method invocation.

An assignable expression of the form e.id is evaluated as a getter invocation.

An assignable expression of the form e1[e2] is evaluated as a method invocation of the operator method [] on e1 with argument e2.

An assignable expression of the form super.id is evaluated as a getter invocation.

An assignable expression of the form super[e2] is equivalent to the method invocation super.[e2].

Identifier Reference

An identifier expression consists of a single identifier; it provides access to an object via an unqualified name.

identifier:
IDENTIFIER_NO_DOLLAR
| IDENTIFIER
| BUILT_IN_IDENTIFIER
;

IDENTIFIER_NO_DOLLAR:
IDENTIFIER_START_NO_DOLLAR IDENTIFIER_PART_NO_DOLLAR*
;

IDENTIFIER:
IDENTIFIER_START IDENTIFIER_PART*
;

;

BUILT_IN_IDENTIFIER:
abstract
| assert
| Dynamic
| factory
| get
| implements
| import
| interface
| library
| negate
| operator
| set
| source
| static
| typedef
;

IDENTIFIER_START:
IDENTIFIER_START_NO_DOLLAR
| ‘$’
;

IDENTIFIER_START_NO_DOLLAR:
LETTER
| ‘_’
;

IDENTIFIER_PART_NO_DOLLAR:
IDENTIFIER_START_NO_DOLLAR
| DIGIT
;

IDENTIFIER_PART:
IDENTIFIER_START
| DIGIT
;

qualified:
identifier (‘.’ identifier)?
;

Built-in identifiers are identifiers that are used as keywords in Dart, but are not reserved words in Javascript. To minimize incompatibilities when porting Javascript code to Dart, we do not make these into reserved words.

Evaluation of an identifier expression e of the form id proceeds as follows:
Let d be the innermost declaration in the enclosing lexical scope whose name is id. It is a compile-time error if d is a class, interface or type variable. If no such declaration exists in the lexical scope, let d be the declaration of the inherited member named id if it exists. If no such member exists, let d be the declaration of the static member named id declared in a superclass of the current class, if it exists.

  • If d is a library variable, local variable, or formal parameter, then e evaluates to the current binding of id. This case also applies if d is a library or local function declaration, as these are equivalent to function-valued variable declarations.
  • If d is a static method, then e evaluates to the function defined by d.
  • If d is the declaration of a static variable or static getter declared in class C, then e is equivalent to the getter invocation C.id.
  • If d is the declaration of a top level getter, then e is equivalent to the getter invocation id.
  • Otherwise e is equivalent to the property extraction this.id.

Type Test

The is-expression tests if an object is a member of a type.
isOperator:
is ‘!’?
;

Evaluation of the is-expression e is T proceeds as follows:

The expression e is evaluated to a value v. Then, if the interface induced by the class of v is a subtype of T, the is-expression evaluates to true. Otherwise it evaluates to false.

It follows that e is Object is always true. This makes sense in a language where everything is an object.

Also note that null is T is false unless T = Object or T = Null. Since the class Null is not exported by the core library, the latter will not occur in user code. The former is useless, as is anything of the form e is Object. Users should test for a null value directly rather than via type tests.

The is-expression e is! T is equivalent to the expression !(e is T).
It is a compile-time error if T does not denote a type available in the current lexical scope.
The static type of an is-expression is bool.

By Dartlang.org

< Prev – Google Dart Language Specification II

< Prev – Google Dart Language Specification I

Google Dart Languaje Specification VI – Next >

:: Official Link Dartlang.org

3 thoughts on “Google Dart Language Specification III – Interfaces and expressions

  1. Pingback: Google Dart Language Specification VI – Statemets, scripts and types « Moises Belchin

  2. Pingback: Google Dart Language Specification « Moises Belchin

  3. Pingback: Google Dart Language Specification II – Variables, Functions and Classes « Moises Belchin

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s