Version 1.3 of 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.
!issue
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; --
D : Integer := DInt'Alignment; --
R : Integer := Rec'Alignment); --
for SInt'Alignment use 1; --
for DInt'Alignment use 1; --
for Rec'Alignment use 1; --
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; --
subtype OInt2 is Integer
with Dynamic_Predicate => OInt2 in SInt2'range; --
subtype OInt3 is Integer
with Dynamic_Predicate => OInt3 in SInt3'First .. SInt3'Last; --
subtype OInt4 is Integer
with Dynamic_Predicate => OInt4 in SInt4'First+1 .. SInt4'Last-1; --
private
for SInt1'Alignment use 1; --
for SInt2'Alignment use 1; --
for SInt3'Alignment use 1; --
for SInt4'Alignment use 1; --
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.
****************************************************************
From: Tucker Taft
Sent: Friday, January 28, 2022 8:24 AM
AI22-0037-1 concludes that from a freezing point of view, T'Range is *not*
equivalent to T'First .. T'Last. I would have come to the opposite
conclusion, because of the simple equivalence defined in 3.5(14) between
them (see below). On the other hand, T and T'Range are not equivalent as far
as freezing in my view, whereas it seems AI22-0037-1 presumes they are
equivalent.
>Doesn't need to be a discussion, as Foo and Foo'Range are equivalent for this
>purpose.
>...
>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.
But there is also an equivalence in 3.5(14) which is as simple as it could be:
S'Range is equivalent to the range S'First .. S'Last.
and I would normally interpret this as all of the legality, static semantics,
and dynamic semantics rules that apply to 'First and 'Last, due to this
equivalence, also apply to 'Range. Is there some indication in the RM that
this equivalence does not cover legality and static semantics rules?
****************************************************************
From: Randy Brukardt
Sent: Wednesday, February 2, 2022 7:43 PM
FYI, Tucker and I have been discussing this case off-line. My main concern is
that this equivalence, like almost any other, cannot be perfect (see below)
and there is no description in the AARM telling what is intended. So it's
unclear as to whether freezing (and any other Legality Rules that might apply)
was intended to be included in the equivalence.
One thing Tucker and I do agree on is that it is really difficult to write a
realistic program in which any such difference would matter. (Most example
programs use a combination of aspect_specifications and late
attribute_definition_clauses, which is not a likely combination. One also
could do it with a late local constant declaration that hides a use-visible
constant, but that isn't very likely either.) Moreover, T'range for discrete
T isn't used often anyway, as one can use T by itself in all of the same
contexts. So this becomes rare**2, and thus isn't very interesting to decide.
The reason that I believe the equivalence cannot be exact is that two
expressions using T'range and T'First..T'Last cannot conform given the current
wording. Conformance uses a syntax equivalence check, and that is not met for
the above supposedly equivalent expressions. Moreover, the array case of
T'range uses the same equivalence wording with a dynamic semantics exception;
because of the dynamic semantics exception, it would defeat the purpose of
full conformance if an array Obj'range conformed with Obj'First .. Obj'Last
(since the number of times that Obj is evaluated is different for the two
expressions; expressions that conform are supposed to be semantically
identical). Since the conformance wording and the equivalence wording both
are the same for both discrete and array 'Range, one cannot get different
answers for the two cases (one would have to have some wording about Legality
differences somewhere for that to happen). Ergo, there cannot be an exact
equivalence for Legality Rules for T'range, and we then don't have a way to
figure out what the real boundary is (and which side of the boundary that we
find freezing).
Since, as noted above, this is very unimportant, it isn't worth spending more
energy trying to tighten up the hole, and especially not ARG energy. So,
Tucker and I will need to hash out AI !discussion text that is acceptable to
both of us; but whatever we come up with will not change the basic conclusion
of the AI (which is the difference between T and T'First .. T'Last vis-a-vis
freezing for a static discrete T). We can certainly discuss and possibly
approve this AI if it comes up on the agenda tomorrow.
****************************************************************
Questions? Ask the ACAA Technical Agent