!standard 13.14(7.2/5) 22-06-23 AI22-0037-1/02 !standard 13.14(8/4) !class ramification 22-01-27 !status ARG Approved 14-0-1 22-06-23 !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" 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. ****************************************************************