!standard 4.3.1(17/3) 15-07-13 AI12-0086-1/04 !standard 4.3.1(19/3) !class Amendment 13-10-30 !status Amendment 1-2012 16-02-29 !status WG9 Approved 15-10-16 !status ARG Approved 7-1-1 15-06-28 !status Promising (9-0-1) 15-01-28 !status work item 13-10-30 !status received 13-08-22 !priority Medium !difficulty Easy !subject Aggregates and variant parts !summary A discriminant that controls a variant can be non-static if the subtype of the discriminant is static and all values belonging to that subtype select the same variant. !problem Ada requires that the discriminant that selects a variant in an aggregate be static. This can be overly limiting for constructor functions. For instance, consider: type Enum is (Aa, Bb, Cc, ..., Zz); subtype S is Enum range Dd .. Hh; type Rec (D : Enum) is record case D is when S => Foo, Bar : Integer; when others => null; end case; end record; The following is illegal because the discriminant is not static, even though it can only select a single variant: function Make (D : S) return Rec is begin return (D => D, Foo => 123, Bar => 456); end; This can only be worked around by abandoning the aggregate (and losing the associated completeness check), or by using a case statement of similar aggregates (which has a lot of duplicated code): function Workaround (D : S) return Rec is begin case D is when Dd => return (D => Dd, Foo => 123, Bar => 456); when Ee => return (D => Ee, Foo => 123, Bar => 456); ... when Hh => return (D => Hh, Foo => 123, Bar => 456); end case; end; Neither workaround is appealing. !proposal (See summary.) !wording Replace 4.3.1(17/3) The value of a discriminant that governs a variant_part P shall be given by a static expression, unless P is nested within a variant V that is not selected by the discriminant value governing the variant_part enclosing V. with If a variant_part P is nested within a variant V that is not selected by the discriminant value governing the variant_part enclosing V, then there is no restriction on the discriminant governing P. Otherwise, the value of the discriminant that governs P shall be given by a static expression, or by a nonstatic expression having a constrained static nominal subtype. In this latter case of a nonstatic expression, there shall be exactly one discrete_choice_list of P that covers each value that belongs to the nominal subtype and satisfies the predicates of the subtype, and there shall be at least one such value. AARM Ramification: This means that in the nonstatic value case, the (static) subtype cannot have a null range. Append to the end of 4.3.1(19): If the value of a discriminant that governs a selected variant_part P is given by a nonstatic expression, and the evaluation of that expression yields a value that does not belong to the nominal subtype of the expression, then Constraint_Error is raised. AARM Ramification: In this case, the value of the discriminant is outside the base range of its type, does not satisfy the (static) predicates of the subtype, or is an invalid representation. This is a rule similar to that used for case statements. !discussion With these rules, the compiler always knows at compile-time which components should be present in the aggregate. We expect that the change would be very localized in compilers. !example (See question.) !ASIS No ASIS effect. !ACATS test An ACATS C-Test is needed to check that this extension is supported correctly. !appendix From: Steve Baird Sent: Thursday, August 22, 2013 7:15 PM When we get around to discussing amendments in Pittsburgh, I'd like to consider relaxing the requirement that a discriminant value in a record aggregate must be static if it governs a variant part. Given type Enum is (Aa, Bb, Cc, ..., Zz); subtype S is Enum range Dd .. Hh; type Rec (D : Enum) is case D is when S => Foo, Bar : Integer; when others => null; end case; end I want to allow the following example, which is currently illegal: function Make (D : S) return Rec is begin return (D => D, Foo => 123, Bar => 456); end; As things stand today, a user who wants write such a constructor function has a choice of two workarounds: 1) Give up on using an aggregate, as in function Workaround_1 (D : S) return Rec is begin return Result : Rec (D => D) do Result.Foo => 123; Result.Bar => 456; end return; end; This forfeits the advantages of using an aggregate. For example, if a new component is added to the record type, this version will continue to compile; this is often undesirable. 2) Use many similar aggregates, as in function Workaround_2 (D : S) return Rec is begin case D is when Aa => return (D => Aa, Foo => 123, Bar => 456); when Bb => return (D => Bb, Foo => 123, Bar => 456); ... when Zz => return (D => Zz, Foo => 123, Bar => 456); end case; end; The resulting duplication introduces obvious maintenance problems. Neither of these alternatives are very satisfactory. After some very helpful discussions with Randy, it looks like the wording changes needed to accomplish this would be reasonably straightforward (see below), and can follow the example of how case statements make use of the nominal subtype of the selecting expression. Opinions? Incidentally, Randy points out that essentially the same idea was suggested in AC-00017 in 2001 (see www.ada-auth.org/cgi-bin/cvsweb.cgi/acs/ac-00017.txt?rev=1.2). ==== !wording Replace 4.3.1(17/3) The value of a discriminant that governs a variant_part P shall be given by a static expression, unless P is nested within a variant V that is not selected by the discriminant value governing the variant_part enclosing V. with Given a variant part P governed by a discriminant of type T, an expression of type T is said to "determine a variant" of P if either - the value of the expression is static; or - the nominal subtype of the expression is static and constrained, and there exists exactly one discrete_choice_list of P that covers every value of that static subtype which satisfies the predicate of that subtype. The value of a discriminant that governs a variant_part P shall be given by an expression which determines a variant of P, unless P is nested within a variant V that is not selected by the discriminant value governing the variant_part enclosing V. [Note: The "Exactly one" wording handles the case where the static subtype has no values.] Append to the end of 4.3.1(19). If the value of a discriminant is given by a non-static expression which is required by the above rules to determine a variant of some variant_part [of the type of the aggregate], and if furthermore the evaluation of that expression yields a value which does not belong to the nominal subtype of the expression, then Program_Error is raised. [TBD: in the analogous situation with case statements, we raise Constraint_Error and not Program_Error - see 5.4(13). Which exception do we want here? It really doesn't matter, either here or for case statements, because 13.9.1(9) explicitly permits raising either exception.] [TBD: instead of the awkward "is required by the above rules" wording, we could define yet another term in the 4.3.1(17/3) stuff with something like "Such an expression is said to be aggregate governing" or "variant part determining" or somesuch and then refer to that new term in the dynamic semantics rule.] !discussion The "and constrained" part of the "static and constrained" wording is important because of 4.6's wording about how subtype conversions work: After conversion of the value to the target type, if the target subtype is constrained, a check is performed that the value satisfies this constraint. The proposed wording is also consistent with 5.4(7/3)'s "having a static and constrained nominal subtype".] **************************************************************** From: Bob Duff Sent: Friday, August 23, 2013 9:59 AM > I want to allow the following example, which is currently illegal: > > function Make (D : S) return Rec is > begin > return (D => D, Foo => 123, Bar => 456); > end; I agree that's annoying. I have run into this limitation many times. I'm opposed to making big changes for Ada 2022, but this one seems small enough. On the other hand, anything having to do with aggregates is an implementation headache. I suggest that if there is support for the idea, AdaCore should implement it and report back how much of an earthquake it is. > 2) Use many similar aggregates, as in > > function Workaround_2 (D : S) return Rec is > begin > case D is > when Aa => return (D => Aa, Foo => 123, Bar => 456); > when Bb => return (D => Bb, Foo => 123, Bar => 456); > ... > when Zz => return (D => Zz, Foo => 123, Bar => 456); > end case; > end; Why doesn't the above mention just Dd .. Hh? > After some very helpful discussions with Randy, it looks like the > wording changes needed to accomplish this would be reasonably > straightforward (see below), and can follow the example of how case > statements make use of the nominal subtype of the selecting > expression. General language-design comment: I don't think the difficulty of wording is particularly important, except to the extent that such difficultly reflects inherent complexity. What matters most is the programmer's view (e.g. incompatibilities are a menace), and second most is implementation difficulty. Both trump wording difficulty. > !wording > > Replace 4.3.1(17/3) > > The value of a discriminant that governs a variant_part P shall > be given by a static expression, unless P is nested within a > variant V that is not selected by the discriminant value > governing the variant_part enclosing V. > > with > > Given a variant part P governed by a discriminant of type T, an I think "discriminant of type T" is ambiguous. T could be the record type containing the discriminant, or T could be the type of the discriminant. > expression of type T is said to "determine a variant" of P if > either > - the value of the expression is static; or > - the nominal subtype of the expression is static and > constrained, I don't think expressions have nominal subtypes. Objects, views of objects, and names of objects do. Maybe "the expression is the name of an object whose nominal subtype ..."? > and there exists exactly one discrete_choice_list of P that > covers every value of that static subtype which satisfies "that" > the predicate of that subtype. > > The value of a discriminant that governs a variant_part P shall > be given by an expression which determines a variant of P, unless "that" > P is nested within a variant V that is not > selected by the discriminant value governing the variant_part > enclosing V. > > [Note: The "Exactly one" wording handles the case where the static > subtype has no values.] The empty subtype case would necessarily raise an exception, by the rule below, right? I think we'd better explicitly outlaw the empty subtype case. Otherwise this is legal: type T(D: boolean) is record case D is when True | False => ...; end case; end record; subtype Empty is Boolean range True..False; procedure P(X: Empty) is ... T'(D => X, ...) ... because there's "exactly one". I think this example should be illegal -- it seems like a (small) implementation headache to allow it, and it's useless. > Append to the end of 4.3.1(19). > > If the value of a discriminant is given by a non-static > expression which is required by the above rules to determine "that" > a variant of some variant_part [of the type of the aggregate], > and if furthermore the evaluation of that expression Might be better to leave out "if furthermore"? > yields a value which does not belong to the nominal subtype "that" > of the expression, then Program_Error is raised. This can only happen for invalid values, right? It's worth pointing that out in the AARM -- i.e. it's a very-corner case. > [TBD: in the analogous situation with case statements, we raise > Constraint_Error and not Program_Error - see 5.4(13). Which exception > do we want here? It really doesn't matter, either here or for case > statements, because 13.9.1(9) explicitly permits raising either > exception.] Don't care. Here, I'll flip a coin for you ... OK, choose C_E. > [TBD: instead of the awkward "is required by the above rules" wording, > we could define yet another term in the 4.3.1(17/3) stuff with > something like "Such an expression is said to be aggregate governing" > or "variant part determining" or somesuch and then refer to that new > term in the dynamic semantics rule.] Don't much care. I guess I'd prefer not adding yet another jaw-breaking term. > !discussion > > The "and constrained" part of the "static and constrained" > wording is important because of 4.6's wording about how subtype > conversions work: > After conversion of the value to the target type, if the target > subtype is constrained, a check is performed that the value satisfies > this constraint. I don't understand that point. Example, please? > The proposed wording is also consistent with 5.4(7/3)'s "having a > static and constrained nominal subtype".] P.S. Thanks for writing this up. **************************************************************** From: Steve Baird Sent: Friday, August 23, 2013 11:23 AM >> 2) Use many similar aggregates, as in >> >> function Workaround_2 (D : S) return Rec is >> begin >> case D is >> when Aa => return (D => Aa, Foo => 123, Bar => 456); >> when Bb => return (D => Bb, Foo => 123, Bar => 456); >> ... >> when Zz => return (D => Zz, Foo => 123, Bar => 456); >> end case; >> end; > > Why doesn't the above mention just Dd .. Hh? > Because I foolishly didn't attempt to compile my example before sending it. >> >> Given a variant part P governed by a discriminant of type T, an > > I think "discriminant of type T" is ambiguous. T could be the record > type containing the discriminant, or T could be the type of the > discriminant. > Good point. Would "of scalar type T" be clear enough? >> expression of type T is said to "determine a variant" of P if >> either >> - the value of the expression is static; or >> - the nominal subtype of the expression is static and >> constrained, > > I don't think expressions have nominal subtypes. Objects, views of > objects, and names of objects do. Maybe "the expression is the name > of an object whose nominal subtype ..."? > You're exactly right. I didn't follow the case-statement template closely enough (the analogous case statement wording includes "If the selecting_expression IS A NAME having a static and constrained nominal subtype"). Your suggested wording addresses the problem because (as you know) every name (as opposed to expression) has a nominal subtype (that was the point of (AI05-0006). >> [Note: The "Exactly one" wording handles the case where the static >> subtype has no values.] > > The empty subtype case would necessarily raise an exception, by the > rule below, right? > > I think we'd better explicitly outlaw the empty subtype case. > Otherwise this is legal: > > type T(D: boolean) is > record > case D is > when True | False => ...; > end case; > end record; > > subtype Empty is Boolean range True..False; > > procedure P(X: Empty) is > ... T'(D => X, ...) ... > > because there's "exactly one". I think this example should be illegal > -- it seems like a (small) implementation headache to allow it, and > it's useless. > Sound good to me. >> Append to the end of 4.3.1(19). >> >> If the value of a discriminant is given by a non-static >> expression which is required by the above rules to determine > "that" > >> a variant of some variant_part [of the type of the aggregate], >> and if furthermore the evaluation of that expression > Might be better to leave out "if furthermore"? > >> yields a value which does not belong to the nominal subtype > "that" > >> of the expression, then Program_Error is raised. > > This can only happen for invalid values, right? > It's worth pointing that out in the AARM -- i.e. it's a very-corner > case. > Right. Once again, the answer is "follow the example of case statements". For case statements, we have AARM 5.4(13.a): Ramification: In this case, the value is outside the base range of its type, or is an invalid representation. >> The "and constrained" part of the "static and constrained" >> wording is important because of 4.6's wording about how subtype >> conversions work: >> After conversion of the value to the target type, if the target >> subtype is constrained, a check is performed that the value satisfies >> this constraint. > > I don't understand that point. Example, please? It's part of the "make sure uninitialized variables lead to bounded errors, not erroneousness" stuff. I guess the real reason for this wording is to keep things as similar as possible to the treatment of case statements. > P.S. Thanks for writing this up. And thanks for your usual detailed and insightful review. **************************************************************** From: Bob Duff Sent: Friday, August 23, 2013 11:55 AM > Would "of scalar type T" be clear enough? I suppose. A nitpicker might then say, "But scalar types can't have discriminants." To which ARG should respond, "!no action". Hopefully without much discussion. Or we could say "discriminant whose type is T", or even "discriminant whose (scalar) type is T". Or "discriminant that is of type T". (Not "which", please!) I'm quite sure I've run into this frustrating English ambiguity many times. > And thanks for your usual detailed and insightful review. You're welcome. **************************************************************** From: Erhard Ploedereder Sent: Friday, August 23, 2013 6:24 AM > When we get around to discussing amendments in Pittsburgh, I'd like to > consider relaxing the requirement that a discriminant value in a > record aggregate must be static if it governs a variant part. A few questions and observations: Do we have other instances in the language, were "when statically known, then compile-time check / error else run-time check / exception" semantics apply? Would this be the first rule that requires compilers to carry detailed structural information about records into the run-time (be it as descriptors or as inline case-distinction code)? Or do they already, anyway? Put differently, what is the implementation model? Easily done as part of aggregate building, or hard to do by before-hand checking? Coding guidelines that prohibit the use of exceptions need to be amended with a "static aggregates only" rule. How to tell them? On the C_E or P_E: in for a penny ... my vote goes for C_E for sure. > It really doesn't matter, either here or for case statements, because > 13.9.1(9) explicitly permits raising either exception. Not so, because 13.9.1(9) talks about scalar values only. I see no scalar value on the screen here. **************************************************************** From: Bob Duff Sent: Friday, August 23, 2013 10:13 AM > A few questions and observations: > > Do we have other instances in the language, were > "when statically known, then compile-time check / error > else run-time check / exception" > semantics apply? Case statements. If you say: type T is range 1 .. 10; X : T := 2; case X**100 is when 1 => ...; when 2..10 => ...; the compiler is allowed to compute the correct answer for X**100, even though it's (likely) outside T'Base. Then it must raise an exception. (GNAT even has an option to do unbounded arithmetic at run time, although I'm not sure if it works in this context.) Same for "case X is ..." where X is an uninitialized variable, and therefore could have a value outside what the full-coverage rules checked. Ada 83 made this case erroneous, but Tucker wanted to make the use of uninit vars a Bounded Error in Ada 95, hence this rule. > Would this be the first rule that requires compilers to carry detailed > structural information about records into the run-time (be it as > descriptors or as inline case-distinction code)? Or do they already, > anyway? Put differently, what is the implementation model? > Easily done as part of aggregate building, or hard to do by > before-hand checking? I don't see any need to "carry detailed structural information..." -- not sure what you mean. The implementation simply assigns the needed components into the object. Steve's rules preserve the important property: the compiler knows which variant is selected. But as I suggested, we should get some implementation experience before standardizing this new feature. Maybe I'm wrong, and it will be a nightmare to implement. > Coding guidelines that prohibit the use of exceptions need to be > amended with a "static aggregates only" rule. How to tell them? Shrug. It's not our job to worry about coding conventions. People who have such conventions simply need to avoid invalid values. ("Simply" -- hah! ;-) ) Any use of invalid values can raise an exception, so they already have that problem in many other cases. > On the C_E or P_E: in for a penny ... my vote goes for C_E for sure. Ah, that's the same penny I flipped. ;-) > > It really doesn't matter, either here or for case statements, > > because 13.9.1(9) explicitly permits raising either exception. > > Not so, because 13.9.1(9) talks about scalar values only. I see no > scalar value on the screen here. The value we're talking about is the value of the discriminant, which must be scalar. And unless I'm confused, it can only be out of bounds if it is invalid. **************************************************************** From: Steve Baird Sent: Friday, August 23, 2013 11:36 AM >> When we get around to discussing amendments in Pittsburgh, I'd like >> to consider relaxing the requirement that a discriminant value in a >> record aggregate must be static if it governs a variant part. > > A few questions and observations: > > Do we have other instances in the language, were > "when statically known, then compile-time check / error > else run-time check / exception" > semantics apply? > As Bob pointed out, this entire AI is modeled on the existing treatment of case statements. I think that is the real answer to your question. However, there are also a few cases (not at all related to this AI) where we disallow accessibility violations statically but then also have runtime checks in expanded bodies (where legality checks don't apply). I don't think these are relevant to this discussion, but your question did bring them to mind. > Would this be the first rule that requires compilers to carry detailed > structural information about records into the run-time (be it as > descriptors or as inline case-distinction code)? Or do they already, > anyway? Put differently, what is the implementation model? > Easily done as part of aggregate building, or hard to do by > before-hand checking? I don't see any such requirement here. This is a small change with no complicated dynamic semantics (or at least that is the intent). >> It really doesn't matter, either here or for case statements, because >> 13.9.1(9) explicitly permits raising either exception. > > Not so, because 13.9.1(9) talks about scalar values only. I see no > scalar value on the screen here. As Bob points out, any discriminant that governs a variant part must be a scalar. **************************************************************** From: Erhard Ploedereder Sent: Friday, August 23, 2013 11:45 AM >>> > > It really doesn't matter, either here or for case statements, >>> > > because 13.9.1(9) explicitly permits raising either exception. >> > >> > Not so, because 13.9.1(9) talks about scalar values only. I see no >> > scalar value on the screen here. > The value we're talking about is the value of the discriminant, which > must be scalar. And unless I'm confused, it can only be out of bounds > if it is invalid. But it isn't out of bounds as far as the declaration of the discriminant of the type is concerned. The value merely does not match up with the discriminant value implied by the other components of the aggregate. E.g., on the Make(Zz) call, I do not see any invalid value anyplace, merely a bad combination of discriminant value and components. After all, an aggregate (D => D) would be o.k. for that call. So how could the use of the parameter D be an invalid value in one aggregate, but a valid value in the other of the same type? **************************************************************** From: Steve Baird Sent: Friday, August 23, 2013 12:02 PM I think the source of the confusion here may be an issue Bob identified (and suggested a fix for): my original proposal failed to include a requirement that the discriminant expression must be a name in order for this new rule to apply. So let's assume that correction has been incorporated and then take a look at the question you raise. If we evaluate an expression which happens to also be a name and that evaluation yields a scalar value which is outside of the nominal subtype of that name, I think 13.9.1(9) applies. **************************************************************** From: Bob Duff Sent: Friday, August 23, 2013 12:10 PM > But it isn't out of bounds as far as the declaration of the > discriminant of the type is concerned. The value merely does not match > up with the discriminant value implied by the other components of the > aggregate. E.g., on the Make(Zz) call, I do not see any invalid value > anyplace, merely a bad combination of discriminant value and components. > After all, an aggregate (D => D) would be o.k. for that call. So how could > the use of the parameter D be an invalid value in one aggregate, but a > valid value in the other of the same type? We have: function Make (D : S) return Rec is Zz is outside S, so Make(Zz) will raise C_E, and we don't get to evaluating the aggregate, and the rule doesn't apply. The case where the rule we're talking about happens like this: X : S; -- happens to be initialized to stack junk that -- looks like Zz. ... Make(X) ... -- attempt to pass the invalid value Zz The compiler is allowed to check that X is in bounds on the call, in which case C_E will be raised. AdaMagic does that check, and I think recent versions of GNAT do too. But that check is not required. If the compiler chooses not to do the check, then that's the case where the rule we're talking about applies -- we need to say that the aggregate itself raises an exception. As Steve said, this stuff is exactly analogous to the case statement. The full-coverage rules attempt to require at compile time that every case is covered. But there's a loophole in the case of "case Uninitialized_Var is...", so we detect that at run time. The other loophole, also covered by the same rule, is where the discriminant is of type Integer, and Integer'Base is 32 bits, and you pass X**48 (where X = 2), and the compiler chooses to hold the correct value of 2**48 in a 64-bit register. The case statement or variant record covers all values of type Integer'Base, but it doesn't cover 2**48 (it's not even allowed to) -- so we need a run-time check. **************************************************************** From: Brad Moore Sent: Friday, August 23, 2013 2:13 PM ... > The compiler is allowed to check that X is in bounds on the call, in > which case C_E will be raised. AdaMagic does that check, and I think > recent versions of GNAT do too. > > But that check is not required. If the compiler chooses not to do the > check, then that's the case where the rule we're talking about applies > -- we need to say that the aggregate itself raises an exception. This says to me that the choice between C_E and P_E should be not based on a coin flip. If the compiler does the check, then C_E is raised. If the compiler doesn't do the check, then one would hope that the same exception is at least raised, so that different compilers generate the same exception for better portability. **************************************************************** From: Bob Duff Sent: Friday, August 23, 2013 2:56 PM I think everybody so far (including my coin) has voted for C_E. ;-) I'm a big fan of portability, but I don't think case is a very important one. **************************************************************** From: Gary Dismukes Sent: Friday, August 23, 2013 2:56 PM > I think everybody so far (including my coin) has voted for C_E. ;-) Another vote for C_E. > > I'm a big fan of portability, but I don't think case is a very > important one. Agreed. **************************************************************** From: Erhard Ploedereder Sent: Friday, August 23, 2013 4:55 PM > We have: > > function Make (D : S) return Rec is > > Zz is outside S, so Make(Zz) will raise C_E, and we don't get to > evaluating the aggregate, and the rule doesn't apply. O.k. Agreed there. What I had in mind (and misremembered the signature of the original Make) was function Make (D : Enum) return Rec is begin if Oracle then return (D => D, Foo => 123, Bar => 456); else return (D => D); end if; end; Make(Zz); No invalid value in sight, yet the same issue of run-time checking completeness of the aggregate. **************************************************************** From: Bob Duff Sent: Friday, August 23, 2013 5:46 PM > O.k. Agreed there. What I had in mind (and misremembered the signature > of the original Make) was > > function Make (D : Enum) return Rec is > begin > if Oracle then return (D => D, Foo => 123, Bar => 456); > else return (D => D); Ah, now I see what you meant by "requires compilers to carry detailed structural information about records into the run-time". Fortunately, that is not necessary. Both of the above aggregates are illegal in Ada, and will still be illegal after Steve's suggested change, if I understand correctly. The first aggregate would be legal if the nominal subtype of D were S, as in Steve's original example. The second aggregate would be illegal (because S selects the variant with Foo and Bar, and they're missing from the aggregate). > end if; > end; > > Make(Zz); > > No invalid value in sight, yet the same issue of run-time checking > completeness of the aggregate. Right, that would be a big problem if it were legal. Steve's change preserves the property that the compiler knows which variant is being selected. In the above code, the compiler can't know that, so it is (still) illegal. After Steve's change, it's just like case statements: full-coverage checking is a compile-time thing, except for the two loopholes I noted. (I'd like to close those loopholes statically, too, but that's off-topic for this AI. The same two loopholes will exist for variant-record-aggregates as already exist for case statements.) To clarify the compiler algorithm: Determine which variant is being used, based on the static value of the discriminant (as in Ada 83) or the static nominal subtype of the discriminant (this is the new part). If this can't be done statically, it's illegal. Generate code to: - (Also new) check that the value of the discriminant is within that static nominal subtype, and raise C_E if not. This can only fail in the two loophole cases I mentioned. - Build the aggregate (no changes here). Seems simple enough, implementation-wise. Nothing fancy is going on at run time. **************************************************************** From: Tucker Taft Sent: Saturday, August 24, 2013 6:32 AM > When we get around to discussing amendments in Pittsburgh, I'd like to > consider relaxing the requirement that a discriminant value in a > record aggregate must be static if it governs a variant part. ... We bumped into this problem when writing the SofCheck Inspector, aka AdaCore CodePeer, in the "factory" routines for value numbers. By the way, my other pet peeve in this general vicinity is when defining a variant record, it is annoying that the nested variant parts having to cover the full range of the discriminant, rather than just that part of the range of the discriminant that is possible in the nested variant. E.g. case D is when 1..5 => X : Float case D is when 1 => ... when 2..5 => ... when others => ... -- Why on earth is this needed? Just a knit, but I bump into it *every time* I define a variant record with nested variants. **************************************************************** From: Gary Dismukes Sent: Sunday, August 25, 2013 9:13 AM > Just a knit, but I bump into it *every time* I define a variant record with > nested variants. knit => nit :-) Yes, I've always found that very irritating (since the early days of Ada). Would be nice to address that one as well. **************************************************************** From: Brad Moore Sent: Sunday, August 25, 2013 1:36 PM I also found this to be very annoying in the past, and agree it would be nice to see a fix. The challenge would be to address this problem without breaking backwards compatibility. I think it could work by continuing to allow the extra redundant coverage to be specified, but not requiring it to be specified. Compiler vendors would then probably want to issue warnings to guide programmers to remove unnecessary discriminant coverage. **************************************************************** From: Randy Brukardt Sent: Monday, August 26, 2013 7:42 PM ... > By the way, my other pet peeve in this general vicinity is when > defining a variant record, it is annoying that the nested variant > parts having to cover the full range of the discriminant, rather than > just that part of the range of the discriminant that is possible in > the nested variant. E.g. > > case D is > when 1..5 => > X : Float > case D is > when 1 => ... > when 2..5 => ... > when others => ... -- Why on earth is this needed? > > Just a knit, but I bump into it *every time* I define a variant record > with nested variants. Is "knit" a Harvard spelling for "nit", or did your iPhone get you? ;-) Anyway, I've thought about this problem several times in the past, and pretty much concluded that it is unsolvable. First of all, there is the compatibility problem that Brad mentioned. I don't like his solution of "optional" coverage, mainly because it would seem to require to keeping track of as many subtypes as there are nesting levels, and checking the coverage for each (if any work, then it is OK, otherwise it is bad). That seems too complicated to me. Second, I don't care much about the integer case Tucker showed here. Every time this has happened to me (and it has been frequent), the discriminant is some sort of enumeration. For an example from our compiler (greatly simplified): case Kind is when A_Type => ... when A_Package => ... when A_Function | A_Procedure | An_Entry => -- Common components here (parameter lists among others). case Kind is when A_Function => Return_Type : Type_Number; when A_Procedure => ... when An_Entry => ... when others => -- ** Junk ** end case; when An_Object | An_Exception | A_Generic_Formal_Object | A_Parameter => -- Common components here. (These all have lists of identifiers.) case Kind is when An_Object => ... when An_Exception => ... when A_Generic_Formal_Object => ... when A_Parameter => ... when others => -- ** Junk ** end case; when ... => end case; The "others" here are actively harmful, as they prevent finding maintenance errors in these declarations. (If a new enumeration is added to the list of callable entities, it also has to be added to the inner case statement. This would be a compile-time check without the "others"). The problem here is that these are (or might be) discontiguous ranges. I would be unhappy to solve this problem only for contiguous ranges, as that would reintroduce a maintenance problem that we just got rid of in Ada 2012 (a need to figure out an order for enumeration literals to make memberships and subtypes possible). A possible solution to this problem would be to make a requirement that the problem is only solved for limbs that are described by a single named subtype. We could use static predicates to describe the above examples and thus are able to describe any such problem in Ada 2012. I briefly thought that the problem was already solved in Ada 2012 (for named subtypes): subtype Callable_Kind is Entity_Kind with Static_Predicate => Callable_Kind in A_Function | A_Procedure | An_Entry; subtype Multiname_Kind is Entity_Kind with Static_Predicate => Multiname_Kind in An_Object | An_Exception | A_Generic_Formal_Object | A_Parameter; case Kind is when A_Type => ... when A_Package => ... when Callable_Kind => -- Common components here (parameter lists among others). case Callable_Kind'(Kind) is when A_Function => Return_Type : Type_Number; when A_Procedure => ... when An_Entry => ... end case; when Multiname_Kind => -- Common components here. (These all have lists of identifiers.) case Multiname_Kind'(Kind) is when An_Object => ... when An_Exception => ... when A_Generic_Formal_Object => ... when A_Parameter => ... end case; when ... => end case; I eventually realized that the qualifications aren't allowed here (the discriminant name must stand alone). But this does provide a possible compatible solution: if we made this legal (when the subtype of the case limb and that of the qualification on the choice match), then we wouldn't need any further language changes nor would there be a compatibility issue (as this isn't currently legal). I realize it is a bit clunky, but any other solution is incompatible or complex. (It's certainly better than anything I've came up with in the past for this problem, mostly because Ada 2012 now can describe discontiguous static subtypes.) I could also see making the change only for explictly named subtypes (without the qualification); those are much more rare in variants than just listing things explicitly and thus that would limit the compatibility problem. (But I'm dubious that this problem is important enough to allow a compatibility problem.) **************************************************************** From: Tucker Taft Sent: Monday, August 26, 2013 8:06 PM > Is "knit" a Harvard spelling for "nit", or did your iPhone get you? > ;-) Neither. It was a brain freeze. > Anyway, I've thought about this problem several times in the past, and > pretty much concluded that it is unsolvable. > > First of all, there is the compatibility problem that Brad mentioned. I don't understand the compatibility problem. You are allowed to have an "others" part that doesn't cover anything, aren't you? Here it would seem to be easy enough to say that if the "others" part didn't cover any values, then it should have "null;" for its component list as well. > ... The problem here is that these are (or might be) discontiguous > ranges. I would be unhappy to solve this problem only for contiguous ranges,... I agree, it should support discontiguous ranges. Case statements already allow individual case alternatives to cover a discontiguous range, so compilers are perfectly capable of dealing with this problem. I would say that in a nested variant, the variant part must cover every value covered by the enclosing "when". That doesn't need to be a contiguous range. It can certainly cover more values than that (so long as they are within the base range of the discriminant subtype), but I would say that if a given variant alternative covers *no* values from the enclosing "when" then it must have a "null;" component list. > ... any other solution is incompatible or complex. I don't see the incompatibility nor the big complexity. **************************************************************** From: Randy Brukardt Sent: Monday, August 26, 2013 8:51 PM ... > > First of all, there is the compatibility problem that Brad > > mentioned. > > I don't understand the compatibility problem. You are allowed to have > an "others" part that doesn't cover anything, aren't you? Sure, but that only works if the user *used* an "others" part. What if they listed the other values instead? I'm assuming that we want the coverage rules to be checked on the "useful" subtype of the discriminant (the one from the case limb) rather than the nominal subtype of the discriminant. In that case, mentioning values outside of that are illegal. If we had a simplified version of my example: case Kind is when A_Type => ... when A_Package => ... when A_Function | A_Procedure | An_Entry => -- Common components here (parameter lists among others). case Kind is when A_Function => Return_Type : Type_Number; when A_Procedure => ... when An_Entry => ... when A_Type | A_Package => null; -- ** Junk ** end case; end case; For those of us that try to avoid "others", it makes sense to write this if the enumeration range is short enough. If the subtype of Kind is based on the case limb, instead of the entire nominal subtype of Kind, writing A_Type and A_Package is illegal. I don't think this would be very common, but it certainly is an incompatibility. > Here it would seem to be easy enough to say that if the "others" part > didn't cover any values, then it should have "null;" for its component > list as well. That also would be an incompatibility, but it would be one that would detect bugs, so I think we could let it slide. > > ... The problem here is that these are (or might be) discontiguous > > ranges. I would be unhappy to solve this problem only for > contiguous ranges,... > > I agree, it should support discontiguous ranges. Case statements > already allow individual case alternatives to cover a discontiguous > range, so compilers are perfectly capable of dealing with this > problem. Huh? Just because a case limb can be discontiguous doesn't mean that the subtype of a case selector (a totally different thing) can be discontiguous. A better argument would be that the subtype of a case selector can be discontiguous because of Ada 2012 rules, in which case you are right, but for the wrong reasons. :-) I'm not sure if Ada 2012 requires that (I haven't implemented this yet); I know it will be a mess to do in our compiler. (If we have to do it anyway, however, it is irrelevant how hard it is.) > I would say that in a > nested variant, the variant part must cover every value covered by the > enclosing "when". That doesn't need to be a contiguous range. It can > certainly cover more values than that (so long as they are within the > base range of the discriminant subtype), but I would say that if a > given variant alternative covers *no* values from the enclosing "when" > then it must have a "null;" component list. I object to creating new coverage rules for a variant part. These are far too complex already, and trying to separate the current rules into case and variant parts (that is all shared currently) sounds like a nightmare. (Both for wording and for implementation; I know we share the implementation.) I'm OK with changing the subtype on which those rules are applied; we've done that many times in maintaining the language. But not with allowing things that would otherwise be illegal just because it is in a variant part. > > ... any other solution is incompatible or complex. > > I don't see the incompatibility nor the big complexity. You've just invented a whole new set of coverage rules (allowing values that the nominal subtype does not currently allow, and as well allowing values to be omitted that are currently required). That's *not* complex? And it's *still* marginally incompatible (perhaps its detecting bugs, but the extra components are harmless and that's no consolation if your program won't compile). In addition, variants can be nested to many levels. It's unclear to me which values that you are allowing to be omitted (and how that would be described). Certainly, if the subtype has to be named as I had proposed, then there is no such problem. I admit that I hadn't considered modifying the coverage rules just for variants, because I considered the equivalence of such rules something that we wouldn't break for a minor issue. (Especially one that has been known for 30 years and certainly hasn't changed in that time.) I think I'd have to see the exact rules you are proposing in order to have some idea as to why you think this is not complex as it seems to me. (I don't see any way to either describe or implement this without treating variants separately.) I have to say that neither of these problems is worth fixing, IMHO. Steve's fix is clever (although it ought to be mentioned that Dan Eilers suggested it in 2001), but it won't help in the majority of the cases where this is a problem. As such, it's pretty marginal. For the second problem, a fix that leaves the others clauses intact doesn't fix the real issue (the loss of compile-time checking of missing items), and a fix that removes the others clauses is incompatible. So I don't see much value to fixing this, certainly not to the level of changing the coverage rules for variants. **************************************************************** From: Tucker Taft Sent: Monday, August 26, 2013 9:00 PM > ... >>> First of all, there is the compatibility problem that Brad >>> mentioned. >> >> I don't understand the compatibility problem. You are allowed to >> have an "others" part that doesn't cover anything, aren't you? > > Sure, but that only works if the user *used* an "others" part. What if > they listed the other values instead? I'm assuming that we want the > coverage rules to be checked on the "useful" subtype of the > discriminant (the one from the case limb) rather than the nominal > subtype of the discriminant. In that case, mentioning values outside of that > are illegal. ... I wouldn't make them illegal. I would only require that if a given variant alternative mentions only values that never can occur for that nested variant part, that it be associated with a "null" component list. That would seem to give adequate checking without the incompatibilities. **************************************************************** From: Randy Brukardt Sent: Monday, August 26, 2013 9:25 PM But as I said, that's still incompatible (there could be components in those variants). I think you could argue that the incompatibility is harmless (the component could never be accessed), but that's still not the same as "no incompatibilities". Anyway, that Legality Rule doesn't fix anything at all, as the maintenance problem still exists and you still need to modify the coverage rules to even allow programmers to avoid it. In particular, if you have type Enum is (A, B, C, D); type Rec (Disc : Enum) is record case Disc is when C => ... when A | B | D => -- Common components. case Disc is when A => ... when B => ... when D => ... when others => null; end case; end case; This passes your Legality Rule. If you now add E: type Enum is (A, B, C, D, E); type Rec (Disc : Enum) is record case Disc is when C => ... when A | B | D | E => -- Common components. case Disc is when A => ... when B => ... when D => ... when others => null; end case; end case; We've failed to add a branch for E. This is not detected by your Legality Rule. But this missing compile-time detection of modification errors is the primary reason for modifying these rules in the first place (otherwise, the only effect is that the code is ugly, and that isn't worth the language disruption of any sort of fix). So this Legality Rule doesn't really do anything of value (and if a vendor thinks it does, it makes perfect sense for a warning). There has to be some change to the coverage rules or to the subtype to be covered in order for this to do any good. (The others has to go.) And that's when it gets complex and/or incompatible. **************************************************************** From: Tucker Taft Sent: Monday, August 26, 2013 9:36 PM > But as I said, that's still incompatible (there could be components in > those variants). I think you could argue that the incompatibility is > harmless (the component could never be accessed), but that's still not > the same as "no incompatibilities". True. Strictly speaking, this is an incompatibility. And perhaps there is no reason to make it illegal, but you could certainly encourage compilers to warn about such "silly" variant alternatives. > Anyway, that Legality Rules doesn't fix anything at all, as the > maintenance problem still exists and you still need to modify the > coverage rules to even allow programmers to avoid it. > > In particular, if you have > type Enum is (A, B, C, D); > > type Rec (Disc : Enum) is record > case Disc is > when C => ... > when A | B | D => > -- Common components. > case Disc is > when A => ... > when B => ... > when D => ... > when others => null; > end case; > end case; > > This passes your Legality Rule. If you now add E: > > type Enum is (A, B, C, D, E); > > type Rec (Disc : Enum) is record > case Disc is > when C => ... > when A | B | D | E => > -- Common components. > case Disc is > when A => ... > when B => ... > when D => ... > when others => null; > end case; > end case; > > We've failed to add a branch for E. This is not detected by your > Legality Rule. ... I've lost you here. If you use an "others" then you are never going to detect a missing branch. If you want to take advantage of this new capability, you would certainly have to remove all of the "when others" clauses from your variant records. Having done that, then the compiler *would* find a missing variant alternative for "D". Isn't that solving the most important maintenance problem? **************************************************************** From: Randy Brukardt Sent: Monday, August 26, 2013 10:12 PM > I've lost you here. If you use an "others" then you are never going > to detect a missing branch. If you want to take advantage of this new > capability, you would certainly have to remove all of the "when > others" clauses from your variant records. Having done that, then the > compiler *would* find a missing variant alternative for "D". Isn't > that solving the most important maintenance problem? What "new capability"? I've been trying to get a straight answer from you on that all evening. If you *must* remove the "others" branches, you have a major incompatibility. You said you don't want that. But the only way that I can see to make a simple change is to leave the coverage rules alone and change the subtype of the selector (the discriminant in this case) -- which has this effect (and incompatibility). If you *can* (but don't have to) remove the "others" branches, you must have changed the coverage rules such that they are now different for variants and case statements. This is a major new complexity, at least in description and most likely in implementation as well (depending on how much of the coverage rules a compiler shares). But you claim that there is no new complexity. Thus I can only conclude that the only thing you were planning to change is the new Legality Rule, and I've shown that doesn't work. So now I'm just 100% confused. You are either proposing something very complex and calling it simple, or there is a serious incompatibility, or nothing useful is being changed. Or (hopefully), you have some brilliant new insight that no one has seen before. In any case, please explain precisely what changes you are proposing, and how they would work in the context of language wording. (In this case, implementations follow the language wording very closely -- at least ours does -- and any increase in wording complexity would be matched by a corresponding increase in implementation complexity.) **************************************************************** From: Tucker Taft Sent: Monday, August 26, 2013 10:31 PM > What "new capability"? I've been trying to get a straight answer from > you on that all evening. Sorry, I think we were on different wavelengths here. The "new capability" is that the coverage rules for nested variant parts would be different, namely there is no need to cover a value that cannot "reach" the nested variant part. So we are making programs legal that were illegal before. You could only get the benefit of this "new capability" if you removed the "when others" clauses from your nested variant parts. This seems quite straightforward, but I think you are saying this is a major new complexity. We clearly have different views here. To me nested variant parts are special beasts, and it would be fine if they have special rules about coverage. The "added legality" rule seems optional, and it would be that if you have a variant alternative of a nested variant part where *all* of its choices are impossible for the given nested variant part, then the component list must be "null;". That seems like a "nice to have" but it admittedly would create an incompatibility for some slightly "strange" programs (but of course such programs could exist, perhaps due to past maintenance activity). A warning would probably be adequate here to get most of the value. **************************************************************** From: Randy Brukardt Sent: Tuesday, August 27, 2013 1:33 AM > The "new capability" is that the coverage rules for nested variant > parts would be different, namely there is no need to cover a value > that cannot "reach" the nested variant part. > So we are making programs legal that were illegal before. > You could only get the benefit of this "new capability" if you removed > the "when others" clauses from your nested variant parts. > > This seems quite straightforward, but I think you are saying this is a > major new complexity. We clearly have different views here. To me > nested variant parts are special beasts, and it would be fine if they > have special rules about coverage. I think I understand what you're suggesting, and maybe (just maybe) understand the disconnect. (1) My understanding is that you are suggesting that the coverage rules for nested variants be modified to *allow* coverage in the nominal subtype, but not *require* coverage outside of the values of the parent alternative. Is this correct? If so, this appears to be a complex new capability to me. The current coverage rules either require a value to be covered, or not covered. There is no grey area of values that don't have to be covered. So we're talking about a new or heavily modified mechanism. (It's hard enough to get the coverage code to work without having grey areas.) (2) Nested variants aren't special, at least in terms of their coverage rules. They work the same as any other variant or case statement so far as Legality Rules go. The processing for some other things (especially assignment of storage for components) is somewhat special, but that's not related to the coverage rules in my view. Making those rules special means a lot of new work. (3) Issues (1) and (2) also have an effect on the wording of the standard. We don't have any concept of a value that might be covered but doesn't have to be covered today. We'd have to come up with some way to represent that. That had to be added complexity (I don't see how it could be done for free). It seems to me that implementations here follow the wording almost exactly, so added complexity in wording translates to added complexity in implementation. (4) Aside: This rule (as I understand it) would seem to allow weird constructions where some, but not all, of the values of the nominal subtype that aren't in the alternative, are covered. Brad's suggestion eliminated that possibility: either all of the values of the nominal subtype would be covered, or just those in the alternative. That's better from a readability perspective (and it also eliminates most of the grey area), but I think it too would get very complex when you have multiple levels of nesting -- you would have to check the coverage against a list of possible subtypes. (5) Aside 2: For Janus/Ada, implementing this would require either throwing away the entire existing coverage mechanism, or constructing subtypes for every case alternative. That's because the existing mechanism takes the nominal subtype of the selecting expression/discriminant and a representation of the case alternatives. The easiest (but most expensive at runtime) way to deal with that would be to create a subtype for every alternative in order to have one to pass in. That would be particularly expensive for discontiguous subtypes (clearly the most common kind), and would have ripple effects elsewhere in the compiler (more subtypes -> slower loading of withed compilation units [which is quadratic in the number of subtypes as we eliminate duplicates on the fly] -> slower compilation speeds). The alternative would be to totally replace the inputs to the algorithm with some other data structure; certainly possible but not easy. I would expect that many other compilers would have a similar issue because changing from a nominal subtype (something that's always a first-class symboltable item) to a determined-by-context subtype (something that usually doesn't exist in the symboltable) would have some significant cost. (After all, there is no place in Ada currently where we need a discontiguous anonymous subtype. If we hadn't already added static predicates in Ada 2012 to give discontiguous subtypes, I would consider the cost of adding that as completely unacceptable for this problem; as it is, adding them implicitly is pretty expensive) Anyway, I hope you can see why I think this would add substantial new complexity to both the wording of the Standard and of the implementations. True, this isn't of the level of interfaces, but then again the value of the change is also much, much less. I think we can agree to disagree on this one -- but I do insist that this be a separate proposal from Steve's original one, because I don't think that they necessarily should tied together. (The problems are quite different.) > The "added legality" rule seems optional, and it would be that if you > have a variant alternative of a nested variant part where *all* of its > choices are impossible for the given nested variant part, then the > component list must be "null;". > That seems like a "nice to have" but it admittedly would create an > incompatibility for some slightly "strange" > programs (but of course such programs could exist, perhaps due to past > maintenance activity). A warning would probably be adequate here to > get most of the value. Probably this Legality Rule isn't worth the headache. It's hard to justify introducing an incompatibility here, especially at this late date. (If we had considered this for Ada 2005, maybe the thinking would have been different.) **************************************************************** From: Tucker Taft Sent: Tuesday, August 27, 2013 5:26 PM > I think I understand what you're suggesting, and maybe (just maybe) > understand the disconnect. > > (1) My understanding is that you are suggesting that the coverage > rules for nested variants be modified to *allow* coverage in the > nominal subtype, but not *require* coverage outside of the values of > the parent alternative. Is this correct? Yes. > ... If so, this appears to be a complex new capability to me. We all have our own definition of "complex" I guess! > ... The > current coverage rules either require a value to be covered, or not covered. > There is no grey area of values that don't have to be covered. So > we're talking about a new or heavily modified mechanism. (It's hard > enough to get the coverage code to work without having grey areas.) > > (2) Nested variants aren't special, at least in terms of their > coverage rules. In my view, that is the problem! I am proposing that they *should* be treated specially, since they have a very limited set of values that are possible, namely those of the enclosing variant alternative. > (3) Issues (1) and (2) also have an effect on the wording of the standard. Doesn't seem that complicated. Given the addition of predicates, we could simply say that for the purposes of checking that all needed values are covered, the (implicit) predicate that applies to a discriminant used in a nested variant part is that it is in the range of values of the choice list for the enclosing variant alternative. For deciding what values that must *not* be covered, the usual nominal subtype would apply, (primarily for upward compatilibity). **************************************************************** From: Randy Brukardt Sent: Wednesday, August 28, 2013 11:43 AM ... > > (2) Nested variants aren't special, at least in terms of their > > coverage rules. > > In my view, that is the problem! I am proposing that they > *should* be treated specially, since they have a very limited set of > values that are possible, namely those of the enclosing variant > alternative. I don't mind changing their *subtype* to reflect the limited values, but changing the actual coverage rules is a different matter, as those are already very complex to describe and implement. > > (3) Issues (1) and (2) also have an effect on the wording of the standard. > > Doesn't seem that complicated. Given the addition of predicates, we > could simply say that for the purposes of checking that all needed > values are covered, the (implicit) predicate that applies to a > discriminant used in a nested variant part is that it is in the range > of values of the choice list for the enclosing variant alternative. > For deciding what values that must *not* be covered, the usual nominal > subtype would apply, (primarily for upward compatibility). For this, I would say that you have to prove it. The current wording has no distinction between what must be covered and what must not be -- it's either required to be covered or required to not be covered. I don't see any simple way to separate those concerns short of completely duplicating the rules. So, please provide actual wording to show how this would work. There is no other way to prove or disprove your assertion. **************************************************************** From: Tucker Taft Sent: Thursday, August 29, 2013 7:58 AM > So, please provide actual wording to show how this would work. There > is no other way to prove or disprove your assertion. Here is a possible way of accomplishing this: Revise 3.8.1(14-16) as follows: The possible values of the discriminant of a variant_part shall be covered as follows: * If the discriminant is of a static constrained scalar subtype, then, except within an instance of a generic unit, each non-others discrete_choice shall cover only values in that subtype that satisfy its predicate, and each value of that subtype that satisfies its predicate shall be covered by some discrete_choice [(either explicitly or by others)]{, either of a discrete_choice_list of this variant_part, or, if the variant_part is itself nested within a variant, by a discrete_choice_list of some non-enclosing variant of an enclosing variant_part}; * If the type of the discriminant is a descendant of a generic formal scalar type, then the variant_part shall have an others discrete_choice{, or if the variant_part is itself nested within a variant, some non-enclosing variant of an enclosing variant_part shall have an others discrete_choice}; **************************************************************** From: Bob Duff Sent: Thursday, August 26, 2013 9:34 AM > By the way, my other pet peeve in this general vicinity is when > defining a variant record, it is annoying that the nested variant > parts having to cover the full range of the discriminant, rather than > just that part of the range of the discriminant that is possible in the nested variant. E.g. > > case D is > when 1..5 => > X : Float > case D is > when 1 => ... > when 2..5 => ... > when others => ... -- Why on earth is this needed? I agree this is annoying, but I am opposed to fixing it. It doesn't come up very often. "Every time I write a nested variant" just isn't very often. When it does come up, the cost is minor: one extra line of code saying: when others => null; -- can't happen I don't buy the "big maintenance hazard" idea. You add an enumeration literal. The full coverage rules cause the OUTER variant to fail to compile. So you fix that. It's pretty obvious at that point that you need to inspect the code in the INNER variant, and that code is sitting right there in front of you. I suggest Randy and Tucker quit arguing about how hard it is to fix this problem until we agree that it might be worth fixing. I mean, I don't think it's worth fixing even if the fix is trivial wording effort and small compiler effort. What do others think? And I hope we all agree that it's not worth ANY incompatibilities! P.S. By the way, this whole sub-thread is off-topic. It has nothing to do with aggregates. **************************************************************** From: Jeff Cousins Sent: Thursday, August 29, 2013 9:54 AM I'm tending towards the "it's not worth bothering with" view. It's less irritating than the need to use different identifiers for components of different alternatives of a variant. **************************************************************** From: John Barnes Sent: Thursday, August 29, 2013 1:37 PM >It's less irritating than the need to use different identifiers for >components of different alternatives of a variant. I agree. That has always annoyed me. **************************************************************** From: Randy Brukardt Sent: Thursday, August 29, 2013 4:39 PM > I suggest Randy and Tucker quit arguing about how hard it is to fix > this problem until we agree that it might be worth fixing. I mean, I > don't think it's worth fixing even if the fix is trivial wording > effort and small compiler effort. I strongly disagree with this on general principles (having nothing to do with this case). *Any* bug that can be fixed compatibly with a trivial wording effort and small compiler effort should be fixed. In almost all such cases, either there is no trivial compatible fix (as in this case), or it is arguable if even there is a bug (not in this case, being required to cover values that can never happen is clearly silly at best and requires writing confusing code). So we don't usually get to this point. My entire purpose of discussing this with Tuck was to see if there was a trivial fix that I had missed. If not, the problem is insufficiently common to justify fixing. I think Tucker proved the point with "...some non-enclosing variant of an enclosing variant_part..." :-) ... > When it does come up, the cost is minor: one extra line of code > saying: > > when others => null; -- can't happen This isn't minor. Untestable paths are a big deal in some environments, and being unable to avoid writing them may very well prevent the use of nested variants at all. And they're something I use often (they appear in most of my variant records) because they bring an extra level of use checking that does not happen if you toss all of the components together into a single variant. (And I do use a lot of variant records - the OOP alternative requires writing and debugging far more code to the extent that it is often impractical, especially for a one-man shop like mine.) > I don't buy the "big maintenance hazard" idea. I don't think anyone ever said "big". But no programming language should require maintenance hazards that can be avoided. > You add an > enumeration literal. The full coverage rules cause the OUTER variant > to fail to compile. So you fix that. It's pretty obvious at that > point that you need to inspect the code in the INNER variant, and that > code is sitting right there in front of you. The notion that the programmer "obviously needs" to do something is bogus in general. After all, we don't need strong typing, because its "obvious" that the programmer "needs" to combine correct types. Moreover, I find that I depend almost completely on Ada's completeness checking. When I add an enumeration these days, it's a completely mindless operation - the only thing I do is think about where the enumeration needs to be added to case alternatives and the like. There is no chance that I would look at the inner variant without an error; any place where completeness checking fails is a danger point. (I didn't work this way in the past, but compilations are now fast enough that there isn't much value to expending mental effort to save compilation time.) The only mitigating factor here is that it is not unlikely that the missing variant alternative has no components, so we would get code that works correctly even though it is formally wrong. Arguably, that's even worse (since the "can't happen" comment is now a lie), but that is a matter of taste. Again, to summarize, I think this problem ought to be fixed, but only if the solution is trivial. And no such solution has yet been proposed. **************************************************************** From: Bob Duff Sent: Monday, August 26, 2013 4:57 PM > Again, to summarize, I think this problem ought to be fixed, but only > if the solution is trivial. And no such solution has yet been proposed. I don't think we're going to find a solution that is trivial in terms of compiler effort. But anyway, please make sure that this issue is a separate AI from the "aggregates and variant parts" AI (which I DO think is worth fixing). **************************************************************** From: Jean-Pierre Rosen Sent: Monday, September 2, 2013 2:55 AM > I mean, I don't think it's worth fixing even if the fix is trivial > wording effort and small compiler effort. > What do others think? 1) I never write: when others => null; -- can't happen but: when others => Failure ("Impossible"); and guess what? Every now and then, it gets fired (especially when the case selector is a subtype that covers more values than I thought). 2) I think it is a minor annoyance. An annoyance, but minor (i.e. I can live with it). When I hit it, I generally think "Oh, it's annoying, but that's the price to pay for all the other nice features of Ada". To be honest, when I see how the language is used in industry, I think energy should be spent on teaching people how to use the language properly, not on fixing minor issues. But of course, it is not in the charter of the ARG... **************************************************************** From: Jean-Pierre Rosen Sent: Monday, September 2, 2013 3:26 AM > 1) I never write: > when others => null; -- can't happen > but: > when others => Failure ("Impossible"); Ooops, before anybody else notices: Of course, this is bogus, since it is a record case, not a statement case. I am too used to arguing against "when others => null"... **************************************************************** From: Bob Duff Sent: Monday, September 2, 2013 8:36 AM > Of course, this is bogus, since it is a record case, not a statement > case. I was just trying to think of how to make a joke about your mistake, but I couldn't figure it out. Something about how that's "Impossible" syntax in a variant record. ;-) >... I am too used to arguing against "when others => null"... Yeah! I like to say that "others" means ALL the others, including the ones you might invent next week or next year. **************************************************************** From: Tucker Taft Sent: Monday, January 26, 2015 5:38 PM Here is an attempt to simplify the wording for this AI. [This is version /02 of this AI - Editor.] I think this is important functionality, as I have worked on several programs that bump into the awkwardness that occurs any time you have a variant-part where a given variant has more than one choice in its choice list. Trying to create aggregates for these beasts is painful. I wanted to rewrite this in a way that didn't introduce yet another term. Nitpickers might complain about the use of "(selected)" in the run-time check, but I think the meaning is pretty clear. **************************************************************** From: Tucker Taft Sent: Monday, January 26, 2015 5:43 PM The following, which appears at the beginning of the wording, is a lie at this stage: [Editor's note: This wording was Steve's original wording updated with suggestions from Bob and others in e-mail, plus a couple of minor issues that I noticed.] **************************************************************** From: Randy Brukardt Sent: Monday, January 26, 2015 5:50 PM OK, I'll take it out when I file it. [And I did - Editor.] ****************************************************************