!standard 3.9(13-15) 07-05-23 AI05-0056-1/00 !class binding interpretation 07-05-25 !status work item 07-05-25 !status received 07-05-19 !priority Medium !difficulty Medium !qualifier Omission !subject The class attribute of a constrained subtype !summary !question 3.9(13-15) says: For every subtype S of a tagged type T ... the following attributes are defined: S'Class S'Class denotes a subtype of the classwide type ... for the class rooted at T. S'Class is unconstrained. However, if S is constrained, then the values of S'Class are only those that when converted to the type T belong to S. However, S'Class is an unconstrained type by 3.7(26). Thus, 4.6(51/2) does not apply, and thus there is no check that a converted value actually belongs to S. Similarly, 4.5.2(30/2) says that a membership returns True if the value satisfies any constraints of the named subtype. But a classwide type doesn't have any constraints (it's unconstrained) nor is there any definition of satifies for them. Something is seriously wrong here. !recommendation (See Summary.) !wording !discussion The idea of a type conversion to an *unconstrained* type raising *Constraint*_Error for a *constraint* violation is enough to make me sick and dizzy. It also seems to add a third kind of "partially constrained" type which invalidates predicates like Is_Constrained in compilers (and ASIS). A brief compiler survey with a simple test program shows that there is no consistency in the handling of this case: GNAT gets bogus error messages; with those fixed it makes no checks and memberships don't check the discriminants; Janus/Ada makes no checks and memberships doesn't check the discriminants; IBM Rational makes checks in some cases and not in others; Steve Baird claims that the checks are just accidents of a case not considered at all; Sofcheck makes checks in most cases, but not in memberships or type conversions (there also was a private report that it gives various compile-time errors in the AppletMagic version, so it may be even be consistent). --!corrigendum A.4.3(26/2) !ACATS Test !appendix [This thread was split from AI05-0032-1, as it is a separate issue.] From: Randy Brukardt Sent: Saturday, May 19, 2007 12:07 AM Tucker writes: > Here is the AI on allowing the type of the return object > in an extended_return_statement to be a specific type when > the result type is class-wide. While writing this, I bumped > into what seems to be a longstanding hole in 4.6 (Type > Conversions). Given two subtypes of T'Class, S1'Class > and S2'Class, we don't ever seem to check that when converting > from S2'Class to S1'Class, that the operand satisfies > the constraints of S1. See the AI discussion section for > more on this. A related hole seems to exist in 6.5 > (Return Statements), where if the result subtype is > S1'Class, we allow the return object to be declared of > subtype S2'Class, and again never check against S1. > I tried to fix this latter hole, but ended up just > presuming that 4.6 did the right thing, which it doesn't. > So another AI is probably needed to address the 4.6 hole. I don't understand your hole. I don't think there is any hole in 6.5, at least before your proposal. 6.5(5.2/2) requires static matching of the return object subtype if the result subtype is constrained; so if the result subtype S1'Class has a constraint, an object subtype of S2'Class is illegal unless it has the same constraints. If the result subtype doesn't have a constraint, there is nothing to check. Humm, 4.6(51/2) says that after the conversion, there is a check that the value satisfies any constraint of the target subtype if it is constrained. What more do you need? (If, for some bizarre reason, S1'Class is not considered constrained, then it has no real constraints, because pretty much all of the rules of the language are written "if constrained, then blah blah". It would effectively act like was unconstrained. While I'd prefer this interpretation, I don't think it was what was intended - I remember *losing* that argument during Ada 95. And having constraints on unconstrained subtypes seems like the first step on the road to madness...) **************************************************************** From: Tucker Taft Sent: Saturday, May 19, 2007 3:22 PM The problem is that all classwide subtypes have unknown discriminants, and are therefore considered to be *un*constrained. It is true that they might have a constraint on some of the discriminants, but there might be others that are unconstrained. Perhaps we could fix the hole by simply stating that such class-wide subtypes are indefinite, yet constrained, subtypes. But that is not currently a possible combination. **************************************************************** From: Randy Brukardt Sent: Tuesday, May 22, 2007 3:10 PM Whatever we do would be a new combination; I worry that the wording of the standard is not up to handling such combinations (not to mention compilers). I'd like to make the argument that the current wording of the language is just fine (even if it isn't quite what the designers intended). I thought that there were some ACATS tests that insisted on checks, but when I looked I wasn't able to find them. (Since I'm pretty sure that Janus/Ada doesn't make any such checks, any such tests should show up easily.) As such, I'd like to do a compiler survey in order to figure what compilers currently in these sorts of cases. Otherwise, we'll not know whether compatibility is a real issue here, and what (if anything) the language ought to be compatible with. Its probably too close to the meeting to get results from such a survey before the meeting, so I'll try to do it afterwards (this issue isn't yet on the agenda anyway). **************************************************************** From: Tucker Taft Sent: Tuesday, May 22, 2007 5:57 PM The Ada_Magic-based compilers (ours, Green Hills, and Aonix) certainly check any discriminant constraint associated with the root subtype of a class-wide subtype, in case you are making a table. Anything else would be highly surprising to the user, I would think. The RM seems clearly broken in this area. For example: type T(D : Integer) is tagged ... subtype S1 is T(1); X : S1'Class := T'(D => 2, ...); will definitely raise Constraint_Error with our compiler. **************************************************************** From: Randy Brukardt Sent: Tuesday, May 22, 2007 6:49 PM A classwide type essentially means to allow everything that's possible, now and in the future, for a type. That's inconsistent with a constraint. Moreover, Ada does not allow partially constrained types (for good reason, I think). For these reasons, I happen to think that using 'Class on a subtype where you can see the unconstrained type should be illegal. (The rule could be very similar to what we did to fix general access subtypes.) Cases where you can't see the subtype also couldn't fail any checks and thus would cause no surprise. [This happens to correspond to the cases where you can avoid writing a constrained subtype.] An attempt to partially constrain a type is a bug, and that's what this is. Sadly, I suspect such a rule would be too incompatible to seriously consider. [If I'm wrong, that's *my* proposal for this issue...] > The RM seems clearly broken in this area. For example: ... And I find it just too weird that an unconstrained type would raise Constraint_Error. And I seriously doubt that the language rules (any of them) allow for the possibility. After all, your original problem in 6.5 is a artifact; there is supposed to be a static matching check on anything with a constraint (thus eliminating any need for a runtime check). That doesn't happen because S'Class is unconstrained. But rather than fixing that or fixing the 6.5 wording so the static matching check will cover it, you want to change to a runtime check. That doesn't make sense to me: why should this one goofy case be different? It seems to me that fixing this somehow will require major surgery to the standard (as is necessary to fix 6.5 properly) and possibly to compilers, yet I don't see any indication that this is an important capability or even that it is widely implemented. Perhaps I'm wrong, but I want to get evidence of that one way or the other before making a decision. **************************************************************** From: Pascal Leroy Sent: Wednesday, May 23, 2007 7:29 AM > type T(D : Integer) is tagged ... > > subtype S1 is T(1); > > X : S1'Class := T'(D => 2, ...); For the record our compiler don't raise C_E here. I must say that I have a hard time following Tuck's reasoning. The RM is fairly clear: 3.7(26) states that "A tagged class-wide type also has unknown discriminants. Any subtype of a type with unknown discriminants is an unconstrained and indefinite subtype". So S1'Class is unconstrained and indefinite and there is no reason to raise C_E. In other words, as far as I can tell there is no difference between S1'Class and T'Class. Surely we are not going to introduce subtypes that are partially constrained. That would be an earthquake for the language and for implementers, and it wouldn't really bring any benefit to users. **************************************************************** From: Tucker Taft Sent: Wednesday, May 23, 2007 7:47 AM I agree this capability is not essential, but it has been a part of the language since 1995. It is discussed quite explicitly in 3.9(13-15) in the Ada 95 RM (and unchanged in the Ada 2005 RM): For every subtype S of a tagged type T ... the following attributes are defined: S'Class S'Class denotes a subtype of the classwide type ... for the class rooted at T. S'Class is unconstrained. However, if S is constrained, then the values of S'Class are only those that when converted to the type T belong to S. I don't see how it could be more explicit. Clearly other parts of the manual, in particular 4.6, failed to implement this requirement properly. I don't see it as a particularly big job to fix, since the wording above is so clearly explicit about what it means. And although both Randy and Pascal claim that the language doesn't have "partially constrained" subtypes, it clearly has classwide subtypes as described above, so perhaps they should be called "partially unconstrained" ... ;-). There is still the issue about whether we want to require static matching or introduce a run-time check. In poking around, I found a precedent in the rules for X'Access, which require static matching for constrained untagged types, but allow looser "coverage" rules for tagged types, with a view conversion providing a run-time check whenever there isn't static matching. See RM 2005 3.10.2(27/2-27.2/2, 30). Of course all of this depends on fixing up 4.6 so it does the right thing on a view conversion to S'Class. I think the following would probably do the trick: Add another sentence to the end of 4.6(51/2): ... If the target subtype is a discriminated class-wide subtype S'Class, and S is constrained, then a check is performed that the discriminants of the converted value satisfy the constraint. **************************************************************** From: Edmond Schonberg Sent: Wednesday, May 23, 2007 2:06 PM >> type T(D : Integer) is tagged ... >> >> subtype S1 is T(1); >> >> X : S1'Class := T'(D => 2, ...); > > For the record our compiler don't raise C_E here. Also for the record, GNAT rejects the code, indicating that the confusion here is widespread! Part of the GNAT code tries to find an actual constrained subtype for this class-wide object, and another part rejects the constraint on the grounds that it has an already constrained subtype.... Given the very explicit text in 3.9 (15) I think that Tuck's reading is inescapable. **************************************************************** From: Randy Brukardt Sent: Wednesday, May 23, 2007 5:48 PM Pascal writes: ... > Surely we are not going to introduce subtypes that are partially > constrained. That would be an earthquake for the language and for > implementers, and it wouldn't really bring any benefit to users. Not to mention unconstrained types that have constraints raise Constraint_Error - a complete twisting of English and logic. I'm glad to see that I'm not completely nuts :-) and others also find this distasteful. Tucker replies: > I agree this capability is not essential, but it has > been a part of the language since 1995. And it was wrong and misguided there, too. > It is discussed > quite explicitly in 3.9(13-15) in the Ada 95 RM (and > unchanged in the Ada 2005 RM): > For every subtype S of a tagged type T ... the following > attributes are defined: > S'Class S'Class denotes a subtype of the classwide type > ... for the class rooted at T. S'Class is > unconstrained. However, if S is constrained, > then the values of S'Class are only those that > when converted to the type T belong to S. > I don't see how it could be more explicit. Clearly other > parts of the manual, in particular 4.6, failed to implement > this requirement properly. I would argue that it is essentially the *entire* manual that fails to implement this. I haven't been able to find any text in the Standard that takes such "partially constrained" types into account. The closest I've found is membership operations, but those talk about constraints on the subtype, while the above text talks about values (the S'Class subtype does not appear to have constraints as defined by the language). There's no definition of the constraints on such a type, there is no definition of compatibility with other constraints and satisfaction of values for these types, nor anything else that I can see. > I don't see it as a particularly big job to fix, since the wording above > is so clearly explicit about what it means. Except that that this definition doesn't match any of the other semantics definitions of the language. At a very least, there has to be a recognition of the fact that this does *not* define a constraint, but still acts like one. Ed says: > Given the very explicit text in 3.9 (15) > I think that Tuck's reading is inescapable. I surely hope that the simple existence of some text doesn't mean we have to adopt a wrong-headed rule. After all, Ada 95 had text which clearly defined an incomplete type as a different type than its completion (and thus making all useful programs containing incomplete types as illegal). We didn't try to fix up this explicit wording, we simply replaced it by "view" terminology. The questions about this rule are: (1) Is this rule of any benefit to users? (Not much, if any. I think coextensions are more likely to be useful in practice, and I think *those* are very unlikely to be useful.) (2) Is anyone depending on this rule? (Seems unlikely, given that GNAT crashed when it came up) (3) How high is the implementation cost of this rule? (Very high, at least for some implementations where it would destroy all of the invariants.) (4) Is the rule going to make the language easier to understand and/or use? (Arguable either way. An unconstrained type that raises Constraint_Error isn't going to help anyone's understanding, but silently ignoring constraints isn't ideal, either.) The Gnat results suggest that we could adopt a relatively radical solution without too much user pain. (User's can't be depending on code that crashes the compiler!) But I don't want to argue anything without having done a more formal compiler survey. I'll try to get that initiated ASAP. **************************************************************** From: Edmond Schonberg Sent: Wednesday, May 23, 2007 7:24 PM Small correction: the compiler does not crash, it rejects the code with a rather contorted error message. If the solution you have in mind is somehow to make this code illegal, then indeed GNAT is already there. The error message could be improved.... **************************************************************** From: Tucker Taft Sent: Wednesday, May 23, 2007 11:02 PM It is of course hard to debate how much of an earthquake something is for anyone's compiler, but I can tell you why this isn't a big deal for ours. Class-wide types are pretty much a special case everywhere the appear in our compiler, so for conversion, the special handling implied by 3.9(15) is no big deal. I wouldn't leap to conclusions about the GNAT result. The example I concocted was artificial. It would be rare to create such an obvious constraint violation. More interesting would be a case where an object of subtype S1'Class is initialized from a parameter of subtype T'class, or a function call returning subtype T'Class. The question is whether GNAT performs a constraint check in that case. procedure Test(X : T'Class) is Y : S1'Class := X; begin ... The reason I don't think this creates much of an "earthquake" in the manual is that in almost all interesting contexts, we use "subtype conversion" to capture the need for constraint checks. So if subtype conversion does the right thing for classwide subtypes, then most other parts of the manual will follow along. I might add that I would have expected the big Ada 2005 "earthquake" in this general vicinity to be that access types now can be "constrained" in two independent ways, one with a null exclusion, and one (for access-to-composite) with a constraint on the designated subtype. Semi-constrained class-wide subtypes seems like small potatoes compared to that... ;-) **************************************************************** From: Randy Brukardt Sent: Thursday, May 23, 2007 12:18 AM > It is of course hard to debate how much of an earthquake > something is for anyone's compiler, but I can tell you > why this isn't a big deal for ours. Class-wide types are > pretty much a special case everywhere the appear in our > compiler, so for conversion, the special handling implied > by 3.9(15) is no big deal. But that's backwards. Even though there are literally hundreds of places to check [that turns out to be an overestimate, there are 63 calls of Is_Classwide in Janus/Ada - although it is likely that there are some cases not protected with such a call - Randy - May 25th], that isn't what worries me. What worries me is that such a subtype would necessarily look identical to a constrained subtype, and that would cause breakage everywhere. For instance, what does Is_Unconstrained return for such a type? Either True or False is likely to be wrong in some contexts. The only way to implement this in our compiler would be something like (the following is pseudo-Ada, of course): subtype S1 is T(1); subtype S1'Class is T'Class(1); A constraint is one kind of node, and a type (like T'Class) is another kind of node, chained together. (Duplicating all of the constraint code in the class node is just too disgusting to contemplate, and then we'd have a forest of similar types which would usually compare equal, which would damage other invariants.) You couldn't tell from the constraint whether it was "normal" or one of these special thingy's. > I wouldn't leap to conclusions about the GNAT result. > The example I concocted was artificial. It would be > rare to create such an obvious constraint violation. > More interesting would be a case where an object of > subtype S1'Class is initialized from a parameter of > subtype T'class, or a function call returning subtype > T'Class. The question is whether GNAT performs a > constraint check in that case. > > procedure Test(X : T'Class) is > Y : S1'Class := X; > begin > ... I think code like this is brain-damaged. Why in heaven's name would you try to force a failure like this? I can vaguely imagine using S1'Class in a parameter specification in order to define some sort of contract, but not inside of a procedure to essentially violate the contract given the user. In any case, I wish you had sent this a half-hour ago when I was still writing the test; now I'll have to retract it and write another one... Finally, my understanding of the Gnat problem is that it failed trying to look for the constraint in S1'Class. I doubt that it will matter where it is used for that case. > The reason I don't think this creates much of an > "earthquake" in the manual is that in almost all > interesting contexts, we use "subtype conversion" > to capture the need for constraint checks. So if > subtype conversion does the right thing for classwide > subtypes, then most other parts of the manual will > follow along. I don't buy this. There is a lot more here than just constraint checks; we need to define memberships, "satisfaction", and probably a number of other things. And we'd still have the insanity of an unconstrained type raising Constraint_Error. > I might add that I would have expected the big Ada 2005 > "earthquake" in this general vicinity to be that access types > now can be "constrained" in two independent ways, one with > a null exclusion, and one (for access-to-composite) > with a constraint on the designated subtype. Semi-constrained > class-wide subtypes seems like small potatoes compared > to that... ;-) Null exclusion doesn't have any runtime baggage (it's just a single bit), so it can be duplicated in every access type and subtype and not worried about beyond that. I do suspect that checking the rules for reconstraint will be a mess and probably buggy forever, but at least they are in a single place, not more than a half-dozen places like discriminant checks/memberships/etc. **************************************************************** From: Randy Brukardt Sent: Thursday, May 23, 2007 12:51 AM [Note: Version 1 of the test is not posted here, as no one other than me used it. - RLB] Over on the ARG list, we're having a debate about the semantics of classwide types. I've created a test program to determine what existing compilers do (from what has been discussed so far, that seems to be all over the map). If you have access to a compiler other than GNAT, we'd appreciate it if you'd run this test program and send us the results. (You can post them here or send them privately to me.) I only exclude GNAT because pretty much every one has a version, and we already have results on several versions of it. I've put the test program as an attachment in hopes that Outlook won't wrap the lines incorrectly. If you have trouble with the attachment, please drop me a line personally and I can send the test another way. ------------------- with Ada.Text_IO; procedure Chk_AI57 is -- Check the behavior of an Ada compiler on classwide constrained -- subtypes. -- Edit History: -- 5/23/07 - RLB - Created test. -- 5/24/07 - RLB - Added function result cases to reduce the -- chance of compile-time detection. type Root(D : Integer) is tagged null record; subtype S1 is Root(1); subtype S2 is Root(2); type Der(C : Integer) is new Root(C) with null record; procedure Check_It_1 (Obj : in S1'Class) is begin if Obj.D = 1 then null; -- Not interesting. else null; -- More info. at call site. --Ada.Text_IO.Put_Line ("-- No check on constrained " & -- "classwide parameter subtype"); end if; end Check_It_1; procedure Check_It_2 (Obj : in Root'Class) is begin null; end Check_It_2; O1 : Root(1) := Root'(D => 1); O2 : Root(2) := Root'(D => 2); OD1 : Der(1) := Der'(C => 1); OD2 : Der(2) := Der'(C => 2); subtype Obj_Kind is Natural range 1 .. 4; function Factory (Kind : Obj_Kind) return Root'Class is begin case Kind is when 1 => return O1; when 2 => return O2; when 3 => return OD1; when 4 => return OD2; end case; end Factory; begin Ada.Text_IO.Put_Line ("--- Check behavior of constrained classwide " & "types (version 2)"); begin Check_It_1 (O1); exception when Constraint_Error => Ada.Text_IO.Put_Line ("** Check failed with matching " & "discrims (root)"); end; begin Check_It_1 (O2); Ada.Text_IO.Put_Line ("-- No discrim. check on constrained " & "classwide parameter subtype (root)"); exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide parameter subtype (root)"); end; begin Check_It_1 (OD1); exception when Constraint_Error => Ada.Text_IO.Put_Line ("** Check failed with matching " & "discrims (der)"); end; begin Check_It_1 (OD2); Ada.Text_IO.Put_Line ("-- No discrim. check on constrained " & "classwide parameter subtype (der)"); exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide parameter subtype (der)"); end; begin declare X : S2'Class := Root'Class(O1); begin if X in S1'Class then Ada.Text_IO.Put_Line ("-- Membership ignores discrims (root)"); else Ada.Text_IO.Put_Line ("-- Membership checks discrims (root)"); end if; Ada.Text_IO.Put_Line ("-- No Discrim. check on constrained " & "classwide object subtype (root)"); end; exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide object subtype (root)"); end; begin declare X : S2'Class := Root'Class(O2); begin if X in S1'Class then Ada.Text_IO.Put_Line ("-- Membership ignores discrims (root)"); else Ada.Text_IO.Put_Line ("-- Membership checks discrims (root)"); end if; end; exception when Constraint_Error => Ada.Text_IO.Put_Line ("** Check failed with matching " & "discrims (root, obj)"); end; begin declare X : S2'Class := Root'Class(OD1); begin if X in S1'Class then Ada.Text_IO.Put_Line ("-- Membership ignores discrims (der)"); else Ada.Text_IO.Put_Line ("-- Membership checks discrims (der)"); end if; Ada.Text_IO.Put_Line ("-- No Discrim. check on constrained " & "classwide object subtype (der)"); end; exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide object subtype (der)"); end; begin declare X : S2'Class := Root'Class(O2); begin if X in S1'Class then Ada.Text_IO.Put_Line ("-- Membership ignores discrims (der)"); else Ada.Text_IO.Put_Line ("-- Membership checks discrims (der)"); end if; end; exception when Constraint_Error => Ada.Text_IO.Put_Line ("** Check failed with matching " & "discrims (der, obj)"); end; begin Check_It_2 (S1'Class(O1)); exception when Constraint_Error => Ada.Text_IO.Put_Line ("** Check failed with matching " & "discrims (root)"); end; begin Check_It_2 (S1'Class(O2)); Ada.Text_IO.Put_Line ("-- No Discrim. check on constrained " & "classwide type conversion (root)"); exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide type conversion (root)"); end; begin Check_It_2 (S1'Class(OD1)); exception when Constraint_Error => Ada.Text_IO.Put_Line ("** Check failed with matching " & "discrims (der)"); end; begin Check_It_2 (S1'Class(OD2)); Ada.Text_IO.Put_Line ("-- No Discrim. check on constrained " & "classwide type conversion (der)"); exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide type conversion (der)"); end; begin Check_It_1 (Factory(1)); exception when Constraint_Error => Ada.Text_IO.Put_Line ("** Check failed with matching " & "discrims (factory root)"); end; begin Check_It_1 (Factory(2)); Ada.Text_IO.Put_Line ("-- No discrim. check on constrained " & "classwide parameter subtype (factory root)"); exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide parameter subtype (factory root)"); end; begin Check_It_1 (Factory(3)); exception when Constraint_Error => Ada.Text_IO.Put_Line ("** Check failed with matching " & "discrims (factory der)"); end; begin Check_It_1 (Factory(4)); Ada.Text_IO.Put_Line ("-- No discrim. check on constrained " & "classwide parameter subtype (factory der)"); exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide parameter subtype (factory der)"); end; begin declare X : S2'Class := Factory(1); begin if X in S1'Class then Ada.Text_IO.Put_Line ("-- Still ignores discrims (factory root)"); end if; Ada.Text_IO.Put_Line ("-- No Discrim. check on constrained " & "classwide object subtype (factory root)"); end; exception when Constraint_Error => Ada.Text_IO.Put_Line ("-- Discrim. check on constrained " & "classwide object subtype (factory root)"); end; Ada.Text_IO.Put_Line ("--- Test complete"); end Chk_AI57; **************************************************************** From: Randy Brukardt Sent: Thursday, May 20, 2007 12:53 AM For Janus/Ada, I get: --- Check behavior of constrained classwide types (version 2) -- No discrim. check on constrained classwide parameter subtype (root) -- No discrim. check on constrained classwide parameter subtype (der) -- Membership ignores discrims (root) -- No Discrim. check on constrained classwide object subtype (root) -- Membership ignores discrims (root) -- Membership ignores discrims (der) -- No Discrim. check on constrained classwide object subtype (der) -- Membership ignores discrims (der) -- No Discrim. check on constrained classwide type conversion (root) -- No Discrim. check on constrained classwide type conversion (der) -- No discrim. check on constrained classwide parameter subtype (factory root) -- No discrim. check on constrained classwide parameter subtype (factory der) -- Still ignores discrims (factory root) -- No Discrim. check on constrained classwide object subtype (factory root) --- Test complete For the older version of Gnat, I get the same results as before. I tried commenting out parts of the test to (dis)prove Tucker's contention, and as I suspected, the error moved to different uses of S1'Class or S2'Class. I did find one case that I could get to compile, and it reports: -- No discrim. check on constrained classwide parameter subtype (factory root) -- No discrim. check on constrained classwide parameter subtype (factory der) which is consistent with Janus/Ada on those test cases. (So far, Tucker, you're all alone on this... ;-) **************************************************************** From: Sent: Thursday, May 24, 2007 2:50 AM > Here's a revised test program; I added some function result > cases to cover Tucker's concern. My current development compiler (which no customer has ever seen) produces the following output: --- Check behavior of constrained classwide types (version 2) -- Discrim. check on constrained classwide parameter subtype (root) -- Discrim. check on constrained classwide parameter subtype (der) -- Membership ignores discrims (root) -- No Discrim. check on constrained classwide object subtype (root) -- Membership checks discrims (root) -- Membership ignores discrims (der) -- No Discrim. check on constrained classwide object subtype (der) -- Membership checks discrims (der) -- Discrim. check on constrained classwide type conversion (root) -- Discrim. check on constrained classwide type conversion (der) -- Discrim. check on constrained classwide parameter subtype (factory root) -- Discrim. check on constrained classwide parameter subtype (factory der) -- Still ignores discrims (factory root) -- No Discrim. check on constrained classwide object subtype (factory root) --- Test complete On the face of it, it looks like Steve B. couldn't make up his mind: I don't think he actually flips a coin when deciding whether to make a check or not, but that's close. ;-) **************************************************************** From: Stephen W. Baird Sent: Thursday, May 24, 2007 12:22 PM Not a coin flip; more of an uninitialized variable. If code is written with no thought given to a particular case (and certainly no testing of that case), then you get this sort of result. **************************************************************** From: Robert Dewar Sent: Thursday, May 24, 2007 6:54 AM My view here is that a) this "feature" is completely useless, so what we do should not be based on an argument of providing more functionality. b) the RM is unclear/inconsistent, so what we do should not be based on an argument of being faithful to the RM. c) compilers do radically different things now, so what we do should not be based on arguments about portability. The goals should be to a) figure out rules that are as simple as possible to implement in existing compilers. b) figure out rules that are as simple as possible to state in the RM. **************************************************************** From: Tucker Taft Sent: Thursday, May 24, 2007 7:36 AM I would hope that pretty high on our list is that users can easily understand what Ada source code means. There are no "surprises." Making such subtypes illegal would clearly address that issue, but as long as S1'Class is legal, we should be somewhat attentive to what user intent it intuitively represents. Of course we as implementors worry about all the different places where the RM talks about "satisfies" and so on, but most users would look at the one place where S'Class is defined and read that (i.e. 3.9(13-15)). We really should try to see if we can find any usage of this feature in the various user code we have access to, and if so, how it is being used. **************************************************************** From: Robert Dewar Sent: Thursday, May 24, 2007 1:15 PM > I would hope that pretty high on our list is that users > can easily understand what Ada source code means. There are no > "surprises." Making such subtypes illegal would clearly > address that issue, but as long as S1'Class is legal, we > should be somewhat attentive to what user intent it intuitively > represents. Don't worry about it too much if, as seems the case, users don't in practice use such subtypes anyway. > > Of course we as implementors worry about all the different > places where the RM talks about "satisfies" and so on, but > most users would look at the one place where S'Class is > defined and read that (i.e. 3.9(13-15)). We really should > try to see if we can find any usage of this feature > in the various user code we have access to, and if so, > how it is being used. SOunds like a good idea. **************************************************************** From: Randy Brukardt Sent: Friday, May 25, 2007 7:37 PM For what it's worth (probably not a lot), I've never seen a visible discriminant on a tagged type nor a constrained tagged subtype in any user code nor any of our code. S'Class therefore can't happen. I don't think there are any discriminanted tagged types in Claw or any of our other programs. (I say "don't think" because I can't think of any practical way to verify that; manually looking through several hundred type declarations is not practical.) **************************************************************** From: Christoph Grein Sent: Thursday, May 24, 2007 7:30 AM I nevertheless fed it [the test program - ED] to GNAT 6.0.1 (the latest supported version as of 2007-01-17): chk_ai57.adb:98:33: type is already constrained chk_ai57.adb:116:33: type is already constrained chk_ai57.adb:132:33: type is already constrained chk_ai57.adb:150:33: type is already constrained chk_ai57.adb:238:33: type is already constrained compilation error Is GNAT right to reject these? **************************************************************** From: Robert A. Duff Sent: Thursday, May 24, 2007 9:36 AM That's what the ARG is trying to decide. Apparently, the RM is unclear on this point, and different compilers have taken different interpretations. **************************************************************** From: Tucker Taft Sent: Thursday, May 24, 2007 9:25 AM I think Ed Schonberg is looking into these messages. They don't relate to any RM legality rule of which I am aware. **************************************************************** From: Edmond Schonberg Sent: Thursday, May 24, 2007 1:48 PM as i mentioned, the error message indicates a semantic confusion in the front-end: one part of it considers class-wide types to be unconstrained, another one considers that a discriminant constraint indicates that the type cannot possibly receive an additional one. It is a trivial matter to make the error message disappear, after which GNAT reports: --- Check behavior of constrained classwide types (version 2) -- No discrim. check on constrained classwide parameter subtype (root) -- No discrim. check on constrained classwide parameter subtype (der) -- Membership ignores discrims (root) -- No Discrim. check on constrained classwide object subtype (root) -- Membership ignores discrims (root) -- Membership ignores discrims (der) -- No Discrim. check on constrained classwide object subtype (der) -- Membership ignores discrims (der) -- No Discrim. check on constrained classwide type conversion (root) -- No Discrim. check on constrained classwide type conversion (der) -- No discrim. check on constrained classwide parameter subtype (factory root) -- No discrim. check on constrained classwide parameter subtype (factory der) -- Still ignores discrims (factory root) -- No Discrim. check on constrained classwide object subtype (factory root) --- Test complete that is to say, in all other circumstances GNAT treats the type as unconstrained and applies no checks at all. I suspect that it will be a fairly distributed fix to force checks in all these places, should we decide that this is the desired semantics. **************************************************************** From: Tucker Taft Sent: Thursday, May 24, 2007 9:16 AM Thanks for the test case, Randy. It is almost always interesting to run such test cases against one's compiler, as you can be sure to discover things you didn't know about your compiler. One related case that it would be interesting to check is the following: type Der1 is new Root(1) with null record; ... declare X : Root'Class := Factory(2); Y : Der1; begin Y := Der1'(Root(X) with null record); Ada.Text_IO.Put_Line("-- No discrim.check on ancestor part"); exception when Constraint_Error => Ada.Text_IO.Put_Line("-- Discrim. check on ancestor part"); end; ... This involves a case where the discriminant is constrained as part of the derived type definition. Here I think we would all agree that a check is required on the value of the discriminant as part of the extension aggregate. It would be nice to try to comment out the lines of your test that give GNAT compile-time problems and see what it does with the ones that remain. In any case, without further ado, here are the results of running your test on a relatively recent version of our compiler: --- Check behavior of constrained classwide types (version 2) -- Discrim. check on constrained classwide parameter subtype (root) -- Discrim. check on constrained classwide parameter subtype (der) -- Discrim. check on constrained classwide object subtype (root) -- Membership ignores discrims (root) -- Discrim. check on constrained classwide object subtype (der) -- Membership ignores discrims (der) -- No Discrim. check on constrained classwide type conversion (root) -- No Discrim. check on constrained classwide type conversion (der) -- Discrim. check on constrained classwide parameter subtype (factory root) -- Discrim. check on constrained classwide parameter subtype (factory der) -- Discrim. check on constrained classwide object subtype (factory root) --- Test complete Interestingly, we do the check on subtype conversion, but on type conversion, don't bother to do a final subtype conversion as required by 4.6(51/2) for "constrained" subtypes. Touche'. We also don't seem to do any discriminant checking in classwide membership tests. We only do tag coverage checks. By the way, the wording for membership tests uses the phrase "satisfies any constraints of the named subtype." This nicely sidesteps the issue of "constrained" vs. "unconstrained," and gives us a possible way to describe S1'Class. That is S1'Class "has a constraint" but it is not a (fully) "constrained" subtype (since any given value might have additional, unconstrained discriminants). FYI, for the additional test case I suggested, we produce the following: -- Discrim. check on ancestor part By the way, I don't think there is a problem with the RM wording relating to "satisfies." A value satisfies a discriminant constraint if its discriminants have the values imposed by the constraint. That works fine for classwide values. The discriminants of a classwide value T'Class are defined in 3.4.1(5) to be those of T. Another tidbit: the only wording I could find that seems to disallow applying a constraint to a classwide type as follows: subtype S1C is T'Class(1); -- presumably not legal is that a discriminant constraint can only be applied to an unconstrained *discriminated* subtype, and if you read the definition of "discriminated" you might be able to decide that a classwide subtype is never "discriminated" (even though it "has discriminants") because it doesn't "inherit known discriminants" and also it has "unknown discriminants." All pretty subtle. 3.7.1(7/2) could be more explicit by saying an unconstrained discriminated *specific* subtype, or by adding a sentence at the end saying the subtype_mark shall not denote a class-wide subtype. Overall, I would say 4.6(51) is only one of several places where we didn't fully account for class-wide types and/or subtypes. I would argue it is more disruptive to the language to change the meaning of S1'Class as given clearly in 3.9(13-15), than to correctly account for the special characteristics of class-wide types and subtypes where appropriate. Omissions are a normal sort of after-the-fact correction. To withdraw a whole paragraph defining the semantics of a construct since we goofed in subtle ways on wording elsewhere in the manual seems like a funny sort of after-the-fact correction. As suggested above, one possible wording that would help is "has a constraint" when we want to include S1'Class beasties, as opposed to "constrained" or "unconstrained." That is what membership tests use, and seems like it could help fix 4.6(51/2). And perhaps similarly, "has discriminants" rather than "discriminated" if we want to include values of a class-wide type. **************************************************************** From: Pascal Leroy Sent: Thursday, May 24, 2007 10:52 AM > As suggested above, one possible wording that would help > is "has a constraint" when we want to include S1'Class > beasties, as opposed to "constrained" or "unconstrained." > That is what membership tests use, and seems like it could > help fix 4.6(51/2). And perhaps similarly, "has > discriminants" rather than "discriminated" if we want to > include values of a class-wide type. You are actually making a pretty compelling argument *against* your own position, Tuck. Who wants the added conceptual complexity of distinguishing between "constrained" and "has constraint", and "discriminated" and "has discriminants"? I surely don't want to have to explain to a customer that their deployed application failed because they didn't realize that their non-discriminated type actually had discriminants. I don't care which way the decision goes, but I am with Robert: choose the simplest option; we should not add even the tiniest bit of complexity to deal with this cornercase. **************************************************************** From: Tucker Taft Sent: Thursday, May 20, 2007 11:29 AM > ... > It would be nice to try to comment out the lines of your test that give > GNAT compile-time problems and see what it does with > the ones that remain... After commenting out the lines that seemed to cause compile-time failure in GNAT, and adding in my "extra" test case with an extension aggregate, I got the following from GNAT 5.03b: --- Check behavior of constrained classwide types (version 2) -- No discrim. check on constrained classwide parameter subtype (root) -- No discrim. check on constrained classwide parameter subtype (der) -- No Discrim. check on constrained classwide type conversion (root) -- No Discrim. check on constrained classwide type conversion (der) -- No discrim. check on constrained classwide parameter subtype (factory root) -- No discrim. check on constrained classwide parameter subtype (factory der) -- Discrim. check on ancestor part --- Test complete **************************************************************** From: Tucker Taft Sent: Thursday, May 24, 2007 11:34 AM >> ... And perhaps similarly, "has >> discriminants" rather than "discriminated" if we want to >> include values of a class-wide type. > ... I surely don't want to have to > explain to a customer that their deployed application failed because they > didn't realize that their non-discriminated type actually had > discriminants. Unfortunately, this issue has nothing to do with the partially constrained class-wide subtypes. It has to do with whether or not a class-wide type is considered a "discriminated" type. 3.7 is clear that class-wide types have "unknown discriminants" but is not at all clear whether they should be called "discriminated" types, which is then relevant to whether T'Class(D => 1) is permitted in 3.7.1. So I think we do need to look more carefully at the wording associated with class-wide types where the root type has discriminants. ****************************************************************