--- ai05s/ai05-0153-2.txt 2009/10/23 06:06:31 1.3 +++ ai05s/ai05-0153-2.txt 2009/10/29 04:05:05 1.4 @@ -1,4 +1,4 @@ -!standard 3.2.2(7) 09-10-16 AI05-0153-1/01 +!standard 3.2.2(7) 09-10-26 AI05-0153-1/02 !class Amendment 09-10-16 !status work item 09-10-16 !status received 09-10-16 @@ -41,27 +41,52 @@ [Author's note: I used "when" and "discrete_choice_list" here to be consistent with other parts of the language. We could use other syntaxes, but that would -just make it harder to remember the correct syntax IMHO. Alternatives that have -turned up include using "with" or "in" here. See also the discussion.] +just make it harder to remember the correct syntax IMHO. Alternatives are +discussed in the discussion section.] A value *belongs* to the discrete_choice_list of a list_constraint if it is of the type of the choices of the list, and it is either equal to one of the expressions of the list or it belongs to one of the discrete_ranges of the list. A value *satisfies* a list_constraint if it belongs to the discrete_choice_list -of the constraint. The *lower bound* of a list_constraint is the smallest value +of the constraint. + +The *lower bound* of a list_constraint is the smallest value +used in an expression or the lower bound of a discrete range in the +discrete_choice_list of the constraint, unless the list only contains null range +choices, in which case it is the largest value used in the lower bound of +a discrete range. The *upper bound* of a list_constraint is the largest value used in an expression or the lower bound of a discrete range in the -discrete_choice_list of the constraint. The *upper bound* of a list_constraint -is the largest value used in an expression or the lower bound of a discrete -range in the discrete_choice_list of the constraint. A list_constraint is -*discontiguous* if there exists a value that belongs to the range defined by the -lower and upper bound of the constraint. A scalar subtype is *discontiguous* if -it has a discontiguous list_constraint. +discrete_choice_list of the constraint, unless the list only contains null range +choices, in which case it is the smallest value used in the upper bound of +a discrete range. + +AARM Reason: We define upper and lower bounds for list_constraints so that +the existing assumption that all discrete subtypes have such bounds is maintained. +We have the special case for all null range choices so that the bounds in that +case represent a null range as well. For instance, for Natural when 3 .. 1 | 11 .. 10, +the basic rules give bounds of 3 .. 10, which would be annoying; the special rules +give given bounds of 11 .. 1 (which make more sense). +End AARM Reason. + +A list_constraint is *discontiguous* if there exists a value that belongs to the range +defined by the lower and upper bound of the constraint that does not belong to the +list_constraint, or if the list_constraint has more than one choice of which one or more +are null ranges. A scalar subtype is *discontiguous* if it has a discontiguous +list_constraint. -AARM Note: We require staticness for the discrete_choices, so the lower and +AARM Ramification: We require staticness for the discrete_choices, so the lower and upper bounds can be determined at compile-time (we don't want to have to do a complex calculation to determine S'First). This also allows us to make legality depend on whether the list_constraint is contiguous. +End AARM Ramification. +AARM Reason: We define lists that have multiple choices and one or more null ranges +as discontigious so that they cannot be used in arrays and slices. Otherwise, users +could be confused by the bounds. Such things could have a contiguous set of values: +Arr(Natural when 5 | 5 .. 4) and Arr(Natural when 3 .. 1 | 11 .. 10) both would be +allowed without the extra rule. +End AARM Reason. + Add after 3.5(5): For a subtype_indication containing a list_constraint, the type of the expressions @@ -145,7 +170,9 @@ [Author's note: Should S'Range be illegal/raise Program_Error if the subtype has a discontiguous list constraint? It's a dubious construct, but since you can always write it explicitly (it's defined to be equivalent to S'First..S'Last), -it's hard to say that it should be illegal.] +it's hard to say that it should be illegal. And not allowing S'First or S'Last +seems bad (it is useful to find the smallest or largest value even without +using the range).] Add as the last sentence of 3.6(9): @@ -176,6 +203,18 @@ rule so when it is statically known (outside of a generic) we detect the problem at compile-time.] +Add after 3.8.1(5): + +The discrete_range of a discrete_choice shall not be a subtype_indication with +a list_constraint. + +AARM Reason: This is needed to avoid ambiguity. Otherwise, it would be impossible +to tell where the subtype_indication ended and the case choices start in something +like "when Natural when 1 | 3 | 5 | (-1) =>". Note that it matters because if +the (-1) is included in the subtype_indication the program would certainly be +illegal, but it might be legal if it is a case choice (depending on the subtype +of the case expression). + Add after 4.1.2(4): Legality Rules @@ -208,7 +247,7 @@ scalar constraint with a list_constraint "has no range_constraint". But that's very tricky!] -[For 4.9.1(1.1/2), see below; it's modified by both parts here.] +[For static matching, 4.9.1(1.2/2), see below; it's modified by both parts here.] Add an AARM note after 5.4(7): @@ -246,18 +285,77 @@ Extended discriminant constraints: +[Editor's note: These rules are in order of modification in the Standard as is +traditional for AIs; that is exactly the wrong order to read them in, because +three relatively unimportant details come first. Start with the 3.7.1 +syntax/wording.] + +Modify 3.3(23): + +... +A subtype is an indefinite subtype if it is an unconstrained array subtype, [or] +if it has unknown discriminants or unconstrained discriminants without defaults (see 3.7){, +or if it has a discriminant constraint of an indefinite subtype that contains a +discrete_range (see 3.7.1); otherwise the subtype is a definite subtype (all +elementary subtypes are definite subtypes). ... + +AARM Reason: A discriminant constraint that contains a discrete_range effectively +makes any object with that nominal subtype mutable, as the discriminants can be +changed (by a full record assignment) to any value that belongs to the discrete_range. +That is dangerous for types that are not already mutable: for instance, we have rules +to prevent declaring mutable tagged types, and we surely do not want to introduce a +back door to get mutable tagged subtypes. Additionally, mutable types potentially +have additional space and/or time overhead compared to non-mutable discriminanted types, +and we do not want to require such overhead of all discriminated record types. + +[Editor's notes: The term "mutable" is defined in the AARM (and only in the AARM). So +we can use it in AARM notes, but not in normative text. + +We might benefit from defining a term for "discriminant_constraint that contains +at least one discrete_range". I considered "mutable subtype" for that, but the +term is more general than the use. So I didn't define a term at all.] + +Modify 3.3(23.2/3): [Added by AI05-0008-1] + +* its nominal subtype is constrained{, the constraint is not a discriminant_constraint +that contains a discrete_range}, and is not an untagged partial view; or + +AARM Reason: A discriminant_constraint that contains a discrete_range is mutable, +in the sense that the discriminant(s) can be changed (to a different value that +also belongs to the range or list). "Known to be constrained" +exists specifically to avoid problems with renames and the like when discriminants +change, so we need to add this case to it. The subtype is constrained, but not +enough to prevent problems. + Modify 3.3.1(12): * The implicit initial (and only) value for each discriminant of a constrained - discriminanted subtype is determined by the subtype. {If the discriminant + discriminated subtype is determined by the subtype. {If the discriminant constraint defines a range or subtype for the value of the discriminant, the - lower bound of the range or subtype is used as the initial value.} + lower bound of the range or subtype is used as the initial value. If the range + or subtype is null, Constraint_Error is raised.} AARM Reason: A discriminant constraint that defines a range or list of acceptable discriminant values means that any value in the range or list is acceptable for that discriminant. We select the lower bound in order that programs are portable (although depending on that is dubious). +If the range or subtype is null, then there is no value to give the discriminant, +so we have to raise Constraint_Error. + +Such objects are always considered "constrained by its initial value" so that +the discriminants cannot be changed later. That's important if a discriminant +is constrained with a range or subtype; we don't want to allow changing the +discriminant to some other value within the range or subtype. Otherwise, we +would add all of the implementation problems of mutable discriminants to any +object with a constraint that *might* contain a range or subtype. +End AARM Reason. + +[Editor's note: We didn't check for null ranges in discriminant_constraints, +because the range could have been dynamic and the constraint could have been used +such that no object is every created. This could be fairly common and could cause +programs to fail to elaborate if a table is empty or the like.] + Replace 3.7.1(3) by: discriminant_association ::= @@ -276,12 +374,12 @@ Modify 3.7.1(8): -...A discriminant_constraint shall provide exactly one value {or subtype} for -each discriminant of the subtype being considered. +...A discriminant_constraint shall provide exactly one value {, range, or +subtype} for each discriminant of the subtype being considered. Replace 3.7.1(10) with: -A discriminant_constraint is *compatible* with an unconstrained discriminanted +A discriminant_constraint is *compatible* with an unconstrained discriminated subtype if: * the value for each discriminant given by an expression belongs to the subtype @@ -300,7 +398,7 @@ * for each discriminant given by an expression in the discriminant_constraint, the discriminant value has the value imposed by the discriminant constraint; and -* for each discriminant given by a discrete_range in the discrminant_constraint, +* for each discriminant given by a discrete_range in the discriminant_constraint, the discriminant value belongs to the subtype or range imposed by the discriminant constraint. @@ -328,10 +426,12 @@ For both changes: -Replace 4.9.1(1.1/2): +Replace 4.9.1(1.2/2): * both are static and: - - if discrete, each value that belongs to one also belongs to the other; + - if discrete, each value that belongs to one also belongs to the other, + each have equal lower bounds and equal upper bounds, and both or + neither are discontiguous; - if real, each have equal lower bounds and equal upper bounds; - if index constraints, have equal corresponding bounds; - if discriminant constraints: @@ -347,6 +447,17 @@ statically matching of they define the same set of values. For instance range 1 .. 4 matches when 1 | 2 | 3 | 4 as well as when 4 | 1 .. 2 | 3. + We have to include the bounds in the first bullet in case the constraints + represent null ranges. We don't want range 6 .. 3 to match range 3 .. 2 + as the bounds are different, even though the value sets are the same + (there are no values that belong to either range). + + We have to include whether constraints are discontiguous to handle cases + where null ranges are involved: + when 10 .. 3 should not match when 10 .. 9 | 7 .. 3 + when 5 .. 5 should not match when 5 | 7 .. 3 + as the latter is treated as discontiguous in both cases. + AARM Reason: The rules for discriminant constraints are complex so that appropriate matching can be applied to discrete_ranges. The rules imply that a value and @@ -389,12 +500,70 @@ there isn't enough value to dynamic ones (and having fully dynamic ones would make implementing looping, the 'First and 'Last attributes into a nightmare). + +We considered several syntax choices. "When" is the obvious choice, as it +currently introduces other choice lists. But it reads weirdly: + + subtype My_List is Natural when 1 | 3 | 5; + +We also considered "with", but that seems to be used by a different planned enhancement. + +Several people suggested using "in". But that is unfortunately ambiguous. Consider +the following case limbs if "in" was used in list_constraints: + when Natural in 1 .. 3 => + when Object in 1 .. 3 => +The first is a subtype_indication with a list_constraint, while the second is a +Boolean expression. Using this grammar would require unifying subtype_indications +with expressions, and that would bring up the problem of resolution. Currently, +subtype_indications are resolved with no context, while expressions are resolved +with particular expected types. Using a unified grammar would require doing +both at the same time. That's not promising. + +Adding additional syntax after 'in' doesn't really help because that would require +additional lookahead to get an unambiguous grammar; but few parsers (and especially +parser generators) allow more than one token of lookahead. + +Thus we reverted to 'when', despite the uninspiring syntax. + + +Unfortunately, the syntax ambiguity noted for the "range" still occurs: + when Natural when 1 .. 10 | 12 | 14 => +is ambiguous as to where the list_constraint ends. + +To fix the original ambiguity problem, we appear to have two choices: + (1) Add parens around the choice list in a list_constraint: + when Natural when (1 .. 10 | 12) | 14 => + (2) Make list_constraints illegal in discrete_choice_lists. In that + case, the example given here is syntactically illegal. + +Both of these possibilities work. We used the second, since there +doesn't seem to be much need to support list_constraints directly +in choice lists (a named subtype can always be used if necessary). + +We defined the illegality with wording, because otherwise we would have to duplicate +a large part of the grammar. Implementations (at least those using parser +generators) probably would want to duplicate the grammar, because +typcially legality rules are handled long after parsing. + + +A radical alternative would be to add square brackets to Ada's delimiters, +then then just extend range constraints to have choice lists: + range_constraint ::= range '[' discrete_choice_list ']' + Natural range [1 .. 10 | 12 | 14] + +This looks nicely like sets. (We can't use parens here, as that would +require potentially unlimited lookahead to tell apart from the beginning +of an expression.) It also completely avoids the need for funny rules to +eliminate ambiguity. + +The author actually prefers this alternative, but it seems just too radical. + --- We make the use of discontiguous subtypes as array indicies illegal/raise Program_Error. It was briefly thought that we could use techniques similar to those used for holey enumeration types to implement them, but that is not true. -Sliding would also be problematic, as well as holey by-reference slices (as +Sliding would be problematic, as well as holey by-reference slices (as mentioned in the AARM note above). We raise Program_Error to avoid generic contract problems. An alternative @@ -420,19 +589,120 @@ --- +Discontiguous subtypes are allowed in case statement choices, for loops, and +entry families. + +Choices seem obvious, as the syntax is similar and this +could be a useful shorthand for useful sets. + +For loops allow discontiguous subtypes so that iterating over useful sets can +easily be accomplished. Since list_constraints are static, it is reasonably +easy to generate code to iterate in the proper order. (If dynamic constraints +were allowed, this would become more difficult.) + +Protected entry families allow discontiguous subtypes, as such families are +essentially a special parameter which can be used in a barrier. It is expected +that any range of family be allowed in these (for instance, +entry Big_Guy (Natural) (C : Character);) and it would seem weird to +adopt an additional restriction. That's especially true as the motivating +use of discontiguous ranges is to allow better specification of parameters. + +Task entries families also allow discontiguous subtypes as we've generally kept +the rules for protected and task entries the same. This case might be problematic +to implement on some runtime systems (similar to the array cases). However, a +fairly strong case of implementation expense needs to be made to add an inconsistency +to the language. + +--- + +The rules for null ranges in list_constraints are designed to be as simple as possible. +We don't really care what the result is in these cases (these constraints are intended +to be used to define sets, and empty sets are not very interesting), so long as the +following prinicples are followed: + * Identical range and list constraints have the same properties: + -- range 3 .. 1 and when 3 .. 1 have the same bounds (and no values) + * List constraints that represent no elements have bounds that represent a null + range: + -- when 3 .. 1 | 11 .. 10 has bounds 11 .. 1 + If we used the "natural" rule, we'd end end up with bounds of 3 .. 10, which seems + like nonsense. + * All bounds given explicitly participate in the bound calculation; bounds don't + "disappear". Thus: + -- when 3 .. 1 | 5 has bounds 3 .. 5. + +The last principle is necessary to avoid massive changes in semantics when tiny changes +are made to constraints: + -- when 3 .. 4 | 5 has bounds 3 .. 5. + -- when 3 .. 3 | 5 has bounds 3 .. 5. + -- when 3 .. 2 | 5 has bounds 3 .. 5. + +An alternative rule that was considered was to completely ignore null ranges unless +the list_constraint only includes null ranges. That complicates the rules, and also leads +to unusual results -- the last example above ends up with bounds of 5 .. 5. + +We also considered doing without the special rule making constraints with null ranges +automatically discontiguous. Combined with the above suggestion, pretty much all constraints +with null ranges would be allowed in arrays and slices. That allows very confusing +constructs: + Arr (Natural when 3 .. 2 | 5) + +A final alternative considered was simply to make null ranges illegal in list_constraints. +Since choices in a list have to be static, that is possible. But it could cause trouble +with length constants used in constraints: + + Natural when 1 | 3 | 5 .. Array_Length + +would become illegal if Array_Length < 5. As such expressions are common in aggregates, +it does not seem unlikely that they would occur in list_constraints as well (which +are modeled on aggregates). + +--- + An alternative semantics considered for extended discriminant constraints was for constraints with subtypes to be "partial constraints". A partial constraint -would not make the subtype definite. However, that leads to additional -complications not found in Ada 2005: constrained, indefinite subtypes. (Well, at -least if you don't believe AI05-0057-1.) - -Since the model is that a discriminant constraint containing a subtype allows -one of a set of discriminants, it makes sense that when an object is created -using that constraint, a random value of the set is used to set the -discriminants. We arbitrarily choose the first value simply so that the value -chosen is the same on all compilers. This avoids the complications of new kinds -of constraints (which would quickly spread to new kinds of generic matching -rules, etc.). +could be reconstrained with single constraints. But that just adds a lot of +complication, and doesn't seem to add anything. So we still consider these +as full constraints. + +We also tried a model that eliminated the need to introduce constrained, +indefinite subtypes. But that effectively made it possible to make mutable objects +of otherwise immutable record types. For instance: + + type TRec (D : Character) is tagged null record; + + type Mutable_TRec is TRec (D => Character); + + Obj : Mutable_Trec := (D => Anything); -- This is illegal with the proposed rules. + + Obj := (D => Anything_Else); -- We can change the discriminant to anything we like. + +Anything and Anything_Else can be any character. This causes problems as the implementation +would have to treat Obj as mutable. That might require "assuming the largest size" or +adding a runtime mechanism for discontiguous components; in either case adding overhead +not otherwise required. + +We also considered a model that Obj is always constrained by the default discriminant +value (Character'First in this example). But that is weird, as in all other Ada cases +the default discriminant can be replaced explicitly by the initial expression. + +Obj is illegal with the proposed rules. If TRec had been a mutable record type, then +it would have been legal. We considered making all such subtypes indefinite, but that +doesn't seem to buy anything: if the parent type is mutable, there is no implementation +or conceptual problem with mutable objects and components. + +When such a constraint is definite, we need to have a default value for the discriminant +(since one may not be given in the declaration). Since the model is that a discriminant +constraint containing a subtype allows one of a set of discriminants, it makes sense +that when an object is created using that constraint, a random value of the set is used +to set the discriminants. We arbitrarily choose the first value simply so that the value +chosen is the same on all compilers. + +One nice result of changing the definition of definiteness slightly is that it avoids +generic complications. Whether a generic formal is definite or indefinite is a matter +of syntax and existing matching rules. In addition, if an indefinite generic formal +is instantiated with a definite subtype, there might be subtypes that would be legal +in the instance that are not legal in the generic. That is already covered by the +existing generic legality rules. Thus, nothing new is needed for generics. ---

Questions? Ask the ACAA Technical Agent