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

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

!standard 3.2.2(7)          09-10-16 AI05-0153-1/01
!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:
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.]
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. 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.
AARM Note: 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.
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 elababoration 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 hard to say that it should be illegal.]
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 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 4.9.1(1.1/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:
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 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.}
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).
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 {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 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 discrminant_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.1/2):
* both are static and:
- if discrete, each value that belongs to one also belongs to the other; - 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.
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.
---
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 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 (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.
---
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 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.).
---
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.
---
--!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.]

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

Questions? Ask the ACAA Technical Agent