Version 1.5 of ai05s/ai05-0153-2.txt

Unformatted version of ai05s/ai05-0153-2.txt version 1.5
Other versions for file ai05s/ai05-0153-2.txt

!standard 3.2.2(7)          09-10-29 AI05-0153-2/04
!class Amendment 09-10-16
!status work item 09-10-16
!status received 09-10-16
!priority Medium
!difficulty Medium
!subject Discontiguous scalar constraints and extended discriminant constraints
!summary
Add list scalar constraints to the language, and extend the available discriminant constraints.
!problem
Ada's constraints are a powerful way to enhance the contract of an object (including formal parameters). But the constraints that can be expressed are limited.
For instance, it isn't possible to specify that a record type may have any of several discriminant values - for discriminants we can only specify a single value or allow all discriminants.
!proposal
(See Summary.)
!wording
List scalar constraints:
Modify 3.2(7/2):
The set of possible values for an object of a given type can be subjected to a condition that is called a constraint (the case of a null constraint that specifies no restriction is also included); the rules for which values satisfy a given kind of constraint are given in 3.5 for range_constraints {and list_constraints}, 3.6.1 for index_constraints, and 3.7.1 for discriminant_constraints. The set of possible values for an object of an access type can also be subjected to a condition that excludes the null value (see 3.10).
[Editor's note: I'm dubious that there even should be a list of constraint types in this paragraph and the following one, because it ignores the obsolescent kinds of constraints (meaning it is a lie), and is a clear maintenance hazard (as I just happened across these paragraphs looking for something else, otherwise I never would have noticed them).]
Modify 3.2(9/2):
A subtype is called an unconstrained subtype if its type has unknown discriminants, or if its type allows range, {list,} index, or discriminant constraints, but the subtype does not impose such a constraint; otherwise, the subtype is called a constrained subtype (since it has no unconstrained characteristics).
Replace 3.2.2(6) with:
scalar_constraint ::= range_constraint | list_constraint | digits_constraint | delta_constraint
Add after 3.5(4):
list_constraint ::= when discrete_choice_list
[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 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 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, 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.
[Editor's Note: Steve Baird suggests that this concept be called "complex" or some other such name as "discontiguous" is confusing in the case of null ranges. That's a reasonable point; but the best term isn't obvious and the wording changes needed are purely search-and-replace, so I left it for discussion.]
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 (if any) in the discrete_choice_list are expected to be of the type determined by the subtype_mark of the subtype_indication. The type of any discrete_range in the discrete_choice_list shall resolve to that of the subtype_mark of the subtype_indication.
Legality Rules
An others choice may not appear in a discrete_choice_list used in a choice_list_constraint.
[Author's note: We probably could figure out a useful meaning for "others" in this case, but it is unlikely to be intuitive.]
The type of a subtype_indication containing a list_constraint shall be discrete.
[Author's note: This concept could be extended to all scalar types, but that is dubious as equality operations on real types are often not recommended; it seems bad to build bad practice into the language. Restricting real choice_lists to only ranges would fix this, but seems inconsistent with the rest of the language. Also, allowing real choice_lists would require separating the syntax.]
The expressions and discrete_ranges of the discrete_choice_list of a list_constraint shall be static.
[Author's note: See the AARM note above. Perhaps we could make a useful definition without requiring this, but it would be a lot harder to describe and implement.]
Modify 3.5(7):
A constrained scalar subtype is one to which a range constraint {or list constraint} applies. The range of a constrained scalar subtype {with a range constraint} is the range associated with the range constraint of the subtype. The range of a constrained scalar subtype {with a list constraint} is the range determined by the lower bound and upper bound of the constraint. The range of an unconstrained scalar subtype is the base range of its type.
[Author's note: Most places where the range of a list constraint would be used have special rules so that we don't actually use it. We define it so that S'First and S'Last are well-defined.]
Replace 3.5(8) with:
A range is compatible with a scalar subtype S if and only if:
* it is a null range; or
* S has a list constraint and each value of the range belongs to the discrete_choice_list; or
* S is unconstrained or has a range constraint and each bound of the range belongs to the range of the subtype.
A range_constraint is compatible with a scalar subtype if and only if its range is compatible with the subtype.
A list_constraint is compatible with a scalar subtype if and only if
* the value of each expression of the discrete_choice_list satisfies the list constraint of the subtype, or if the subtype does not have a list constraint, belongs to the range of the subtype; and
* each range of the discrete_choice_list is compatible with the subtype.
AARM Discussion:
These rules are intended so that a user (and compiler) only need worry about the most recent subtype declaration. The elaboration of a scalar subtype will fail if its constraint includes values outside of those that satisfy its parent subtype.
This means that legality rules and the definition of dynamic semantics for the use of list_constraints do not have to worry about ancestors of the type
End AARM Discussion.
Add after 3.5(9):
The elaboration of a list_constraint consists of the elaboration of the discrete_choice_list. Expressions and discrete_ranges of the discrete_choice_list are evaluated in an arbitrary order, and are converted to the type of the subtype_mark of the subtype_indication which contains the list_constraint.
[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. 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):
An index subtype shall not statically denote a discontiguous subtype.
Add as the last sentence of 3.6(21):
The elaboration of an array_type_definition raises Program_Error if the index subtype is a discontiguous subtype.
AARM Reason: We don't want to create "holey" array types. By raising Program_Error, we prevent generic contract problems. But we also have a legality rule so when it is statically known (outside of a generic) we detect the problem at compile-time.]
Add after 3.6.1(5):
The discrete_range of an index_constraint shall not statically denote a discontiguous subtype.
Add as the last sentence of 3.6.1(8):
The elaboration of an index_constraint raises Program_Error if any discrete_range is a discontiguous subtype.
AARM Reason: We don't want to create "holey" array subtypes. By raising Program_Error, we prevent generic contract problems. But we also have a legality 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
The discrete_range of a slice shall not statically denote a discontiguous subtype.
Add as the last sentence of 4.1.2(7):
The evaluation of a slice raises Program_Error if any discrete_range is a discontiguous subtype.
AARM Reason: We don't want to create "holey" slices, especially as slices can be required to be passed by reference (for by-reference component types). By raising Program_Error, we prevent generic contract problems. But we also have a legality rule so when it is statically known (outside of a generic) we detect the problem at compile-time.]
[Note: 4.5.2(30/2) uses "satisfies", so no changes need to be made to allow this to work in membership operations. Similarly, 4.6(51/2) uses "satisfies", so no wording changes are needed for subtype conversions.]
Modify 4.9(29):
* A scalar constraint is static {if it has a list_constraint, }if it has no
range_constraint, or one with a static range;
[Author's note: strictly speaking, we don't need to modify this sentence, as a scalar constraint with a list_constraint "has no range_constraint". But that's very tricky!]
[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):
AARM Ramification: This implies that for a static discontiguous subtype, only the values belonging to that subtype should be covered. If instance, if we have:
subtype Small_Odds is Natural when 1 | 3 | 5 | 7;
case Small_Odds'(My_Func) is when 1 | 3 => ... when 5 | 7 => ... end case;
It would be illegal to give any of the values 2, 4, and 6 here, even though they are in Small_Odds'range.
Add an AARM note after 5.5(9):
AARM Ramification: This description implies that for loops will properly iterate over just the values defined by a discontiguous subtype. For instance, if we have
subtype Small_Odds is Natural when 1 | 3 | 5 | 7;
for Val in Small_Odds loop ...
Val will take the values 1, 3, 5, and 7. It will not take the values 2, 4, and 6, even those are included in Small_Odds'range.
Note that the wording requires that the values are produced in order, even if they are not given in order in the constraint. That's important, as:
subtype Small_Odds_2 is Natural when 7 | 3 | 5 | 1;
represents the same constraint (and it will statically match Small_Odds). End AARM Ramification.
-------------------------
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. No other good ideas leapt out at me, so I didn't define a term at all.
Steve Baird suggests converting the normative sentence above into a series of bullets, which would be much more readable. I didn't take that suggestion because this sentence is dead center in the middle of a paragraph, and his suggestion provided no sane way to deal with the implicit spliting of that single paragraph in many. If we follow that approach, the entire paragraph should be reordered.]
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
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. 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 ::= [discriminant_selector_name {|discriminant_selector_name} => discriminant_value
discriminant_value ::= expression | discrete_range
[Editor's note: discrete_range includes subtype_indication, so it includes list constraints (which aren't a range, but changing the name of the non-terminal would be rather disruptive).]
Modify 3.7.1(6):
The expected type for the expression {or discrete_range} in a discriminant_association is that of the associated discriminant(s).
Modify 3.7.1(8):
...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 discriminated subtype if:
* the value for each discriminant given by an expression belongs to the subtype
of the corresponding discriminant; and
* the constraint of the subtype for each discriminant given by a discrete_range
is compatible with the subtype of the corresponding discriminant.
[Editor's note: Compatibility is only defined for constraints and ranges; we really want compatibility of subtypes here. Maybe there is a better way to write this, but there are really 4 cases to worry about: subtype with range constraint, subtype with list constraint, subtype_mark alone, and range alone.]
Replace 3.7.1(11) with
A composite value satisfies a discriminant_constraint if and only if: * 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 discriminant_constraint,
the discriminant value belongs to the subtype or range imposed by the discriminant constraint.
Modify 3.7.1(12):
For the elaboration of a discriminant_constraint, the expressions {and discrete_ranges} in the discriminant_associations are evaluated in an arbitrary order and converted to the type of the associated discriminant (which might raise Constraint_Error see 4.6); the expression of a named association is evaluated (and converted) once for each associated discriminant. The result of each evaluation and conversion is the value{, range, or subtype} imposed by the constraint for the associated discriminant.
[Editor's note: Although I find it weird, the "converted to the type of the discriminant" wording is used for discrete_choices in aggregates, from which discrete_ranges come. So I used the same wording here.]
Modify 4.9(31):
* A discriminant constraint is static if each expression {or discrete_range} of
the constraint is static, and the subtype of each discriminant is static.
---
For both changes:
Replace 4.9.1(1.2/2):
* both are static and:
- 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:
* for each discriminant given by an expression in one, the the corresponding discriminant is also given by an expression and the discriminant values are equal; and
* for each discriminant given by a discrete_range in one, the the corresponding discriminant is also given by a discrete_range and the constraints, ranges, or subtypes statically match.
AARM Ramification:
The first bullet means that list constraints and range constraints can be 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 range with a single value do not match. For example:
Rec(2) does not match Rec(2..2).
[Editor's note: I suppose we could have allowed this case, but it would have made the rules even more complicated.]
!discussion
If adopted, this proposal would replace AI05-0158-1, as the subtype_indications proposed here could be used in membership operations. Having two nearly identical syntaxes in an operation would be horrible, given that they would have different rules.
---
An appealing alternative approach would have been to extend range_constraints to take a discrete_choice_list rather than a range, and then apply the array aggregate staticness rules to them: there can only be a single dynamic choice that has to stand alone.
Unfortunately, that idea would make the grammar for discrete_choice_list ambiguous. For instance:
when Natural range 1 .. 10 | 12 | 14 =>
Is this a single range_constraint with a discontiguous subtype, or a range_constraint and two choices? One could arbitrarily make this just a subtype_indication, but that would be subtly incompatible:
when Natural range 1 .. 10 | (-1) =>
would be illegal if this is treated as a single subtype indication (-1 is not in Natural), but could be legal if treated as a choice_list.
Thus we need a different keyword to introduce a list_constraint in order to avoid this incompatibilty. We then require all list constraints to be static, as 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 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 approach would be to add an indication that discontiguous subtypes are OK to discrete and integer formal subtypes. Something like:
type Fooey is range <> when <>;
would work.
Then, discontiguous subtypes would not be allowed to match the existing generic discrete and integer formals, while array operations would be illegal for the new formal types.
This seems like killing an ant with a bazooka to the author (a lot of complication for something simple) and it also would reduce the usefulness of many existing generics. For instance, Ada.Text_IO.Integer_IO could not be instantiated with a discontiguous subtype. Obviously, this could be fixed for language-defined generics, but the majority of user-defined generics would not allow discontiguous subtypes without modification (whether it matters or not).
The author believes the Program_Error solution to be less disruptive; moreover, most compilers could give a warning should the bad situation actually occur in an instance.
---
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 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.
---
We don't allow subtypes to specify discriminant values for discriminants that have an access type because there would be no obvious value to use for the discriminant if the constraint was used to declare an object. We could allow that if we had used the partial constraint model instead, but that would also mean that we couldn't use discrete_range in the syntax.
!example
In a compiler familar to the author, type Symbol_Ptr references a symbol table entry. Most routines that take a symbol table entry only allow certain kinds of entry. It would be valuable to be able to specify those kinds of entry as part of the profile of the routine.
A simplified example:
type Entity_Kind is (Proc, Func, A_Type, A_Subtype, A_Package, An_Entry);
type Symbol_Record (Entity : Entity_Kind) is record ...
type Symbol_Ptr is access all Symbol_Record;
subtype Type_Symbol_Ptr is not null Symbol_Ptr (A_Type); subtype Callable_Symbol_Ptr is not null Symbol_Ptr (Entity_Kind when Proc | Func | An_Entry);
function Type_Size (A_Type : Type_Symbol_Ptr) return Size_Type;
procedure Generate_Call_Parameters (Callee : Callable_Symbol_Ptr; ...);
Now, a call to Type_Size or Generate_Call_Parameters with a pointer to the wrong kind of symbol record will be detected at the call site rather than at some later point. The call site is closer to the source of the error; in addition, it is possible that the compiler can prove that the predicate will succeed and be able to remove the check altogether. That can't happen for a check inside of a subprogram.
--!corrigendum 3.2.2(7)
!ACATS test
Add ACATS B and C tests for this feature.
!appendix

[Editor's note: Much of the mail that inspired these ideas can be found
in AI05-0153-1 - June 19-23, 2009.]

****************************************************************

From: Tucker Taft
Date: Wednesday, June 24, 2009  8:17 PM

I think I am leaning Randy's way on this for scalar types, allowing sequences
rather than simple ranges. I'll admit I hate the idea of using them in array
indices, and I would hope we could disallow using non-static sequences for array
index types, though there are generic contract model issues.  Perhaps we could
solve this by saying that you can only use static sequences when defining a
subtype, but possibly allow non-static sequences in some other contexts, such as
memberships and for-loops.

This has some vague symmetry with the rules for named array aggregates, where
the choices all have to be static, unless there is exactly one choice consisting
of one range, in which case it can be dynamic.  So we are saying that in various
contexts, you can either use a single range, which can be dynamic, or you can
use a static discontiguous sequence of values.  You can apply a non-static range
on top of a static discontiguous sequence of values, but I guess that is no
worse that having a dynamic subrange of a holey enumeration type.

This doesn't really address the partially-constrained discriminated type issue.
In that case, using a "predicate" actually seems better, since the type remains
unconstrained in most ways, but there is a restriction on the discriminants.

For array types, I'll tell you one thing I would love is a partially constrained
array type, where I could specify the low bound while letting the high bound
vary. But that is probably better handled with some kind of syntax like:

     type Nice_Array is array(Positive range 1..<>)
       of Element;

or

     subtype Nice_String is String(1..<>);

Using a predicate could work:

     subtype Nice_String is String
       with Predicate => Nice_String'First = 1;

but that would not be as easily recognizable by the compiler, and would probably
not do the right thing with respect to "sliding" where you would clearly like
parameters to "slide" so that their low bound was one, rather than failing if
the actual'First wasn't one.

Similarly, for partially constrained discriminants, the general-purpose
"Predicate" idea might be overkill, but we would have to invent the notion of a
partially-constrained discriminated type.  E.g.:

    type Text(Max_Length : Positive) is ...

    subtype Shorter_Text is Text(Max_Length in 1..10);

or something like that, as opposed to:

    subtype Shorter_Text is Text
      with Predicate => Shorter_Text.Max_Length in 1..10;

So it seems that two separate AIs, one for discontiguous scalar subtypes, and
the other for partially-constrained composite types, might make sense.

****************************************************************

From: Steve Baird
Date: Wednesday, June 24, 2009  8:32 PM

This is sounding better and better.
Perhaps the whole "arbitrary predicate" idea was overkill.
These new scalars and partially constrained discriminated could play well
together. We have a variant record with a component that only exists if the
discriminant belongs to some noncontiguous set of discriminant values. It would
be natural to want to define a subtype of the discriminated type that is
constrained to that set of discriminant values. To accomplish this, we declare
the appropriate scalar subtype and then use that in declaring the desired
subtype of the discriminated type. We can eliminate all the generic contract
model issues (I think) and the restrictions on array indexing, for loops, and
(of course) entry families by following the holey enumeration type model.

****************************************************************

From: Randy Brukardt
Date: Wednesday, June 24, 2009  8:40 PM

> I think I am leaning Randy's way on this for scalar types, allowing
> sequences rather than simple ranges.
> I'll admit I hate the idea of using them in array indices, and I would
> hope we could disallow using non-static sequences for array index
> types, though there are generic contract model issues.  Perhaps we
> could solve this by saying that you can only use static sequences when
> defining a subtype, but possibly allow non-static sequences in some
> other contexts, such as memberships and for-loops.

That's essentially what I was thinking. Static discontiguous sequences are not
too awful to implement (they're pretty close to holey enumeration types,
although as I previously noted, the implementation scheme used by Janus/Ada for
those won't work, at least without a lot of adjustment), dynamic ones are not so
obvious.

> This has some vague symmetry with the rules for named array
> aggregates, where the choices all have to be static, unless there is
> exactly one choice consisting of one range, in which case it can be
> dynamic.  So we are saying that in various contexts, you can either
> use a single range, which can be dynamic, or you can use a static
> discontiguous sequence of values.  You can apply a non-static range on
> top of a static discontiguous sequence of values, but I guess that is
> no worse that having a dynamic subrange of a holey enumeration type.

Yes, I think this is correct.

> This doesn't really address the partially-constrained discriminated
> type issue.  In that case, using a "predicate"
> actually seems better, since the type remains unconstrained in most
> ways, but there is a restriction on the discriminants.

Right, so long as the predicate only can read the bounds/discriminants and
static stuff and not arbitrary other junk.

> For array types, I'll tell you one thing I would love is a partially
> constrained array type, where I could specify the low bound while
> letting the high bound vary.
> But that is probably better handled with some kind of syntax like:
>
>      type Nice_Array is array(Positive range 1..<>)
>        of Element;
>
> or
>
>      subtype Nice_String is String(1..<>);
>
> Using a predicate could work:
>
>      subtype Nice_String is String
>        with Predicate => Nice_String'First = 1;
>
> but that would not be as easily recognizable by the compiler, and
> would probably not do the right thing with respect to "sliding" where
> you would clearly like parameters to "slide"
> so that their low bound was one, rather than failing if the
> actual'First wasn't one.

Right. I was thinking about this, but of course nobody was very excited about
partially constrained types in the past. (I suppose if we make that a
first-class language concept, then the S'Class controversy can be put to rest,
as the implementation and semantic objection would disappear. I'm not sure that
is an advantage or disadvantage. :-)

> Similarly, for partially constrained discriminants, the
> general-purpose "Predicate" idea might be overkill, but we would have
> to invent the notion of a partially-constrained discriminated type.
> E.g.:
>
>     type Text(Max_Length : Positive) is ...
>
>     subtype Shorter_Text is Text(Max_Length in 1..10);
>
> or something like that, as opposed to:
>
>     subtype Shorter_Text is Text
>       with Predicate => Shorter_Text.Max_Length in 1..10;

I'm not as sure about this one, since we would often want to get discontiguous
sets of enumerations involved. For instance, the motivating example (in the
original AC-157, at least):

      subtype Callable_Symbol is Symbol_Ptr
         with Predicate => Callable_Symbol.Selector in (PROC | FUNC | INST_PROC | INST_FUNC | AN_ENTRY);

vs.

      subtype Callable_Symbol is Symbol_Ptr (Selector in (PROC | FUNC | INST_PROC | INST_FUNC | AN_ENTRY));

[Symbol_Ptr is an access to unconstrained record, of course.]

I guess both work, presuming that the discontiguous sequence is allowed here.

> So it seems that two separate AIs, one for discontiguous scalar
> subtypes, and the other for partially-constrained composite types,
> might make sense.

Yup. Just like I've been saying all day. ;-)

****************************************************************

From: Tucker Taft
Date: Thursday, June 25, 2009  6:50 AM

I had some nasty thoughts about using discontiguous subtypes as array indices,
and concluded it really isn't worth the trouble.  In particular, slicing and
sliding of such arrays would be really nasty. The hoped-for equivalence with
holey enumeration types doesn't really work, since these are *sub*types, and so
you would expect to be able to have an unconstrained array type, and then be
able to declare objects of the type with different discontiguous subtypes as
bounds, or expect sliding to work between such beasts.

The real point of these discontiguous subtypes is to use them as choices in case
statements, and that leads to a desire to use them as parameter and result
subtypes, and local variable subtypes, and in membership tests and
qualification.  But I really see *no* need to use them as array indices.
Furthermore, because they would be non-static if passed as a formal scalar type,
they would lose their value in case statements, and hence I see little need to
pass them to a generic.  I also think we should disallow "'Range" on them,
because it is too confusing to decide whether Discontig_Subt'Range means the
discontiguous range of the subtype, or the contiguous (and probably useless)
range D_S'First .. D_S'Last.

So after thinking these could be used as "first class" subtypes, I now think we
should relegate them to second class status, and in various places require
*contiguous* subtypes.  In particular, we should require contiguous subtypes in
index subtypes and index constraints, before 'Range, and as an actual for a
formal scalar subtype.

I realize making them second class makes the proposal less appealing, but in
thinking about how to support their full generality in index subtypes and index
constraints, I see a truly nasty amount of stupid work ahead, with little or no
benefit.  Disallowing them in generics also is helpful, as it means shared
generics don't need some kind of complicated descriptor just for discontiguous
subtypes.

I suppose we could invent a syntax for potentially discontiguous formal scalar
subtypes, such as:

     type T is range (<>|<>);
or
     type T is (<>|<>);

but this seems to be pretty bad on the cost/benefit ratio scale.

I still think they are useful as choices in case statements (and variant
records), and in the other places mentioned (membership, qualification,
profiles, variables, even loops), but allowing them as index
subtypes/constraints, generic actuals, and before 'Range seems bad news and
definitely more trouble than they are worth.

****************************************************************

From: Steve Baird
Date: Thursday, June 25, 2009  8:59 AM

> I had some nasty thoughts about using discontiguous subtypes as array
> indices, and concluded it really isn't worth the trouble.  In
> particular, slicing and sliding of such arrays would be really nasty.
> The hoped-for equivalence with holey enumeration types doesn't really
> work, since these are *sub*types, and so you would expect to be able
> to have an unconstrained array type, and then be able to declare
> objects of the type with different discontiguous subtypes as bounds,
> or expect sliding to work between such beasts.

You're right, of course. I was thinking these had only the same implementation
issues as arrays indexed by holey enumeration types, but I was confused.

> The real point of these discontiguous subtypes is to use them as
> choices in case statements, and that leads to a desire to use them as
> parameter and result subtypes, and local variable subtypes, and in
> membership tests and qualification.  But I really see *no* need to use
> them as array indices.  Furthermore, because they would be non-static
> if passed as a formal scalar type, they would lose their value in case
> statements, and hence I see little need to pass them to a generic.  I
> also think we should disallow "'Range" on them, because it is too
> confusing to decide whether Discontig_Subt'Range means the
> discontiguous range of the subtype, or the contiguous (and probably
> useless) range D_S'First .. D_S'Last.
>
> So after thinking these could be used as "first class"
> subtypes, I now think we should relegate them to second class status,
> and in various places require *contiguous* subtypes.  In particular,
> we should require contiguous subtypes in index subtypes and index
> constraints, before 'Range, and as an actual for a formal scalar
> subtype.
>

And for a for-loop? I don't feel strongly about this one, but it's not hard to
implement.

As I mentioned earlier, one place I do want to be able to use these guys is in
something like (syntax tentative):

     type Enum is (Aa, Bb, Cc, Dd, Ee, Ff);

     subtype Discontig is Enum in Aa .. Bb | Ee;

     type Variant_Record (Discrim : Enum) is
        record
           case Discrim is
               when Discontig => Foo : Integer;
               when others => null;
           end case;
        end record;

     subtype Has_Foo is Variant_Record (Discontig); or perhaps
     subtype Has_Foo is Variant_Record
       with Predicate Has_Foo.Discrim in Discontig;

This depends on the other half of this AI (or the other AI, if we view this one
as already having been split into two AIs).

> I realize making them second class makes the proposal less appealing,
> but in thinking about how to support their full generality in index
> subtypes and index constraints, I see a truly nasty amount of stupid
> work ahead, with little or no benefit.  Disallowing them in generics
> also is helpful, as it means shared generics don't need some kind of
> complicated descriptor just for discontiguous subtypes.

You've convinced me.

> I suppose we could invent a syntax for potentially discontiguous
> formal scalar subtypes, such as:
>
>     type T is range (<>|<>);
> or
>     type T is (<>|<>);
>
> but this seems to be pretty bad on the cost/benefit ratio scale.

Agreed. And we can always add this later in the unlikely event that we decide
that this is wanted.

> I still think they are useful as choices in case statements (and
> variant records), and in the other places mentioned (membership,
> qualification, profiles, variables, even loops), but allowing them as
> index subtypes/constraints, generic actuals, and before 'Range seems
> bad news and definitely more trouble than they are worth.

And I had my heart set on using discontiguous entry families ...

****************************************************************

From: Bob Duff
Date: Tuesday, November 3, 2009  1:16 PM

Do we have an AI on user-defined constraints?  If not, shall I write one up?

I think user-defined constraints are far more useful than invariants, as defined
here. (I'm not sure why invariants can't behave as user-defined constraints, but
so I've been told. I guess it's just a terminology issue.)

****************************************************************

From: Gary Dismukes
Date: Tuesday, November 3, 2009  1:26 PM

There's already AI05-153-1 (Subtype predicates), and the competing AI05-153-2
(Discontiguous scalar constraints and extended discriminant constraints).

Those came out of Randy's proposal to have user-defined constraints, so we
probably don't need another one.

****************************************************************

From: Bob Duff
Date: Tuesday, November 3, 2009  1:39 PM

Ah, thanks for the info.  I was using the wrong search terms!

These are on the agenda, so I'll come to the meeting prepared to argue in favor.

****************************************************************

From: Robert Dewar
Date: Tuesday, November 3, 2009  1:48 PM

What I most want is non-contiguous subtypes, everything else is secondary. So I
hope these fall out. Particularly in connection with membership tests, and set
notation, these would be very useful, I don't necessarily want loops and arrays
defined on these things, though given the crud with enumeration rep clauses, it
probably doesn't add that much to implementation burdens to accomodate these
(pretty useless) cases.

****************************************************************

From: Randy Brukardt
Date: Tuesday, November 3, 2009  2:00 PM

I suggest reading AI05-0153-2 (although, please wait until I post the latest
version tonight). This proposes "list constraints" (essentially constraints to
sets of values) and works out the wording and semantics needed.

Loops are actually fall out naturally (the existing wording does exactly the
right thing). Arrays, however, are impossible to implement. Imagine an array of
a by-reference type, and then imagine slicing it with a non-contiguous discrete
subtype. Yuck! So arrays are illegal/raise Program_Error for such subtypes.

Anyway, this is all discussed in detail in the AI.

General user-defined constraints don't work, unfortunately, because the
constraints can't properly identify a value for object creation. (There are
other problems as well with calling them "constraints".) I tried a solution
using subtype predicates (that aren't constraints, but rather just something
attached to the subtype), but that proposal made Steve ill (for various good
reasons), so I don't have a lot of hope that the rest of the ARG will like it
much, either.

****************************************************************

From: Bob Duff
Date: Tuesday, November 3, 2009  2:02 PM

> What I most want is non-contiguous subtypes, everything else is
> secondary.

Yes, I want non-contiguous subtypes of enum types.  I also want non-contiguous
constraints applied to a discriminant like "Kind: Node_Kind".

****************************************************************

From: Randy Brukardt
Date: Tuesday, November 3, 2009  2:14 PM

That's exactly what is proposed in AI05-0153-2. But, as I suggested, wait until
tomorrow to read it.

****************************************************************

From: Tucker Taft
Date: Tuesday, November 3, 2009  3:45 PM

I attempted to explain the difference between invariants and constraints in this
AI [AI05-0146-1 - ED].  Read the "problem" section.

The basic difference is that an invariant applies to all values of a (private)
type, whereas a constraint is used to define a subset of the values of some
type.

****************************************************************

From: Robert Dewar
Date: Tuesday, November 3, 2009  6:51 PM

> Yes, I want non-contiguous subtypes of enum types.  I also want
> non-contiguous constraints applied to a discriminant like "Kind: Node_Kind".

Not clear what you have in mind, at least to me, can you give an example?

****************************************************************

Questions? Ask the ACAA Technical Agent