AI22-0037-1

!standard 13.14(7.2/5)          22-06-23 AI22-0037-1/02

!standard 13.14(8/4)

!class ramification 22-01-27

!status WG9 Approved 22-10-18

!status ARG Approved 14-0-1 22-06-23

!status work item 22-01-27

!status received 21-06-11

!submitter Stephen Baird

!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" 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 OInt3 is Integer
      with Dynamic_Predicate => OInt3 in SInt3'First .. SInt3'Last; -- (2)

  subtype OInt4 is Integer
      with Dynamic_Predicate => OInt4 in SInt4'First+1 .. SInt4'Last-1; -- (3)

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), 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 in place of the explicit range.

(3) 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 -- wherever 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 there 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.

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