From ad7e82573996b4c658e1e9d660ba605dac13cc40 Mon Sep 17 00:00:00 2001 From: James Clark Date: Mon, 17 Jun 2019 10:34:16 +0700 Subject: [PATCH] Properly specify assignment Fixes #201. Fixes #149. --- lang/spec.html | 565 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 404 insertions(+), 161 deletions(-) diff --git a/lang/spec.html b/lang/spec.html index 646d1b83..2ee8bc01 100644 --- a/lang/spec.html +++ b/lang/spec.html @@ -728,9 +728,9 @@

Strings

Structured values

-There are five basic types of structured value. First, there are three container -basic types: list, mapping and table. Second, there is the xml basic type and -the error basic type, which are both special in different ways. +There are five basic types of structured value. First, there are two container +basic types: list and mapping. Second, there are the table, xml and error basic +types, which are each special in different ways.

Values of the container basic types are containers for other values, which are @@ -761,6 +761,14 @@

Structured values

shape of the value. In other words, freezing a container narrows its inherent type to a type that consists of just its current shape.

+

+Each member has a key that uniquely identifies it within the container. The +member type for a key type K in a container type T consists of all +shapes v such that there is a shape in T with key in K and shape v. A type K is +an optional member type for T if there is a shape v in T and a key k in +K such that v does not have a member k; a member type that is not optional is +required. +

structured-type-descriptor :=
@@ -776,7 +784,7 @@ 

Structured values

- + @@ -809,11 +817,12 @@

Structured values

Lists

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

A list is iterable as a sequence of its members. @@ -1795,9 +1804,130 @@

Listener

Abstract operations

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

+

FillMember

+

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

+
Integer indexInteger key String key
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Type descriptorFiller valueWhen available
()()
booleanfalse
int0
float+0.0f
decimal+0.0d
string""
list type descriptor[]if that is a valid constructor for the type
mapping type descriptor{ }if that is a valid constructor for the type
tableempty table (with no rows)
objectnew T()if this is valid and its static type does not include error, where +T is the object type descriptor (an abstract object type will not +have a filler value)
xmlxml``
stream<T>new stream<T>
singletonthe single value used to specify the type
union()if () is a member of the union
the filler value for basic type Bif all members of the union belong to a single basic type B, +and the filler value for B also belongs to the union
T?()
any()
anydata()
byte0
json()
+

Freeze

Freeze(v) is defined for any pure value v. The result of Freeze(v) is always v. @@ -2045,7 +2175,7 @@

6. Expressions

| variable-reference-expr | field-access-expr | annot-access-expr - | index-expr + | member-access-expr | xml-attributes-expr | function-call-expr | method-call-expr @@ -2469,129 +2599,16 @@

List constructor

the expressions for members.

-Some members of a list can be filled in automatically. A member can be filled in -automatically if its type descriptor, as determined by the inherent type of the -list, provides a way to create a filler value. The following table -specifies the filler value for each kind of type descriptor that provides one. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Type descriptorFiller valueWhen provided
()()
booleanfalse
int0
float+0.0f
decimal+0.0d
string""
list type descriptor[]if that is a valid constructor for the type
mapping type descriptor{ }if that is a valid constructor for the type
tableempty table (with no rows)
objectnew T()if this is valid and its static type does not include error, where -T is the object type descriptor (an abstract object type will not -have a filler value)
xmlxml``
stream<T>new stream<T>
singletonthe single value used to specify the type
union()if () is a member of the union
the filler value for basic type Bif all members of the union belong to a single basic type B, -and the filler value for B also belongs to the union
T?()
any()
anydata()
byte0
json()
-

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

Mapping constructor

@@ -2845,16 +2862,16 @@

Annotation access expression

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

-

Index expression

+

Member access expression

index-expr := expression [ expression ]
+class="grammar">member-access-expr := expression [ expression ]
 

{MISSING}

-Evaluation of an index-expr that attempts to accessing a list with an out of +Evaluation of a member-access-expr that attempts to access a list with an out of bounds index will complete abruptly with a panic.

[Preview] XML attributes expression

@@ -4263,44 +4280,270 @@

[Preview] XML namespace declaration statement

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

-

Assignment statement

+

Assignment

+ +

+There are three kinds of assignment statement: +

+ +

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

+ +

Lvalues

+ +

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

+ + +

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

+
assignment-stmt := lhs = action-or-expr ;
-lhs :=
-   variable-reference
-   | lhs . field-name
-   | lhs [ expression ]
-   | lhs @
+class="grammar">lvexpr :=
+   variable-reference-lvexpr
+   | field-access-lvexpr
+   | member-access-lvexpr
 
+ +

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

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

-When a list value has an inherent type that is not fixed length. then attempting -to write a member of a list at an index i that is greater than or equal -to the current length of the list will first increase the length of the list to -i + 1, with the newly added members of the array being filled in -automatically. +An lvalue supports three operations: store, read and filling-read.

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

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

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

+

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

+

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

+

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

+

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

-

Compound assignment statement

+ +
variable-reference-lvexpr := variable-reference
+
+

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

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

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

+

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

+

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

+ + +
member-access-lvexpr := lvexpr [ expression ]
+
+

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

+It is evaluated as follows: +
    +
  1. evaluate expression to get a string or int k;
  2. +
  3. evaluate lvexpr to get lvalue lv;
  4. +
  5. perform a filling-read operation on lv to get a list or mapping value +v;
  6. +
  7. the result is an lvalue referring to the member of c with key +k.
  8. +
+

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

+ +

Assignment statement

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

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

+

+It is executed at as follows: +

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

Compound assignment statement

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

-These statements update the value of the LHS variable to the value that results -from applying the corresponding binary operator to the value of the variable and -the value of the expression. +It is a compile error if lvexpr is potentially undefined unless the static type +of lvexpr is a subtype of the list basic type. It is a compile error if lvexpr +is potentially uninitialized.

-

Destructuring assignment statement

+

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

+

+It is executed as follows: +

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

Destructuring assignment statement

destructure-assignment-stmt :=