Version 1.1 of ai22s/ai22-0037-1.txt

Unformatted version of ai22s/ai22-0037-1.txt version 1.1
Other versions for file ai22s/ai22-0037-1.txt

!standard 13.14(7.2/5)          22-01-27 AI22-0037-1/01
!standard 13.14(8/4)
!class ramification 22-01-27
!status work item 22-01-27
!status received 21-06-11
!priority Low
!difficulty Easy
!subject Freezing of static expressions
!summary
No change is made to the language: An occurrence of a static expression is frozen at its occurrence unless it is part of an aspect_specification; in that case it freezes at the freezing point of the enclosing entity or the end of the enclosing declaration list, whichever comes first.
!question
13.14(7.2/5) says:
At the freezing point of the entity associated with an aspect_specification, any static expressions or names within the aspect_specification cause freezing, ...
Consider a static scalar subtype Foo where Foo'First and Foo'Last are static.
In a situation where "Foo" (or "Foo'Range") is pretty much synonymous with "Foo'First .. Foo'Last", does the equivalence break down with respect to freezing (because the cited freezing rule applies in the 2-expression case and no analogous freezing rule applies in the other case)? In particular, is there some legal construct that would violate some freezing rule (but otherwise remain legal) if "Foo" is replaced with "Foo'First .. Foo'Last"? (Yes.)
!recommendation
(See Summary.)
!wording
!discussion
13.14(7.2/5) is a restating of 13.14(8/4) to reflect the special realities of an aspect_specification. Enforcing 13.14(8/4) would not make sense, since the expression hasn't even been resolved yet. The intent of 13.14(7.2/5) is to move the freezing rule of 13.14(8/4) to the resolution point for expressions found in aspect_specifications.
13.14(8/4) is itself an odd rule with counter-intuitive results. It only has a significant effect in a context where an expression does not freeze until some later point; in which case any contained static (sub)expressions cause freezing, but nothing else does. A default expression for a parameter is such a context, so for example:
subtype SInt is Integer range 1 .. 10; subtype DInt is Integer range 1 .. Report.Ident_Int(10); type Rec is null record;
procedure P (S : Integer := SInt'Alignment; -- Freezes SInt D : Integer := DInt'Alignment; -- Does not freeze DInt R : Integer := Rec'Alignment); -- Does not freeze Rec
for SInt'Alignment use 1; -- ERROR: SInt frozen. for DInt'Alignment use 1; -- OK. for Rec'Alignment use 1; -- OK.
This is bizarre, but AARM 13.14(1.m) gives a Language Design Principle that this oddity is intended:
The compiler should be allowed to evaluate static expressions without knowledge of their context. (I.e. there should not be any special rules for static expressions that happen to occur in a context that requires a static expression.)
Additionally, 13.14(1.b) claims that the compiler needs to be able to find the values of static expressions to properly do overload resolution in a handful of cases. Thus it seems that it is necessary to freeze at least some static expressions very early. (To see this, imagine a 2-dimensional array like "type Two_D is array (Boolean, Character) of Integer", and then imagine an expression like Two_D'First(T'Alignment) -- we don't even know the type of the expression without freezing type T to determine the value of T'Alignment.)
If we assume that 13.14(8/4) is necessary, it follows that we have to have a similar rule for expressions in aspect_specifications. Thus, 13.14(7.2/5) is needed (even if bizarre).
One can illustrate the effect of 13.14(7.2/5) with Dynamic_Predicates, which usually aren't frozen until they are used, or the end of the enclosing specification or declarative part.
package BDE0012 is subtype SInt1 is Integer range 1 .. 10; subtype SInt2 is Integer range 1 .. 10; subtype SInt3 is Integer range 1 .. 10; subtype SInt4 is Integer range 1 .. 10;
subtype OInt1 is Integer with Dynamic_Predicate => OInt1 in SInt1; -- (1)
subtype OInt2 is Integer with Dynamic_Predicate => OInt2 in SInt2'range; -- (2)
subtype OInt3 is Integer with Dynamic_Predicate => OInt3 in SInt3'First .. SInt3'Last; -- (3)
subtype OInt4 is Integer with Dynamic_Predicate => OInt4 in SInt4'First+1 .. SInt4'Last-1; -- (4)
private for SInt1'Alignment use 1; -- OK. for SInt2'Alignment use 1; -- OK. for SInt3'Alignment use 1; -- ERROR: Frozen by end of declaration list. for SInt4'Alignment use 1; -- ERROR: Frozen by end of declaration list. end BDE0012;
For (1), Sint1 is not a static expression, so SInt1 is not frozen until "end P".
For (2), SInt2'Range also is not a static expression. (Note that 4.9(7) requires an attribute_reference to "denote a scalar value"; SInt2'Range denotes a scalar range, not a scalar value.) So again, SInt2 is not frozen until "end P".
For (3), however, SInt3'First and SInt3'Last are static expressions (they do "denote scalar values"), so they are freezing at the "private". Thus the later attribute_definition_clause is too late, and therefore is illegal. One can workaround the issue by using SInt3 or SInt3'Range in place of the explicit range.
(4) demonstrates that it is not always possible to workaround the issue by rewriting the expression; it sometimes is necessary to move the use that requires an unfrozen type.
Luckily, none of this has much effect if one uses only aspect_specifications to set aspects, as they are evaluated at the freezing point -- whereever that turns out to be.
!ACATS test
It would be possible to write an ACATS B-Test to check 13.14(7.2/5) (the second example in the !discussion could be used unmodified, or with additional cases). However, such an example would probably not occur in practice, as they would require mixing aspect_specifications and attribute_definition_clauses in order to detect an error. Most actual uses will use all of one or all of the other. As such, it would seem to be making work for compiler implementers rather than testing something actually likely.
Note that are already a couple of examples in test BDE0008 of 13.14(8/4). Additional examples could make sense (and would not be as unlikely as tests for 13.14(7.2/5).
!appendix

From: Steve Baird [privately]
Sent: Thursday, June 10, 2021  12:01 PM

I know, I should be reviewing draft 31 instead of this, but this question came
up at AdaCore.

We've got a rule about static expressions and freezing:
    At the freezing point of the entity associated with an
    aspect_specification, any static expressions or names within the 
    aspect_specification cause freezing, ...
  
Consider a scalar subtype Foo where Foo'First and Foo'Last are static.

In a situation where "Foo"  (or "Foo'Range") is pretty much synonymous with
"Foo'First .. Foo'Last", does the equivalence break down with respect
to freezing (because the cited freezing rule applies in the 2-expression case
and no analogous freezing rule applies in the other case)?
In particular, is there some legal construct that would violate some freezing
rule (but otherwise remain legal) if "Foo" is replaced with
"Foo'First .. Foo'Last"?

Enquiring minds want to know.

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

[Editor's note: I've left out the original thread, since it came to incorrect
conclusions.]

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

From: Randy Brukardt [privately]
Sent: Wednesday, January 26, 2022  11:08 PM

It turns out that there is a Language Design Principle - 13.14(1.m):
 
 The compiler should be allowed to evaluate static expressions without 
 knowledge of their context. (I.e. there should not be any special rules for
 static expressions that happen to occur in a context that requires a static
 expression.)
 
This seems like a nonsense principle to me; it doesn't make sense to try to 
evaluate a piece of something when the entire thing cannot be evaluated. 
Nevertheless, it is the reasoning behind 13.14(8/4). And 13.14(7.2/5) is an
attempt to keep 13.14(8/4) for aspect specifications, but to wait until the
expression is resolved at least.
 
13.14(8/4) means that something like T'First or T'Size is freezing, even in a 
default expression, when nothing else that appears in a default expression is 
freezing. Ergo:
 
     subtype SInt is Integer range 1 .. 10;
     subtype DInt is Integer range 1 .. Report.Ident_Int(10);
     type Rec is null record;
 
     procedure P (S : Integer := SInt'Alignment; -- Freezes SInt
                         D : Integer := DInt'Alignment; -- Does not freeze DInt
                        R : Integer := Rec'Alignment); -- Does not freeze Rec
 
     for SInt'Alignment use 1; -- ERROR: SInt frozen.
     for DInt'Alignment use 1; -- OK.
     for Rec'Alignment use 1; -- OK.
 
I would prefer to repeal nonsense like this. The only time whether something 
is static is relevant is when it appears in a context that requires a static 
expression. However, there already is an ACATS test BDE0008 that tests a 
couple of cases that are similar to this (a use of an enumeration literal as
a default expression of a discriminant; and a string literal used as a default
parameter value). There isn't anything quite as silly as the above, but still 
it seems the rule is already in compilers.
 
Your example (written in the form of an ACATS test):
 
package P is
     subtype SInt1 is Integer range 1 .. 10;
     subtype SInt2 is Integer range 1 .. 10;
 
     subtype OInt1 is Integer
         with Dynamic_Predicate => OInt1 in SInt1; -- No freezing until the 
						   -- predicate is used.
 
    subtype OInt2 is Integer 
         with Dynamic_Predicate => OInt2 in SInt2'First .. SInt2'Last; 
                    -- Freezes at the end of the declaration list..
 
private -- SInt2 is frozen here, as it appears in a static expression in 
        -- an aspect_specification; SInt1 is not frozen, as it is not a 
        -- static expression.
    for SInt1'Alignment use 1;  -- OK.
    for SInt2'Alignment use 1; -- ERROR: Frozen by end of declaration list.
end;
 
So, the answer is that indeed, one can get different freezing depending on how
you write the expression (at least when discrete subtypes are involved). In 
this example, there is an easy workaround, but obviously that isn't true if 
the bounds are more complex expressions:
 
    subtype OInt3 is Integer 
         with Dynamic_Predicate => OInt3 in SInt2'First+1 .. SInt2'Last-1;
		-- No way to write this without premature freezing.
 
Even so, it doesn't seem worth changing anything, as the original rule 
(13.14(8/4)) is already tested. We should remain consistent with nonsense 
already in the language absent a good reason to make a general change. And 
it's hard to tell what would happen if the static expr parts of 13.14(7.2/5)
and 13.14(8/4) were just removed from the language (or modified to be 
context-specific).
 
Given all of this, I don't think it pays to write this up as an AI; I can't 
see a change to make. (Maybe an AARM note somewhere.) Thoughts???
 
****************************************************************

From: Randy Brukardt [privately]
Sent: Wednesday, January 26, 2022  11:12 PM

I suppose there might be value to writing it up as a Ramification to capture 
the jist of this discussion. Your (later) point that whether an initial 
expression of a constant is static is still relevant even though it is not 
a context that requires a static expression is also true (another Ada bug, but
one we're surely not going to fix) - it makes the design principle slightly 
more sensible (but a initial expression freezes anyway, so it doesn't matter
if it is static or not - those rules only trigger when the expression wouldn't
otherwise be freezing).

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

From: Tucker Taft [privately]
Sent: Thursday, January 27, 2022  10:04 AM

I didn't see a discussion of "Foo" vs. "Foo'Range".  I believe Foo'Range should
be exactly equivalent to "Foo'First .. Foo'Last" from a freezing point of view,
but "Foo" by itself is different, and does not produce freezing since there is
no attribute reference in sight.  I don't have a good rationale for that, but 
I think it should follow from the strict equivalence between Foo'Range and 
Foo'First .. Foo'Last.

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

From: Randy Brukardt [privately]
Sent: Thursday, January 27, 2022  10:45 PM

Doesn't need to be a discussion, as Foo and Foo'Range are equivalent for this
purpose.
 
Remember, the cases we're discussing are those where a static expression 
(usually an attribute) freezes some type while other expressions do not freeze
similar uses. This has nothing to do with things being attributes: the example
I showed has three uses of T'Alignment, one of which freezes T and the other
two  which don't.
 
4.9(7) says that a static expression is (in part) "an attribute_reference that 
denotes a scalar value, and whose prefix denotes a static scalar subtype;"
Foo'Range does not denote a scalar value (it denotes a scalar range, not the
same thing at all), and thus it is not a static expression. Ergo, in a 
context/place where a static expression freezes but a non-static expression 
does not (such as a default expression), Foo'range does not freeze while 
Foo'First .. Foo'Last does.
 
This is just one of the many anomolies caused by the entire idea of giving 
special freezing properties to static expressions. There are supposedly some
reasons for that (some are detailed in the AARM notes in 13.14), so getting 
rid of that seems like a bad idea just to make things more regular (especially
as in normal use, none of these things will matter, especially with modern 
programmers that only use aspect clauses and never 
attribute_definition_clauses).
 
Probably we do need a Ramification AI to make these things clear.

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

Questions? Ask the ACAA Technical Agent