!standard 2.8(6) 19-01-21 AI12-0236-1/08 !standard 3.9.2(3) !standard 3.10.2(9.1/3) !standard 3.10.2(16.1/3) !standard 3.10.2(32.2/3) !standard 4.3.2(5.4/3) !standard 4.3.3(15.1/3) !standard 4.4(7/3) !standard 4.5.9(0) !standard 6.2(10/4) !standard 7.5(2.1/5) !standard 8.1(2.1/4) !class Amendment 17-09-06 !status Amendment 1-2012 19-01-15 !status work item 19-01-17 !status ARG Approved 8-1-3 19-01-14 !status work item 17-09-06 !status received 17-06-21 !priority Low !difficulty Medium !subject declare expressions !summary Add a new kind of expression, the declare expression, that allows one to declare local constants and object renamings in an expression context. !problem Ada 2012 greatly enhanced the power of Ada expressions, primarily in order to make the writing of contracts easier. For that reason, if and case expressions were introduced into the language, as well as expression functions and quantified expressions. However, a missing piece of functionality is the ability to bind the result of an expression - for example a function call - to a name, in order to reuse its value. This often leads to repeating the same expression several times within contracts. Take the following subprogram and its postcondition into consideration: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (if Stream.The_File (Stream.Cur_Position'Old) = EOF_Ch then Stream.Cur_Position = Stream.Cur_Position'Old and then Result = EOF elsif Stream.The_File (Stream.Cur_Position'Old) = ASCII.LF then Stream.Cur_Position = Stream.Cur_Position'Old and then Result = Character'Pos (ASCII.LF) else Stream.Cur_Position = Stream.Cur_Position'Old + 1 and then Result = Character'Pos (Stream.The_File (Stream.Cur_Position'Old))); The contract is not extremely complex in and of itself, but the fact that some expressions are repeated makes it more difficult to read - for example, Cur_Position'Old and The_File (Cur_Position'Old). !proposal A *declare expression* allows constant objects and renamings to be declared within an expression. !wording Add a new section 4.5.9, following 4.5.8 "Quantified Expressions": 4.5.9 Declare Expressions Declare expressions provide a way to declare local constants and object renamings in an expression context. Syntax declare_expression ::= declare {declare_item} begin *body*_expression declare_item ::= object_declaration | object_renaming_declaration AARM Reason: We allow "(declare begin expression)" with no declare_items, for uniformity with block statements, which also allow a pointless "declare". Wherever the Syntax Rules allow an expression, a declare_expression may be used in place of the expression, so long as it is immediately surrounded by parentheses. AARM Discussion: The syntactic category declare_expression appears only as a primary that is parenthesized. The above rule allows it to additionally be used in other contexts where it would be directly surrounded by parentheses. This is the same rule that is used for conditional_expressions; see 4.5.7 for a detailed discussion of the meaning and effects of this rule. Legality Rules A declare_item that is an object_declaration shall declare a constant of a nonlimited type. AARM Reason: We disallow limited objects to avoid the horror of task waiting in the middle of an expression. The solution used for controlled objects (wait until the entire expression is finished) isn't appropriate for tasks. This restriction also eliminates build-in-place complexities and the need to activate tasks found in a declare_item. A declare_item that is an object_renaming_declaration (see 8.5.1) shall not rename an object of a limited type that is a function_call, aggregate, a parenthesized expression, qualified_expression, or type_conversion with an operand of one of these, a conditional_expression that has at least one dependent_expression that is one of these, or a declare_expression whose body_expression is one of these. AARM Reason: We disallow renaming limited temporary objects (like the result of a function call) for the same reasons as for stand-alone objects. We do allow renaming existing limited objects as these don't cause any problems. Note that one cannot directly rename an aggregate, parenthesized expression, conditional_expression, or declare_expression (these are not names), but any of these can appear inside of a qualified expression or type conversion (which are names and thus can be renamed). The following are not allowed within a declare_expression: a declaration containing the reserved word 'aliased'; the attribute_designator Access or Unchecked_Access; or an anonymous access type. AARM Reason: We do not want to define accessibility rules for declare_items, as nested declare_expressions cause complexities or usage warts. We want to keep this feature simple to use, understand, and implement. Thus, we do not allow any of the features that would require accessibility rules. Name Resolution Rules If a declare_expression is expected to be of a type T, then the *body*_expression is expected to be of type T. Similarly, if a declare_expression is expected to be of some class of types, then the *body*_expression is subject to the same expectation. If a declare_expression shall resolve to be of a type T, then the *body*_expression shall resolve to be of type T. [Editor's note: The above rule comes from 4.5.7(8/3) for conditional_expressions.] The type of a declare_expression is the type of the *body*_expression. Dynamic Semantics For the evaluation of a declare_expression, the declare_items are elaborated in order, and then the *body_*expression is evaluated. The value of the declare_expression is that of the *body_*expression. Examples The postcondition for Ada.Containers.Vectors."&" (see A.18.2) could have been written: with Post => (declare Result renames Vectors."&"'Result; Length : constant Count_Type := Left.Length + Right.Length; begin Result.Length = Length and then not Tampering_With_Elements_Prohibited (Result) and then not Tampering_With_Cursors_Prohibited (Result) and then Result.Capacity >= Length) [End of new 4.5.9] Modify 2.8(6): * After a semicolon delimiter, but not within a formal_part{,}[ or] discriminant_part{, or declare_expression}. Modify 3.9.2(3): A name or expression of a tagged type is either statically tagged, dynamically tagged, or tag indeterminate, according to whether, when used as a controlling operand, the tag that controls dispatching is determined statically by the operand's (specific) type, dynamically by its tag at run time, or from context. A qualified_expression or parenthesized expression is statically, dynamically, or indeterminately tagged according to its operand. {A conditional_expression is statically, dynamically, or indeterminately tagged according to rules given in 4.5.7. A declare_expression is statically, dynamically, or indeterminately tagged according to its *body*_expression. }For other kinds of names and expressions, this is determined as follows: [Editor's note: This looks like a complete list of exceptions from the usual rule, but we previously did not mention the special rules for conditional_expression. We're fixing that now.] Add after 3.10.2(9.1/3): The accessibility level of a declare_expression (see 4.5.9) is the accessibility level of the *body*_expression. Modify 3.10.2(16.1/3): In the above rules, the operand of a view conversion, parenthesized expression or qualified_expression is considered to be used in a context if the view conversion, parenthesized expression or qualified_expression itself is used in that context. Similarly, a dependent_expression of a conditional_expression is considered to be used in a context if the conditional_expression itself is used in that context{, and a *body_*expression of a declare_expression is considered to be used in a context if the declare_expression itself is used in that context}. Add to the definition of "distributed accessibility" after 3.10.2(32.2/3): * a declare_expression (see 4.5.9) whose *body_*expression has distributed accessibility; or Add after 4.3.2(5.4/3), in the rule for extension aggregates: * a declare_expression (see 4.5.9) whose *body*_expression would violate this rule. [Editor's note: We have to add a semicolon and "nor" to the previous bullet, to match the others in this set.] Adding after 4.3.3(15.1/3): (this allows "others" in *body_*expressions) For a declare_expression (see 4.5.9), the applicable index constraint for the *body*_expression is that, if any, defined for the declare_expression. [Editor's note: We have to change the period to a semicolon on the previous bullet.] Replace 4.4(7/3) with: primary ::= numeric_literal | null | string_literal | aggregate | name | allocator | (expression) | (conditional_expression) | (quantified_expression) | (declare_expression) Add to the end of 6.2(10/4): For a declare_expression, this object is the one associated with the *body*_expression. Modify 7.5(2.1/5): In the following contexts, an expression of a limited type is not permitted unless it is an aggregate, a function_call, a raise_expression, a parenthesized expression or qualified_expression whose operand is permitted by this rule, [or] a conditional_expression all of whose dependent_expressions are permitted by this rule{, or a declare_expression whose body_expression is permitted by this rule}: Add after 8.1(3): - a declare_expression; [Editor's note: This adds it to the list of constructs that have a declarative region. This, together with 8.2(2) means that the immediate scope of a declare_item goes from the beginning of the declare_item to the end of the innermost enclosing declare_expression.] !discussion Note that AI12-0275-1 allows an object_renaming_declaration without a subtype_mark; this applies to declare_items. On the other hand, we continue to require a subtype_indication in an object_declaration, including as a declare_item. The restriction to nonlimited types is to avoid implementation difficulties related to build-in-place and task waiting. A declare expression cannot be static or predicate-static (see 4.9 and 3.2.4). !examples Post => Fun'Result = (declare X : constant T1 := F(A, B); Y : constant T2 := G(C, D); begin (if X > Y then X else Y+X)) Type_Invariant => (declare M : constant Integer := Integer'Max(T.A, T.B); begin (if M > 0 then M > T.C else M < T.D)) Dynamic_Predicate => (declare Q : constant Integer := H(S.A, S.B); R : constant Integer := G(S.C, S.D); begin (if W(Q, R) then F(Q) else Z(R))) X : T := (declare Temp renames Some_Array(Y..Z); begin Temp & Temp); The example in the !problem: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (declare Old_Pos : constant Position := Stream.Cur_Position'Old; The_Char : constant Character := Stream.The_File (Old_Pos); Pos_Unchg : constant Boolean := Stream.Cur_Position = Old_Pos; begin (if The_Char = EOF_Ch then Pos_Unchg and then Result = EOF elsif The_Char = ASCII.LF then Pos_Unchg and then Result = Character'Pos (ASCII.LF) else Stream.Cur_Position = Old_Pos + 1 and then Result = Character'Pos (The_Char))); !corrigendum 2.8(6) @drepl @xbullet or @fa.> @dby @xbullet, @fa, or @fa.> !corrigendum 3.9.2(3) @drepl A @fa or expression of a tagged type is either @i tagged, @i tagged, or @i, according to whether, when used as a controlling operand, the tag that controls dispatching is determined statically by the operand's (specific) type, dynamically by its tag at run time, or from context. A @fa or parenthesized expression is statically, dynamically, or indeterminately tagged according to its operand. For other kinds of @fas and expressions, this is determined as follows: @dby A @fa or expression of a tagged type is either @i tagged, @i tagged, or @i, according to whether, when used as a controlling operand, the tag that controls dispatching is determined statically by the operand's (specific) type, dynamically by its tag at run time, or from context. A @fa or parenthesized expression is statically, dynamically, or indeterminately tagged according to its operand. A @fa is statically, dynamically, or indeterminately tagged according to rules given in 4.5.7. A @fa is statically, dynamically, or indeterminately tagged according to its @i@fa. For other kinds of @fas and expressions, this is determined as follows: !corrigendum 3.10.2(9.1/3) @dinsa @xbullet (see 4.5.7) is the accessibility level of the evaluated @i@fa.> @dinst @xbullet (see 4.5.9) is the accessibility level of the @i@fa.> !corrigendum 3.10.2(16.1/3) @drepl In the above rules, the operand of a view conversion, parenthesized expression or @fa is considered to be used in a context if the view conversion, parenthesized expression or @fa itself is used in that context. Similarly, a @i@fa of a @fa is considered to be used in a context if the @fa itself is used in that context. @dby In the above rules, the operand of a view conversion, parenthesized expression or @fa is considered to be used in a context if the view conversion, parenthesized expression or @fa itself is used in that context. Similarly, a @i@fa of a @fa is considered to be used in a context if the @fa itself is used in that context, and a @i@fa of a @fa is considered to be used in a context if the @fa itself is used in that context. !corrigendum 3.10.2(32.2/3) @dinsa @xbullet (see 4.5.7); or> @dinst @xbullet (see 4.5.9) whose @i@fa has distributed accessibility; or> !corrigendum 4.3.2(5.4/3) @drepl @xbullet having at least one @i@fa that would violate this rule.> @dby @xbullet (see 4.5.7) having at least one @i@fa that would violate this rule; nor> @xbullet (see 4.5.9) whose @i@fa would violate this rule.> !corrigendum 4.3.3(15.1/3) @drepl @xbullet, the applicable index constraint for each @i@fa is that, if any, defined for the @fa.> @dby @xbullet (see 4.5.7), the applicable index constraint for each @i@fa is that, if any, defined for the @fa;> @xbullet (see 4.5.9), the applicable index constraint for the @i@fa is that, if any, defined for the @fa.> !corrigendum 4.4(7/3) @drepl @xindent<@fa@fa<@ ::=@ >@hr @ @ @ @ @fa@ |@ @b@ |@ @fa@ |@ @fa@hr @ @ |@ @fa@ |@ @fa@ |@ (@fa)@hr @ @ |@ (@fa)@ |@ (@fa)> @dby @xindent<@fa@fa<@ ::=@ >@hr @ @ @ @ @fa@ |@ @b@ |@ @fa@ |@ @fa@hr @ @ |@ @fa@ |@ @fa@ |@ (@fa)@hr @ @ |@ (@fa)@ |@ (@fa)@hr @ @ |@ (@fa)> !corrigendum 4.5.9(0) @dinsc Declare expressions provide a way to declare local constants and object renamings in an expression context. @s8<@i> @xindent<@fa@fa<@ ::=@ >@hr @ @ @ @ @b {@fa}@hr @ @ @ @ @b @i@fa> @xindent<@fa@fa<@ ::=@ >@fa@ |@ @fa> Wherever the Syntax Rules allow an @fa, a @fa may be used in place of the @fa, so long as it is immediately surrounded by parentheses. @s8<@i> A @fa that is an @fa shall declare a constant of a nonlimited type. A @fa that is an @fa (see 8.5.1) shall not rename an object of a limited type that is a @fa, @fa, a parenthesized expression, @fa, or @fa with an operand of one of these, a @fa that has at least one @i@fa that is one of these, or a @fa whose @i@fa is one of these. The following are not allowed within a @fa: a declaration containing the reserved word @b; the @fa Access or Unchecked_Access; or an anonymous access type. @s8<@i> If a @fa is expected to be of a type @i, then the @i@fa is expected to be of type @i. Similarly, if a @fa is expected to be of some class of types, then the @i@fa is subject to the same expectation. If a @fa shall resolve to be of a type @i, then the @i@fa shall resolve to be of type @i. The type of a @fa is the type of the @i@fa. @s8<@i> For the evaluation of a @fa, the @fas are elaborated in order, and then the @i@fa is evaluated. The value of the @fa is that of the @i@fa. @s8<@i> The postcondition for Ada.Containers.Vectors."&" (see A.18.2) could have been written: @xcode<@b Post =@> (@b Result @b Vectors."&"'Result; Length : @b Count_Type := Left.Length + Right.Length; @b Result.Length = Length @b @b Tampering_With_Elements_Prohibited (Result) @b @b Tampering_With_Cursors_Prohibited (Result) @b Result.Capacity @>= Length)> !corrigendum 6.2(10/4) @drepl A parameter of a by-reference type is passed by reference, as is an explicitly aliased parameter of any type. Each value of a by-reference type has an associated object. For a parenthesized expression, @fa, or view conversion, this object is the one associated with the operand. For a value conversion, the associated object is the anonymous result object if such an object is created (see 4.6); otherwise it is the associated object of the operand. For a @fa, this object is the one associated with the evaluated @i@fa. @dby A parameter of a by-reference type is passed by reference, as is an explicitly aliased parameter of any type. Each value of a by-reference type has an associated object. For a parenthesized expression, @fa, or view conversion, this object is the one associated with the operand. For a value conversion, the associated object is the anonymous result object if such an object is created (see 4.6); otherwise it is the associated object of the operand. For a @fa, this object is the one associated with the evaluated @i@fa. For a @fa, this object is the one associated with the @i@fa. !corrigendum 7.5(2.1/5) @drepl In the following contexts, an @fa of a limited type is not permitted unless it is an @fa, a @fa, a @fa, a parenthesized @fa or @fa whose operand is permitted by this rule, or a @fa all of whose @i@fas are permitted by this rule: @dby In the following contexts, an @fa of a limited type is not permitted unless it is an @fa, a @fa, a @fa, a parenthesized @fa or @fa whose operand is permitted by this rule, a @fa all of whose @i@fas are permitted by this rule, or a @fa whose @i@fa is permitted by this rule: !corrigendum 8.1(2.1/4) @dinsa @xbullet;> @dinst @xbullet;> !ASIS New ASIS queries are needed. Those are TBD. !ACATS test ACATS B-Test and C-Tests are needed to check that the new capabilities are supported. !appendix From: Tucker Taft Sent: Wednesday, June 21, 2017 7:07 AM Peter Chapin of Vermont Technical College, user of Ada 2012/SPARK 2014 for the "cube" sat , indicated his most frequent annoyance about the contract features of Ada are the inability to give a name to a subexpression and use multiple times in a pre- or post-condition. In most functional languages this is solved using local "let" declarations. In Ada, there seems no need to introduce "let" as a reserved word, but "declare" or "for" or "constant" seem like reasonable keywords to use for this. Here are some examples using constant/declare/for: Post => F’Result = (constant X := F(A'Old, B); Y := G(C, D'Old) => (if X > Y the X else Y)) Type_Invariant => (for M := Integer’Max(T.A, T.B) => (if M > 0 then M > T.C else M < T.D) Dynamic_Predicate => (declare Q := H(S.A, S.B); R := G(S.C, S.D) => (if W(Q, R) then F(Q) else Z(R))); I think "constant" is my favorite, since these are all implicitly constants that must have initial values, while with "declare" one might expect that a type and "constant" be explicit, whereas this is really a different sort of declaration. Also, "for" is relying on the use of ":=" to distinguish it from other sorts of iterators. I have used ";" to allow multiple constant declarations, rather than using a chain of "=>" which would probably mean multiple levels of parentheses as well. You could go with "comma" instead, but that seems too easy to miss in this context. *************************************************************** From: Florian Schanda Sent: Wednesday, June 21, 2017 7:42 AM Note that Raphael already started a proposal for this, see his message on 30/08/2016 15:28. We (Altran + SPARK Team here) think this is an excellent idea. On Wednesday 21 Jun 2017 08:05:30 Tucker Taft @ adacore wrote: > Peter Chapin of Vermont Technical College, user of Ada 2012/SPARK 2014 > for the "cube" sat , indicated his most frequent annoyance about the > contract features of Ada are the inability to give a name to a > subexpression and use multiple times in a pre- or post-condition. In > most functional languages this is solved using local "let" > declarations. In Ada, there seems no need to introduce "let" as a reserved > word, but "declare" or "for" or "constant" > seem like reasonable keywords to use for this. > > Here are some examples using constant/declare/for: > > Post => F’Result = > (constant X := F(A'Old, B); Y := G(C, D'Old) => (if X > Y the X > else Y)) > > Type_Invariant => > (for M := Integer’Max(T.A, T.B) => (if M > 0 then M > T.C else M < > T.D) > > Dynamic_Predicate => > (declare Q := H(S.A, S.B); R := G(S.C, S.D) => (if W(Q, R) then > F(Q) else Z(R))); I believe we also had "in" in the hat: (declare X := Complex_Function (A, B, C); in X + X) > I think "constant" is my favorite, since these are all implicitly > constants that must have initial values, while with "declare" one > might expect that a type and "constant" be explicit, whereas this is > really a different sort of declaration. Also, "for" is relying on the > use of ":=" to distinguish it from other sorts of iterators. I would try to put a new keyword in the ring as well, "let" doesn't really sound like an identifier that people would have used. A grep in the gnat sources for example (1.1M LOC) does not have this as a single identifier (closest is LLet and ULet for lower and upper case letter). Doing the same thing to a big project here also provides no hits. (let X := Complex_Function (A, B, C); in X + X) Just my 2c. *************************************************************** From: Tucker Taft Sent: Wednesday, June 21, 2017 7:51 AM > Note that Raphael already started a proposal for this, see his message > on > 30/08/2016 15:28. Thanks, I was trying to find the earlier proposal, but I failed. I didn't mean to preempt Raphael's proposal. ... > I believe we also had "in" in the hat: > > (declare X := Complex_Function (A, B, C); in X + X) "; in" seems weird to me. But just "in" would be ambiguous with membership. So that kills the "in" syntax for me, at least. ... > I would try to put a new keyword in the ring as well, "let" doesn't > really sound like an identifier that people would have used. A grep in > the gnat sources for example (1.1M LOC) does not have this as a single > identifier (closest is LLet and ULet for lower and upper case letter). I am afraid "let" is just too likely to have been used in many existing Ada programs. Adding a new reserved word is a very heavy burden on any proposal. *************************************************************** From: Tucker Taft Sent: Wednesday, June 21, 2017 8:37 AM Apparently not everyone has seen this AI from Raphael, or (like me) has lost track of it. ===== !standard 11.5 ??-??-?? AI12-????-1/01 !class Amendment ??-??-?? !status work item ??-??-?? !status received ??-??-?? !priority Low !difficulty Low !subject declare expressions !summary Add a new expression, the declare expression, that allows one to declare local bindings in an expression context. !problem Ada 2012 greatly enhanced the power of Ada expressions, primarily in order to make the writing of contracts easier. For that reason, if and case expressions were introduced into the language, as well as expression functions and quantifier expressions. However, a missing piece of functionality is the ability to bind the result of an expression - for example a function call - to a name, in order to reuse its value. This often leads to repeating the same expression/function call several time within contracts. Take the following subprogram and its post-condition into consideration: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (if The_File (Cur_Position'Old) = EOF_Ch then Cur_Position = Cur_Position'Old and then Result = EOF elsif The_File (Cur_Position'Old) = ASCII.LF then Cur_Position = Cur_Position'Old and then Result = Character'Pos (ASCII.LF) else Cur_Position = Cur_Position'Old + 1 and then Result = Character'Pos (The_File (Cur_Position'Old))); The contract is not extremely complex in and of itself, but due to the fact that some expressions are repeated makes it more difficult to read - for example, Cur_Position'Old and The_File (Cur_Position'Old). !proposal We propose introducing a new expression, the declare expression, that allows computation of one or several sub-expressions, and to bind the results to names, in the lexical scope of the expression. The declare expression would allow you to bind names to the computation of expressions where each name is bound to a constant view of an expression, akin to a renaming but with forced const-ness. We chose not to re-use the assignment syntax for the bindings, because the semantics are not the same. We also choose not to re-use the renames syntax, for the same reason. With a tentative syntax for the declare expression, the previous example's post-condition for the Fgetc procedure could be expressed as: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (declare Old_Pos : Int is Cur_Position'Old; Old_Char : Character is The_File (Old_Pos); in (if Old_Char = EOF_Ch then Cur_Position = Old_Pos and then Result = EOF elsif Old_Char = ASCII.LF then Cur_Position = Old_Pos and then Result = Character'Pos (ASCII.LF) else Cur_Position = Old_Pos + 1 and then Result = Character'Pos (Old_Char))); We also propose making the type annotation optional, to allow the user to not clutter his expressions with types in simple cases: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (declare Old_Pos is Cur_Position'Old; Old_Char is The_File (Old_Pos); in (if Old_Char = EOF_Ch then Cur_Position = Old_Pos and then Result = EOF elsif Old_Char = ASCII.LF then Cur_Position = Old_Pos and then Result = Character'Pos (ASCII.LF) else Cur_Position = Old_Pos + 1 and then Result = Character'Pos (Old_Char))); Syntax Here is what the BNF would look like for the proposed syntax: declare_expression ::= "declare" declare_expression_bindings "=>" expression declare_expression_bindings ::= constant_view_declaration {"," constant_view_declaration} constant_view_declaration ::= defining_identifier [":" (subtype_indication | access_definition | array_type_definition)] "is" expression The "constant" qualifier is not present because the bindings are implicitly constant. The expression is mandatory, but the type annotation is optional. We'll call the expression that has access to the bound constants the "inner expression" in the following paragraphs. Name Resolution Rules The type of a declare_expression is the type of the inner expression. The expected type for the inner expression is the expected type for the declare expression. As in declarative parts, each constant_view_declaration is visible to the subsequent ones, and a constant_view_declaration immediately hides outer declarations of the same name. The expected type for the expression of a constant_view_declaration is the type after the ":", if present, and any type otherwise. Legality Rules Every constant_view_declaration is implicitly constant. Meaning that: It’s illegal to pass it to a function as an out or in-out parameter It’s illegal to take a non-constant access on a constant view. Dynamic semantics For the evaluation of a declare_expression, the constant_view_declarations are elaborated in the order given. Then the expression of the declare_expression is evaluated, converted to the type of the declare_expression, and the resulting value is the value of the declare_expression. !discussion Legality Rules There was an overwhelming agreement that we should restrict what is possible to declare in declare expressions. This can be reconsidered if compelling arguments arise for supporting a specific construct. Syntax It is not yet clear what the delimiter between the object declarations and the inner expression should be. Using "in" is ambiguous: (declare X : Boolean := True in False .. True in X and X) If we use a ";" terminator, we can use in again: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (declare Old_Pos : Int is Cur_Position'Old; Old_Char : Character is The_File (Old_Pos); in ...) We can use "begin": procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (declare Old_Pos : Int is Cur_Position'Old, Old_Char : Character is The_File (Old_Pos) begin ...) Some commented that it looks too much like a regular procedural declare block. We can use parentheses: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (declare (Old_Pos : Int is Cur_Position'Old, Old_Char : Character is The_File (Old_Pos)) in ...) Some commented that it makes the object declaration looks like an aggregate. procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (declare Old_Pos : Int is Cur_Position'Old, Old_Char : Character is The_File (Old_Pos) => ...) Another possibility is to reverse the order and use "with": procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (if Old_Char = EOF_Ch then Cur_Position = Old_Pos and then Result = EOF elsif Old_Char = ASCII.LF then Cur_Position = Old_Pos and then Result = Character'Pos (ASCII.LF) else Cur_Position = Old_Pos + 1 and then Result = Character'Pos (Old_Char) with Old_Pos : Int is Cur_Position'Old, Old_Char : Character is The_File (Old_Pos)); This is similar to Haskell’s "where" clause. The syntax is nice and clean, but having the bindings at the end can be confusing when dealing with long expressions. Semantics There is a debate over the semantic of bound variables. Tucker has argued that renaming semantics are generally better in this context and that it should use a different syntax so as not confused with regular object declarations: (declare X is Something_Complicated(F), Y is X.A + B => X.C + Y) The rationale is that it would be worth the cognitive overload of making the user learn a new syntax ("is") and associated semantics, because it would result in a simpler model and a simpler and more useful feature: It works with limited and non limited, entities are constant by default. On my side, I originally thought that this choice is better left to the user, and that familiarity and consistency are more important, so we should have regular object_declarations. The issues about limited types can be side-stepped by having renaming_object_declaration included. The problems and solutions will hence be the same both for declare blocks and for declare expressions. Also I feel that, from the user point of view, 98% of the time, assignment will be enough. Steve proposes that we define the semantics of the feature by using an equivalence rule based on parameter passing rather than object declarations. A let-expression could be defined to be equivalent to introducing an anonymous expression function and then calling it. However, it's a little bit messy because we would need this anonymous function to have the right visibility (e.g. if the let expression occurs in a postcondition, then the anonymous expression function needs to be able to see the parameters etc. so that hoisting it out to the enclosing declaration list wouldn't work). The example in the AI Post => (declare Old_Pos : Int := Cur_Position'Old, Old_Char : Character := The_File (Old_Pos) => (if Old_Char = EOF_Ch then Cur_Position = Old_Pos and then Result = EOF elsif Old_Char = ASCII.LF then Cur_Position = Old_Pos and then Result = Character'Pos (ASCII.LF) else Cur_Position = Old_Pos + 1 and then Result = Character'Pos (Old_Char))); would then be equivalent to implicitly declaring (somewhere) function _Anonymous_Function_ (Old_Pos : Int; Old_Char : Character) return Boolean is (if Old_Char = EOF_Ch then Cur_Position = Old_Pos and then Result = EOF elsif Old_Char = ASCII.LF then Cur_Position = Old_Pos and then Result = Character'Pos (ASCII.LF) else Cur_Position = Old_Pos + 1 and then Result = Character'Pos (Old_Char))); and then calling it with actual parameter values corresponding to the values given in the let-expression Post => _Anonymous_Function _ (Old_Pos => Cur_Position'Old, Old_Char => The_File (Old_Pos) I think this method is too complicated and unnecessary. Also, it requires the user to think in terms of parameter-passing, which is priority inversion in terms of feature design in my opinion. Implicit constness In the current proposal, bindings are implicitly constant. This is good because we don’t mix expressions and statements, but also prevent some potentially useful use cases: function Next_Element (Result : out Element) returns Boolean; declare R : Element; Has_Element : constant Boolean := Next_Element (R); in (if Has_Element then R else No_Element) The pattern of using both an out parameter and a return value has wide-spread use in some libraries (GtkAda notably) and it is not clear whether we should support that. Generalizing constant_view_declaration Question is whether we should generalize constant_view_declaration to regular declare blocks. It could be useful because there is no way of making a renaming declaration constant at the moment. Also it would keep things coherent with other areas of the code. *************************************************************** From: Tullio Vardanega Sent: Wednesday, June 21, 2017 11:19 AM I like this notion and second the intent, including the "declare" element as per Raphael's phantom AI. *************************************************************** From: Gary Dismukes Sent: Wednesday, June 21, 2017 3:22 PM > I think "constant" is my favorite, since these are all implicitly constants > that must have initial values, while with "declare" one might expect that a > type and "constant" be explicit, whereas this is really a different sort of > declaration. Also, "for" is relying on the use of ":=" to distinguish it from > other sorts of iterators. Another possibility would be "with". (I note that Raphael used that in one of the examples in his "declare expression" AI, but at the end of the construct.) That scans well to me, and has an advantage of being short, though I suppose that some might feel that we shouldn't overload "with" further. > I have used ";" to allow multiple constant declarations, rather than using a > chain of "=>" which would probably mean multiple levels of parentheses as > well. You could go with "comma" instead, but that seems too easy to miss in > this context. I agree with using ';' for multiple decls. *************************************************************** From: Erhard Ploedereder Sent: Wednesday, June 21, 2017 8:12 PM I have semantic questions first, before I have an opinion on syntax: Is the scope of these ghost variables only the respective PRE or POST expression, or does the scope extend across PRE and POST? (The write-ups in most places imply the latter, in others the former. Raphael's analogies certainly go for the former; Tuck's write-up starts out speaking of both PRE and POST.) Intuitively, I am inclined to argue for the latter, since it would be a real nuisance to replicate the declarations in both PRE and POST, if needed in both places. Then the "let" or "where" syntax inside of PRE and POST clearly is not a good choice. Since both proposals work with type inference on ghosts, presumably calls on overloaded functions where the result type could disambuiate an otherwise ambigous call are illegal. Or does one get the chance to slip a type in? *************************************************************** From: Tucker Taft Sent: Thursday, June 22, 2017 1:20 AM These names are local to the parenthesized construct, similar to the scope of a loop variable inside a quantified expression. I frankly can’t imagine how we could bound the scope if we allowed it to extend past the matching right parenthesis. Sorry if I implied otherwise by one of my examples. The intent is that type annotations are optional, but are certainly allowed. Hence: Post => Fun'Result = (constant X : T1 := F(A, B); Y : T2 := G(C, D) => (if X > Y then X else Y+X)) By the way, I agree with Gary that replacing "constant" with "with" would also read well. *************************************************************** From: Tucker Taft Sent: Thursday, June 22, 2017 1:26 AM > These names are local to the parenthesized construct, similar to the scope > of a loop variable inside a quantified expression. I frankly can’t imagine > how we could bound the scope if we allowed it to extend past the matching > right parenthesis. Sorry if I implied otherwise by one of my examples. Sharing the declarations between Pre and Post would also be pretty difficult to define, in my view, since Pre and Post are evaluated at different times. *************************************************************** From: Erhard Ploedereder Sent: Thursday, June 22, 2017 7:19 AM I'll throw one more into the ring (it is similar to one of Raphael's but with different keywords): Post => Fun'Result = (with X = F(A, B), Y = T1'(G(C, D)) => (if X > Y then X else Y+X)) Syntax: 'with' ghost-decl_list ghost-decl_list -> identifier '=' expression {',' ghost_decl_list } Alternatively, see below: identifier '=' simple_expression {'and' ghost_decl_list } ---- I did not like the similarity, yet difference among constant X : T1 := F(A, B); vs. X : constant T1 := F(A, B); a ghost decl vs. an object declaration, where the "constant" goes elsewhere. ---- Use "=" in lieu of ":=", because PRE and POST are all about assertions, and here I assert that "X = something". Raphael uses "IS". I see no reason why not to use "=". Moreover, if eventually "constant" is not used then ":=" looks like assigments to ghosts are acceptable. ---- Use a qualified expression to disambiguate/document in lieu of not-quite declaration syntax. ---- On creating multiple ghosts, I am rather neutral on what is between them, but have a few comments: - ";" : with a semicolon I have a slight preference to repeat "with" i.e. "; with" - "," : o.k. to me - "and" : would be my preference in staying with the notion that this an assertion, even if this raises the spectre of the (via the use of simple_expression unambigous) with A = B and C = D+5 with A = (B and E) and C = D+5 *************************************************************** From: Tucker Taft Sent: Thursday, June 22, 2017 7:38 AM I agree there are some nice things about using "=", but it isn't consistent with Ada's current semantics. "=" is always symmetric in Ada, but now you are proposing that the left hand side is an implicit declaration, which is quite a shift in my mind. Even named numbers use ":=" in Ada, so it is quite consistent to use ":=" for introducing a constant. *************************************************************** From: Randy Brukardt Sent: Friday, June 23, 2017 2:25 PM Here's my 50 cents worth on this topic: Tucker wrote: > Apparently not everyone has seen this AI from Raphael, or (like me) > has lost track of it. I blame operator error. No one saw it, because he never sent it to the list. I went back and checked the logs for 8-29, 8-30, and 8-31, and no mail was received from Raphael on any of those days. But other people's mail was received from the Adacore server that his mail typically comes from on two of those days. And he's managed to send 48 other messages to the ARG list. Erhard said: > I did not like the similarity, yet difference among > constant X : T1 := F(A, B); vs. X : constant T1 := F(A, B); > a ghost decl vs. an object declaration, where the "constant" goes > elsewhere. This is a good point, but if you take that to the limit, you'd have to have (declare X : constant T1 := F(A, B) begin ... ) and even then the lack of an end is a difference (we decided when dealing with if expressions that we didn't want "end" in expressions, so I'm assuming that still holds). Those looking for a shorthand are going to want to be able to write less than this. And the syntax is never going to exactly the same. So I lean toward the "constant" version; the keyword "constant" MUST appear somewhere in or in front of these declarations, lest they be misinterpreted as variables. (I've always thought they WERE variables until Tucker made his proposal.) Making that the primary keyword seems like a useful compromise. Tucker said: > Sharing the declarations between Pre and Post would also be pretty difficult > to define, in my view, since Pre and Post are evaluated at different times. And in particular, the parameters may have different values when they are evaluated. Any long-term declaration of these things seem like madness. Tucker wrote: >The intent is that type annotations are optional, but are certainly allowed. I don't think that they can be optional, either philosophically or practically. Philosophically, Ada does not use type inference to declare anything. In the majority of cases where Ada allows the type name to be omitted, the type is uniquely determined by some other construct (the cursor type for an iterator, the element type for an iterator, for the lambda proposal it's determined by the access-to-subprogram type's parameter). The only exception that I can think of is the index type for a for loop; that gets determined by the range, but that itself isn't required to be specified. And that case has annoying hacks (fall back to Integer if all else fails), it certainly isn't a model that we'd want to emulate. Practically, there are a lot of likely expressions that wouldn't be allowed without a type name. Consider: (constant X := (if Some_Param > MAX then MAX else 10 => (if Some_Param > X then ...) The user probably would expect this to have type universal_integer, but they'd be wrong; this isn't a static expression, so the type preference to root_integer would apply (and has to apply, we've just discussed this in AI12-0227-1). Since root_integer isn't a nameable type, Some_Param has a different type and the expression (specifically the comparison of Some_Param to X) is illegal. This is not going to make us friends among Ada users. Similarly, the Bob Duff example for if expressions is outright ambiguous: (constant Item_Label := (if Items = 1 then "Item" else "Items) => ( ... raise Constraint_Error with "Failed with " & Items'Image & Item_Label)) This is ambiguous because a string literal can have any of the predefined string types. Moreover, if these are "annotations" rather than "specifications", then they shouldn't change the resolution. That means that neither of the above should work right even if the intended type name is given. People would string us up in trees if that was the case (and rightly so). (constant X : Integer := (if Some_Param > MAX then MAX else 10 => (if Some_Param > X then ...) (constant Item_Label : String := (if Items = 1 then "Item" else "Items) => ( ... raise Constraint_Error with "Failed with " & Items'Image & Item_Label)) It would be madness if either of these don't work as everyone expects. The only way that I can see allowing the type name being omitted is if the initializing expression is a type conversion, qualified expression, or a stand-alone object (those not allowing any overloading, so they unambiguously determine a type), or an indexed or selected expression with one of these as the prefix. That seems too wacky to me, but perhaps the value of allowing it for an extreme shorthand outweighs the weirdness. In any case, I think the type name has to be required in general, and if we allowed it at all, we could only allow omitting it for certain expressions. *************************************************************** From: Tucker Taft Sent: Friday, June 23, 2017 3:30 PM > ... > In any case, I think the type name has to be required in general, and > if we allowed it at all, we could only allow omitting it for certain > expressions. I think it is quite important to be allowed to omit the type name (this is a short hand after all). Iterators allow the type name to be omitted, and I see this as a close relative of iterators. But I agree with some of your concerns. Perhaps it can be omitted only if the initializing expression is syntactically a primary, which includes function calls, qualified expressions, conditional expressions, and many other useful things. *************************************************************** From: Randy Brukardt Sent: Friday, June 23, 2017 6:56 PM Both of the problematic cases I showed in the previous message are primaries (if expressions, specifically), so that wouldn't really help anything. I could see adding some explicit rules for conditional expressions. Perhaps we'd want a term so the rules would be easy to craft: A *typed declarative expression* is either: * a name statically denoting a stand-alone object; * a qualified expression; * a type conversion; * a parenthesized expression whose operand is a typed declarative expression; * a conditional expression where at least one operand is a typed declarative expression; * a predefined operator other than & where at least one operand is a typed declarative expression. All of these have a uniquely determined type, and it won't change because of future maintenance elsewhere (only direct changes, never a problem, or changing the type of the stand-alone object, hardly something that people would be surprised about). I don't think we can allow function calls, because adding/modifying a visible operation somewhere else could change the type of such a declaration (or make it illegal via ambiguity or this rule). Anyway, food for thought. *************************************************************** From: Tucker Taft Sent: Wednesday, January 31, 2018 9:00 AM [The following was in version /02 of this AI. - Editor.] > I am assuming another AI will define the semantics of: > > X: constant := expression; -- not as a named number! > Y renames expression; > > i.e. an object_declaration without a subtype_indication (must include > "constant"), and an object_renaming_declaration without a subtype_mark > (expression must denote a constant view). > > That AI should contain the business about *typed declarative expression*, > which allows one to distinguish number_declarations from > object_declarations. ... I missed or have forgotten the discussion of "typed declarative expressions." Who has that AI? Perhaps such an expression should be a different syntactic construct, or at least have some kind of italicized prefix so it stands out in the syntax for constant_declare_item. *************************************************************** From: Bob Duff Sent: Wednesday, January 31, 2018 2:16 PM > I missed or have forgotten the discussion of "typed declarative > expressions." Who has that AI? I had forgotten also. I looked it up in the minutes when I was writing the latest version of this AI. > ...Perhaps such an expression should be a different syntactic > construct, or at least have some kind of italicized prefix so it > stands out in the syntax for constant_declare_item. Sounds reasonable. > > I am assuming another AI will define the semantics of: > > > > X: constant := expression; -- not as a named number! > > Y renames expression; > > > > i.e. an object_declaration without a subtype_indication (must > > include "constant"), and an object_renaming_declaration without a > > subtype_mark (expression must denote a constant view). > > > > That AI should contain the business about *typed declarative > > expression*, which allows one to distinguish number_declarations from > > object_declarations. ... I believe Raphaël volunteered for the above-mentioned AI. I don't know if he's written it up, or if it has a number. I was thinking an object_renaming_declaration should have the same semantics (or as close as possible) whether it's inside a declare_expression, or not. So if we allow the compiler to deduce the type from the renamed object, it should work the same in both cases. I removed a bunch of verbiage about it from THIS AI, which Raphaël might want to copy (from the old version). Similar comments apply to the syntax "X: constant := expression;". That's a bit tricky, because what is now a named number should keep the same semantics. But I was hoping to allow "X: constant := Y;" where Y is of (say) a private type, and X is deduced to be of the same type. Both in a declare_expression, and as a normal declaration. I'm not sure we discussed the "constant" part, but I think it would be really weird to do other than I described above. *************************************************************** From: Randy Brukardt Sent: Wednesday, January 31, 2018 5:55 PM > I missed or have forgotten the discussion of "typed declarative > expressions." Who has that AI? My understanding of the discussion in Lexington is different than Bob's, so I think it belongs in *this* AI. See below. > Perhaps such an > expression should be a different syntactic construct, or at least have > some kind of italicized prefix so it stands out in the syntax for > constant_declare_item. A "typed declarative expression" is an expression that allows the omission of the type name in a constant declaration. I think requiring a special form would rather defeat the purpose (allowing shorter specification of constants in declare exprs). > > Here is a new version of AI12-0236-1 declare expressions. > > This completes my homework. > > ... > > I am assuming another AI will define the semantics of: > > > > X: constant := expression; -- not as a named number! > > Y renames expression; > > > > i.e. an object_declaration without a subtype_indication (must > > include "constant"), and an object_renaming_declaration without a > > subtype_mark (expression must denote a constant view). My recollection of the Lexington discussions was that we wanted this generally *only* for renames expressions, and for those, omitting the type would always be allowed, assuming of course that the "name" resolves without context. In particular, we did not want to allow the omission of the type name for constant declarations, because of confusion with named numbers and because of the violation of a principle that anything declared has a nominal subtype. I note that the homework item for Raphael states that is about dropping type names for renames declarations; it isn't about doing that more generally. > > That AI should contain the business about *typed declarative > > expression*, which allows one to distinguish number_declarations > > from object_declarations. ... We didn't want to do that; the "typed declarative expression" was simply about whether the subtype name could be omitted. I thought we had decided to drop that business in favor of pure resolution (that is, if the expression can be resolved without any context, you can drop the subtype name) in the case of "constants" in declarative expressions. Perhaps I didn't get this recorded well enough in the minutes; my excuse would be it was the last AI we discussed in Lexington and I probably was more interested in getting the meeting over with than perfect note-taking. *************************************************************** From: Bob Duff Sent: Wednesday, January 31, 2018 7:46 PM > My understanding of the discussion in Lexington is different than > Bob's, so I think it belongs in *this* AI. See below. Probably your (Randy) understanding of the discussion is more accurate than mine. But it still seems to me that when it comes to inferring the type of an object, we should make renamings and constants work the same, and we should make these things work the same inside declare_expressions and as regular declarations. For ex., it would seem really weird to me if "X: constant := Y;" is a named number in one context but not in the other. Likewise, it would seem weird if (assuming only one visible function F), "X: constant := F(Y);" can't resolve whereas "X renames F(Y);" can (as a regular decl not in a declare_expr). > Perhaps I didn't get this recorded well enough in the minutes; my > excuse would be it was the last AI we discussed in Lexington and I > probably was more interested in getting the meeting over with than > perfect note-taking. Perhaps, but I bow down to your superior note-taking abilities. *************************************************************** From: Randy Brukardt Sent: Wednesday, January 31, 2018 8:27 PM ... > For ex., it would seem really weird to me if "X: constant := Y;" is a > named number in one context but not in the other. I'd prefer that static expressions were properly declared always (i.e. keyword "static" in place of "constant"), and thus that there is no such thing as a named number, but it's 40 years too late to get rid that. > Likewise, it would seem weird if (assuming only one visible function F), > "X: constant := F(Y);" can't resolve whereas "X renames F(Y);" can (as > a regular decl not in a declare_expr). I understand your point, but... The subtype name in an object renames is (arguably) actively harmful, because the subtype parts (constraint, exclusions, predicates) are completely ignored. Therefore, I can support dropping it; omitting it can't be more confusing than having it and still having to ignore most of its properties. It's likely to be clearer to force one to look at the original object to find the properties. The reverse is true for constant declarations: the nominal subtype given in the declaration is important -- it even determines the semantics of the constant declaration. Omitting it is just going to confuse readers; it's hard to figure out the nominal subtype of an expression in general. If I was running the circus (to steal one of your favorite sayings, and ignoring that in some sense I *am* running this circus :-), I'd follow your principle and not allow dropping the subtype name anywhere from a constant declaration. (I'd also consider making a keyword "static" that could be used in number declarations -- optionally, for compatibility, sigh -- and allow a number declaration to have an expression of any type that can be static. That would be especially useful if we allowed user-defined static types and operations, but it appears that we're not doing that. End rather unrelated tangent.) However, everyone that really wants declare expressions also wants to be able to write them without as much text as possible, so I'm willing to allow dropping the subtype name there -- a constant in a declare expression is much more like a renames than it is an object declaration (especially if it is of a composite type) -- I hope we're not requiring copying here as we do for "real" constant declarations. Anyway, I'm trying to understand others concerns here and be flexible so long as that doesn't require making a hash out of basic Ada principles. *************************************************************** From: Bob Duff Sent: Thursday, February 1, 2018 9:14 AM > The subtype name in an object renames is (arguably) actively harmful, > because the subtype parts (constraint, exclusions, predicates) are > completely ignored. Therefore, I can support dropping it; omitting it > can't be more confusing than having it and still having to ignore most > of its properties. It's likely to be clearer to force one to look at > the original object to find the properties. I agreed about renamings. > The reverse is true for constant declarations: the nominal subtype > given in the declaration is important -- it even determines the > semantics of the constant declaration. I don't see what the big deal is. We just need to define what the nominal subtype is, in the case of this new syntax (because obviously it's not explicit in the constant decl). I can think of several rules. Perhaps: the nominal subtype is the subtype of the init expr, except if that has discrims, in which case it's the unconstrained subtype (and similar for arrays). > If I was running the circus (to steal one of your favorite sayings, > and ignoring that in some sense I *am* running this circus :-),... I didn't make it up. I got it from Steve, who got it from "If I Ran the Circus", by Dr. Seuss. You can pick up a copy here for $200.00: https://www.biblio.com/book/i-ran-circus-seuss-dr-theodore/d/996641864?aid=frg&utm_source=google&utm_medium=product&utm_campaign=feed-details&gclid=EAIaIQobChMI9p3s2f6E2QIV2LXACh1LJw09EAQYASABEgLhzfD_BwE >...I hope we're not requiring copying here as we do for "real" >constant declarations. On the contrary, I assumed it would copy (unless of course the compiler can prove it doesn't matter). It would be confusing to have different semantics in declare_expressions than for "real" constant decls. Isn't the whole point of allowing both constants and renamings is to let you choose "constant" semantics or "renaming" semantics? *************************************************************** From: Bob Duff Sent: Thursday, February 1, 2018 9:23 AM > I don't see what the big deal is. We just need to define what the > nominal subtype is, in the case of this new syntax (because obviously > it's not explicit in the constant decl). If we allow missing subtype indication ONLY in declare_exprs, we STILL need to define "nominal subtype", right? *************************************************************** From: Randy Brukardt Sent: Thursday, February 1, 2018 7:22 PM ... > > The reverse is true for constant declarations: the nominal subtype > > given in the declaration is important -- it even determines the > > semantics of the constant declaration. object------------------------^ > > I don't see what the big deal is. We just need to define what the > nominal subtype is, in the case of this new syntax (because obviously > it's not explicit in the constant decl). I didn't make my point very well (and the typo above doesn't help). The *reader* of the constant declaration needs to see the nominal subtype because it matters in various other places. I often find myself searching for a subtype name in order to write a "use type" clause. That requires finding the declaration (annoying enough, especially if it is in a different file) and then extracting the type name (use clauses could complicate that step, but I rarely use them on type names anyway). If the type name isn't there, either, it could get very frustrating. Leaving subtype names out - anywhere - makes it hard on the readers. I was willing to do that for very short-lived entities, because they're not likely to be used in a way where the subtype really matters. Things that potentially live for a very long time (the entire life of the program) must never omit the subtype name. I'm only willing to do so for renames because (A) the subtype name given is a lie anyway, properties come from the original object; and (B) renames is rarely used in the code I've seen. I'd prefer to see less optional stuff in Ada, not more. (Initializers [with <> meaning explicit no initialization], ending ids, parameter modes all should be required always). Obviously can't do that because of compatibility, but I definitely don't want to go in the wrong direction here. >>...I hope we're not requiring copying here as we do for "real" >>constant declarations. >On the contrary, I assumed it would copy (unless of course the compiler >can prove it doesn't matter). It would be confusing to have different >semantics in declare_expressions than for "real" constant decls. You're right about this, but... >Isn't the whole point of allowing both constants and renamings is to >let you choose "constant" semantics or "renaming" semantics? I thought it was because Steve thought he needed more never-used cases for his Bairdian execution examples. :-) There aren't a lot of good uses for renames (did you know that a rename actually makes the code worse in Janus/Ada? It prevents the name from getting evaluated in the a register, forcing extra memory writes). I wouldn't miss them if they didn't exist at all. OTOH, the idea of adding nested masters in the middle of expressions seems like a whole new level of unnecessary pain. Pain which becomes required if you allow/require copying of composite objects in declare expressions. (Can't have an object disappearing without being finalized!) For Janus/Ada, his means a thumb and a storage pool for each declare expression (plus the composite objects); the ability to "ignore" these scopes would be eliminated. I was imagining that these objects would get assigned to temporaries (usually registers) and would never be materialized at all. That's not possible for composite objects (if real). The original proposal (which gave rename semantics to these guys) had the appropriate properties. I'm now thinking that we should only allow renames in declare exprs; that would fix any pressure to drop the subtype names from constants. "Constant" and copying is OK for elementary types in declare exprs, but that's it. And that seems like a wart. *************************************************************** From: Bob Duff Sent: Sunday, September 16, 2018 3:24 PM Here is a new version of AI12-0236-1, "declare expressions". It no longer restricts the syntax of the obj/renaming decls -- you can use any object_declaration or renaming_declaration. So the semantics (static and dynamic) just piggy-backs on the existing semantics. As agreed, no further attempt to allow "X : constant := ..." (without subtype indication). Too bad (although I admit I got confused by Pos_Unchg below -- until I realized it's Boolean). I translated plain English into RM-ese. ;-) [Followed by version /03 of the AI - Editor.] *************************************************************** From: Tucker Taft Sent: Sunday, September 16, 2018 9:58 PM This restricts renaming to elementary types. I thought we had concluded that we wanted to allow arbitrary renaming, but limit declaring new constants to be of only an elementary type. *************************************************************** From: Jean-Pierre Rosen Sent: Monday, September 17, 2018 1:10 AM But if we allow renaming of the result of a function call with a build-in-place result type, we can as well allow constants, right? *************************************************************** From: Bob Duff Sent: Monday, September 17, 2018 2:13 PM > This restricts renaming to elementary types. Yes. I also meant to restrict the type of the whole declare_expression to be elementary. Some of the !discussion doesn't make sense without that restriction ("If we allow composite types, we would need to..."). > ...I thought we had concluded that we wanted to allow arbitrary > renaming, but limit declaring new constants to be of only an > elementary type. What do you mean by "we"? ;-) I know you suggested it, but I don't get it. To be discussed, along with the additional restriction I suggested above. The discussion between you and me so far (not on arg@) went something like this: Me: Let's disallow limited types, to avoid tasks and b-i-p. I'd like to restrict controlled types too, but I don't see how to do that without breaking privacy (and although I'm not ALWAYS opposed to breaking privacy, this would be a pretty big breakage). Note that my point here is purely about implementation difficulty. You: Let's restrict constants to elementary, and allow renamings of anything. We already support assertions. Me: I don't see the connection to assertions. You: The original rationale for declare_expressions is all about assertions. That's where it ended, and I decided to move the discussion to arg@. Your last statement is true, but it seems like a non sequitur. I mean, how can the original rationale for a feature have anything to do with implementation difficulty? You suggest that for "X : constant T := F(...);", T must be elementary, but for "X : T renames F(...);", T can be controlled. But the renaming is just as hard to implement as the constant (if both are controlled). Harder, in fact, given my memory of a certain compiler bug. I think some compiler writer should implement this stuff, and if they can manage to get controlled/tasks/b-i-p working, then allow those things. I admit that the suggested restrictions are kludges. :-( *************************************************************** From: Tucker Taft Sent: Monday, September 17, 2018 2:48 PM >> This restricts renaming to elementary types. I thought we had >> concluded that we wanted to allow arbitrary renaming, but limit >> declaring new constants to be of only an elementary type. >> > But if we allow renaming of the result of a function call with a > build-in-place result type, we can as well allow constants, right? I would argue that supporting arbitrary renaming is no harder than supporting arbitrary function calls in assertions, preconditions, etc., which is already required. On the other hand, supporting the creation of a new object could be more involved, and might include the whole build-in-place thing and/or invocation of "deep" Adjust, etc. which renaming does not require. My mental model of renaming the result of a function call is that you just defer cutting back the secondary stack, presuming it uses it. This is not based on specific knowledge of GNAT, so it might be way off, but I think from a language semantics point of view, renaming is fundamentally simpler than creating a new object, as soon as you wander past elementary types. *************************************************************** From: Bob Duff Sent: Monday, September 17, 2018 5:33 PM > I would argue that supporting arbitrary renaming is no harder than > supporting arbitrary function calls in assertions, preconditions, > etc., which is already required. I still don't get it. If you do: declare X : T1 := F(...); Y : T2 renames G(...); begin ... -- (1) end; Then code at (1) can refer to X and Y in their unfinalized state, and they both need to be finalized at the "end;". Both are hard to implement, and I'm not sure which is harder. With a declare_expression, we throw a result-of-the-declare into the mix, which probably doesn't simplify things. And I don't see the comparison with pragma Assert. Any function calls returning controlled stuff in the pragma get finalized right there. Those objects don't outlast the pragma. *************************************************************** From: Tucker Taft Sent: Monday, September 17, 2018 9:38 PM > Then code at (1) can refer to X and Y in their unfinalized state, and > they both need to be finalized at the "end;". You need to be a be able to refer to the "unfinalized" state of G(...) in the following: with Pre => (G(...).Comp > 5); I would think it would be no harder to implement: with Pre => (declare Y : T2 renames G(...); begin Y.Comp > 5); The "unfinalized" state of the result of G has roughly the same lifetime as in the expression that didn't use any renaming -- they are both limited to the evaluation of the Pre aspect. On the other hand, if you introduce a constant: with Pre => (declare X : constant T1 := F(...); begin X.Comp > 7); there might be more semantics involved, since if F has any controlled parts, you might need to call the appropriate Adjust procedures. I suppose one might be able to optimize away the copy in the function call case, but if we write: with Pre => (declare X : constant T1 := FF; begin Q(X) > 7); where FF is an existing declared object, we definitely have to make a copy of FF since who knows what happens inside Q. Renaming would not require any copying in such a case. > Both are hard to implement, and I'm not sure which is harder. > > With a declare_expression, we throw a result-of-the-declare into the > mix, which probably doesn't simplify things. > > And I don't see the comparison with pragma Assert. Any function calls > returning controlled stuff in the pragma get finalized right there. > Those objects don't outlast the pragma. None of these things outlast the evaluation of the declare expression, so lifetimes are never very long. The only issue in my mind is that creating a new object by copy of an existing object brings in more semantics, and so the limitation against composite objects seems justified. In the renaming case, I don't see any implementation requirement other than a need to postpone the finalization of the result, and that doesn't seem enough to justify the limitation to elementary types. There is no extra finalization required, and definitely no calls on Adjust, etc., relative to a precondition/postcondition, etc. that has a call on a function returning something complicated. *************************************************************** From: Edward Fish Sent: Tuesday, September 18, 2018 12:59 PM Question: The declare excludes declaration items, does this include USE statements? Because I have seen, and written, a few expression-functions where the ability to include such visibility within their scope w/o polluting the enclosing scope would have been nice. *************************************************************** From: Randy Brukardt Sent: Friday, September 21, 2018 8:33 PM I think that would be a bad idea, as it would mean that visibility could differ wildly in different parts of expressions. It's hard enough to figure out the use of use clauses when they are used globally in a package or subprogram, having part of an expression subject to a use clause and the rest not seems like madness. Object declarations are unlike most other declarations in that they aren't overloadable; this means that any use of the object identifier outside of an expanded name *must* refer to the declared object (and any other such use *must* not) -- this means that they can be handled without actually putting the names into the symbol table (or index, in the case of a Diana-like design). That's important since the symbols are generally treated as an unchanging global to expression operations like resolution -- having to deal with a changing symboltable would require a whole lot of additional mechanism (and care in order of operations). As an implementer, I don't want to go there and would rather forget the entire idea rather than add that level of complexity to an Ada compiler. *************************************************************** From: Randy Brukardt Sent: Friday, September 21, 2018 9:06 PM > > I still don't get it. If you do: > > > > declare > > X : T1 := F(...); > > Y : T2 renames G(...); > > begin > > ... -- (1) > > end; > > > > Then code at (1) can refer to X and Y in their unfinalized state, > > and they both need to be finalized at the "end;". I don't get it, either. > You need to be a be able to refer to the "unfinalized" state of G(...) > in the following: > > with Pre => (G(...).Comp > 5); Yes, but this is nothing new -- it exists in Ada 95 anywhere that "condition"s appear, for instance in an if statement or exit statement. And it was an issue for the return of large objects in Ada 83 -- it's always been handled. But the key here is that such finalization is always handled en-mass at the end of the expression. You don't have to do such finalization in the middle, which a declare expression would require. > I would think it would be no harder to implement: > > with Pre => (declare Y : T2 renames G(...); begin Y.Comp > 5); > > The "unfinalized" state of the result of G has roughly the same > lifetime as in the expression that didn't use any renaming -- they are > both limited to the evaluation of the Pre aspect. True, but that is irrelevant -- the declare is the outermost thing and thus its semantics are indistinguishable from doing everything at the end. What would be relevant is the following (assume type T needs finalization): if (declare Obj1 : T renames F(...) begin ...) + (declare Obj2 : T renames G(...)) then In this expression, Obj1 and Obj2 have to finalized when the respective declares end. That means that you could see: Init Obj1 Fin Obj1 Init Obj2 Fin Obj2 or the reverse (Obj2 before Obj1), but the natural: Init Obj1 Init Obj2 Fin Obj1 Fin Obj2 is definitely not allowed. (At least it shouldn't be allowed, it's pretty obvious in the above that the user wanted the objects to be disjoint.) This is very likely to be an ACATS test (there are existing tests very much like this for other constructs, I wouldn't want this one to be untested). > On the other hand, if you introduce a constant: > > with Pre => (declare X : constant T1 := F(...); begin X.Comp > 7); > > there might be more semantics involved, since if F has any controlled > parts, you might need to call the appropriate Adjust procedures. I > suppose one might be able to optimize away the copy in the function > call case, but if we write: > > with Pre => (declare X : constant T1 := FF; begin Q(X) > 7); > > where FF is an existing declared object, we definitely have to make a > copy of FF since who knows what happens inside Q. > Renaming would not require any copying in such a case. I don't see any reason this would matter. Renaming of a function result means making a temporary place to keep the function result, which either means a copy or using some build-in-place mechanism (which is logically an assignment -- that is, copy -- even if it isn't implemented that way). In any event, we shouldn't be designing the language around odd-ball implementation techniques like a "secondary stack"; we need to stick to very broad notions like "temporary" and "build-in-place" and "assignment". > > Both are hard to implement, and I'm not sure which is harder. > > > > With a declare_expression, we throw a result-of-the-declare into the > > mix, which probably doesn't simplify things. > > > > And I don't see the comparison with pragma Assert. Any function > > calls returning controlled stuff in the pragma get finalized right there. > > Those objects don't outlast the pragma. > > None of these things outlast the evaluation of the declare expression, > so lifetimes are never very long. That's not the issue: the issue is the reverse -- the lifetime has to be unnaturally short. There is no mechanism in Janus/Ada for dealing with any such issues with a lifetime of less than an entire expression: finalization is done "between" expressions (usually between statements). That's echoed by 7.6.1's definition of a master. > The only issue > in my mind is that creating a new object by copy of an existing object > brings in more semantics, and so the limitation against composite > objects seems justified. In the renaming case, I don't see any > implementation requirement other than a need to postpone the > finalization of the result, and that doesn't seem enough to justify > the limitation to elementary types. There is no extra finalization > required, and definitely no calls on Adjust, etc., relative to a > precondition/postcondition, etc. that has a call on a function > returning something complicated. Your mind is rather different than mine. There is no such thing as renaming a function result in implementation terms: there is nothing renamable in that case (can't rename something with a very short lifetime). So it has to be implemented as essentially a build-in-place object declaration. I don't see any problem with calling Initialize or Adjust in the constant case -- one just does that when needed, BFD. The difficulty in both of these cases is the need to do finalization at some unusual point (in the middle of an expression). We have to do the finalization "early" as the objects themselves go away early (can't finalize an object after it is reclaimed). I suppose we could define that a declare block always acts (dynamically) as if it is given at the outermost level of an expression, but I think that would be a bad thing for users (it would be a lie). And it would be preventing a possibly useful capability, given the example above: if (declare Obj1 : T renames F(...) begin ...) + (declare Obj2 : T renames G(...)) then if T is potentially very large, an expression like this could be written to avoid Storage_Error. I don't think we want to promise that. Moreover, as always, the restrictions Bob proposes would allow expansion in the future (perhaps when we know more about how this is used and thus should work). If we allow controlled types now and get it wrong (a near certainty given the issues noted) we'll be stuck forever. And I worry as always that we'd be making a useful and relatively cheap capability a lot more expensive and complex -- we had better be certain that is necessary before doing that (lest the whole idea get dumped into the crapper). *************************************************************** From: Steve Baird Sent: Friday, October 5, 2018 5:03 PM Bob and I have been discussing some issues with declare expressions. First a very minor problem, then a bigger one. 1) The minor problem: fully conformant expressions. Consider pragma Assertion_Policy (Check); procedure Foo (X : Boolean := (declare Flag1 : constant Boolean := ...; Flag2 : constant Boolean := ...; pragma Assert (Flag1 or not Flag2); begin Flag1 /= Flag2)); pragma Assertion_Policy (Ignore); procedure Foo (X : Boolean := (declare Flag1 : constant Boolean := ...; Flag2 : constant Boolean := ...; pragma Assert (Flag1 or not Flag2); begin Flag1 /= Flag2)) is begin ... end; This is legal, but is the pragma checked or ignored when the default parameter value is evaluated? Perhaps it depends on the assertion policy in effect at the call site? We may want a rule that if some construct has an effect on a region of program text (e.g., Pragma Assertion_Policy) and if text is duplicated as in the example above, then what matters is the first copy of the replicated text. Thus the assertion policy in force for the second copy of the duplicated text has no effect on any assertions within that copy. Alternatively, we could have a rule for a default parameter that what matters is the assertion policy that is in force where the parameter is defaulted. The main point is that this should question should be nailed down one way or another. Bob points out that this is nothing new; we already have a similar issue with pragma Suppress. Suppose we have procedure P (X : Integer := Some_Global_Variable + 1); and later we have a (conforming) completion for this declaration. And after that we have a call to this procedure which passes in the default parameter value. If overflow checking is suppressed at some but not all of these three points, what determines whether the check is suppressed? This question has nothing to do with declare expressions, but we'd like to handle similar cases consistently. 2) The bigger problem: Accessibility. Do we want to accept or reject this example? type Ref is access all Integer; type Rec is record F : aliased Integer := 0; end record; Ptr : Ref := Rec'(declare Local : Rec; begin Local).F'Access; I think the example should be (by definition) equivalent to type Ref is access all Integer; type Rec is record F : aliased Integer := 0; end record; function Foo return Rec is Local : Rec; begin return Local; end; Ptr : Ref := Rec'(Foo).F'Access; where Foo is an identifier chosen by the compiler which occurs nowhere else in the program. Therefore, I think we want to accept the example. The currently proposed wording for the AI includes > The accessibility level of a declare_expression is > the accessibility level of the *body*_expression. which means that the preceding example would be rejected. This is at least in part because I suggested to Bob that for this AI he should look at all the RM changes that were made for conditional expressions (e.g., in determining the applicable index constraint, the associated object, the expected type, and that sort of thing) and we have a rule in 3.10.2 that The accessibility level of a conditional_expression is the accessibility level of the evaluated dependent_expression. [Incidentally, AFAIK this rule for conditional expressions is fine.] A function is allowed to return a local variable (or, more precisely, the value of a local variable) because a function is defined to have a return object and that return object has special accessibility rules, different than for a local object of the function. So we want (IMO) declare expressions to have the same semantics as function calls and those semantics include return objects. Unfortunately, the accessibility/master/finalization rules for functions are quite complex (e.g., the static and dynamic checks which prevent "dangling types" in the case of a function with a class-wide result; or the "determined by the point of call" rules) and we don't want to either duplicate each of these rules for declare expressions or explicitly modify each of these rules to take declare expressions into account. This approach gets complicated. We've also got the 3.10.2(6) rule that defines the accessibility level of a master (recall that a declare expression, unlike a conditional expression, is a master). This rule also suggests that the example we are discussing is illegal. Again, the problem is that the special rules for function results do not apply in the case of a declare expression but we want them to. I think (and Bob tentatively agrees with me) that the best solution to all this might be to define a declare expression in terms of an equivalent function call. We define an equivalence rule and then let everything else (in particular, legality checks and dynamic semantics) follow from that. That spares us from having to specifically deal with, for example, stuff like 6.5(5.8/5) and 6.5(8/4) (these are static and dynamic "no dangling types" rules). We might (or might not) want to disallow the case where the type of a declare expression is anonymous, as in X1 : access T := (declare ...); or X2 : array (1 .. 10) of Element := (declare ...); if it turns out that some of these cases lead to problems. For example, in the anonymous array type case, what would the result type of our "equivalent" function be? There is a related question having to do with the applicable index constraint. Consider Y1 : String (1 .. N) := (declare ... begin (others => ...)); or even type Rec (N : Natural) is record Y2 : String (1 .. N) := (declare ... begin (others => ...)); end record; Perhaps we'd want to allow the Y1 example and disallow the Y2 example (because getting the result subtype for our "equivalent" function is too hard in that case). The Y1 example would then be equivalent to something like subtype Foo1 is String (1 .. N); function Bar1 return Foo1 is ... begin return (others => ...); end; Y1 : Foo1 := Bar1; You could imagine doing the same sort of thing with Y2 except that it isn't clear where the subtype and the function would be declared. If we really want this case to work, one could imagine something complicated and messy like function Bar2 (N : Natural) is ... subtype Foo2 is String (1 .. N); begin return Foo2'(others => ...); end; type Rec (N : Natural) is record Y2 : String (1 .. N) := Bar2 (N); end record; but let's not try to define that. In any case, my main point is that we should explore rewording this AI in terms of an equivalence rule instead of trying to duplicate/modify the function-related accessibility rules piecemeal. *************************************************************** From: Steve Baird Sent: Friday, October 5, 2018 5:12 PM > Therefore, I think we want to accept the example. Oops, I blew the example here. We don't want a named access type here, but rather Ptr : access constant Integer := Rec'(declare Local : Rec; begin Local).F'Access; and the claim is that this should be equivalent to Ptr : access constant Integer := Rec'(Foo).F'Access; Or something like that. But even if we just ignore this example, the main point remains that we don't want to duplicate for declare expressions all of the accessibility-related rules that we already have for functions. *************************************************************** From: Randy Brukardt Sent: Tuesday, October 9, 2018 9:02 PM > 1) The minor problem: fully conformant expressions. > > Consider > > pragma Assertion_Policy (Check); > > procedure Foo (X : Boolean := > (declare > Flag1 : constant Boolean := ...; > Flag2 : constant Boolean := ...; > pragma Assert (Flag1 or not Flag2); > begin > Flag1 /= Flag2)); ... The bug here seems to be allowing a pragma in the *middle* of an expression. That's not intended by the rules in 2.8 (they insist that a pragma only appear in places between statements or declarations), and seems to be happening here only by accident (the inclusion of a semicolon in this syntax). Pragma Assert itself might be harmless (although this example shows not completely), but I wonder about other pragmas. For instance, consider Assertion_Policy and Suppress. Those would require a much finer-grained management of the scopes than currently used in Ada compilers (the suppression and assertion states don't currently change within an expression). Sounds like a lot of work (I don't even know how to implement that in the current Janus/Ada, as expressions tend to be evaluated at very different times than they are defined syntactically). As such, and since we're allowing only object decls and renames here (not even use clauses, which would have a similar massive impact), the most sensible thing is to ban the use of a pragma in a declare expression. So, we should modify 2.8(6): * After a semicolon delimiter, but not within a formal_part{,}[ or] discriminant_part{, or declare_expression. [Aside: We ought to check other new syntax for the inclusion of semicolons that would also allow pragmas in the middle.] ... > 2) The bigger problem: Accessibility. > > Do we want to accept or reject this example? > > type Ref is access all Integer; > type Rec is record F : aliased Integer := 0; end record; > Ptr : Ref := Rec'(declare Local : Rec; begin Local).F'Access; Reject, of course. The natural idea is that a declare expression works rather like a declare block. Certainly, 'Access of a local object should not work. It seems obvious that a copy of a very local object stays very local. Indeed, I believe this example should be illegal (ignoring the 'Access) if Rec could be a by-reference type, because implementing it requires some sort of copy. Arguably it should always be illegal except for elementary types; in any case it should be very local. > I think the example should be (by definition) equivalent to > > type Ref is access all Integer; > type Rec is record F : aliased Integer := 0; end record; > function Foo return Rec is > Local : Rec; > begin > return Local; > end; > Ptr : Ref := Rec'(Foo).F'Access; > > where Foo is an identifier chosen by the compiler which occurs nowhere > else in the program. Sounds like an overcomplication that could kill the entire proposal. Indeed, it would be better (if this is a real concern, of which I'm skeptical) to only allow renames in declare expressions, as that would avoid these sorts of questions (we're only dealing with existing objects in that case). There's no function anywhere to be seen. > Therefore, I think we want to accept the example. I don't see any reason that anyone would want this example to work, and using a function model here is a massive complication. > The currently proposed wording for the AI includes > > The accessibility level of a declare_expression is > > the accessibility level of the *body*_expression. > which means that the preceding example would be rejected. Sounds good to me. But I think it has to go further and deny body expressions that are a part of a local object and are of a by-reference type. > This is at least in part because I suggested to Bob that for this AI > he should look at all the RM changes that were made for conditional > expressions (e.g., in determining the applicable index constraint, the > associated object, the expected type, and that sort of thing) and we > have a rule in > 3.10.2 that > The accessibility level of a conditional_expression is the > accessibility level of the evaluated dependent_expression. > [Incidentally, AFAIK this rule for conditional expressions is fine.] This rule allows returning *existing* objects from a conditional expression, which is fine. The same should be fine for declare_expressions. Allowing more seems unnecessary; it just makes massive work for implementers. > A function is allowed to return a local variable (or, more precisely, > the value of a local variable) because a function is defined to have a > return object and that return object has special accessibility rules, > different than for a local object of the function. > > So we want (IMO) declare expressions to have the same semantics as > function calls and those semantics include return objects. Why does this follow? A declare expression has nothing to do with a function; you're not returning anything. The motivating case for a declare expression is preconditions/postconditions where a somewhat complex expression is used in multiple conditions, something like: (declare X renames ; begin (if X > 10 then F'Result > 10 elsif X = 10 then F'Result = 10 else F'Result < 10)) There is no reason to return part of a local in this usage; I don't see any need to allow it at all. Probably in the interest of avoiding arbitrary restrictions we could allow it for either by-copy (elementary) types or for types that are known to not be by-reference (by-reference itself is a dynamic concept and can't be used for legality rules); in such a case, the object would be a copy (and very, very local - any 'Access would be illegal). ... <> ... > In any case, my main point is that we should explore rewording this AI > in terms of an equivalence rule instead of trying to duplicate/modify > the function-related accessibility rules piecemeal. I'd argue that we have the basic rules right (your specific example should not be allowed), and we might need an additional rule to avoid returning a composite object that would have to be a copy of a local. KISS (Keep It Simple, Stupid). :-) I'd need to see a compelling usage of a very local object that couldn't reasonably be done in some other way before I'd even consider this level of complication. *************************************************************** From: Tucker Taft Sent: Tuesday, October 9, 2018 9:24 PM I generally agree with Randy's comments about keeping it simple. I could go so far as to require the type of a declare expression to be an elementary type, since the main purpose is to use it in an assertion expression. If we limit the types of the locally declared objects to being elementary, this all becomes pretty simple. As I mentioned I have no problem with being more liberal in the renamings, but others seem to worry about those. Again, we could start simple, and then get more flexible in a later iteration. I'd be curious what the SPARK folks have to say here... *************************************************************** From: Randy Brukardt Sent: Wednesday, October 10, 2018 12:13 AM > I generally agree with Randy's comments about keeping it simple. I > could go so far as to require the type of a declare expression to be > an elementary type, since the main purpose is to use it in an > assertion expression. I don't see much reason to go *that* far; we allow composites in conditional expressions, which are pretty similar. It's best to avoid completely arbitrary restrictions. So I'd suggest a Legality Rule something like: If the name of an object declared in a declare_expression appears in a *body_*expression, then the type of the declare_expression shall be elementary. AARM Reason: This rule is intended to avoid complications caused by using an object after its scope has exited. We allow elementary types as the declare_expression can be a value rather than an object for such types. AARM Ramification: This is worded to cover nested declare_expressions as well as object declared locally. It does not include objects renamed in the declare_expression, as the renamed object necessarily exists outside of the declare_expression. *************************************************************** From: Jeff Cousins Sent: Wednesday, October 10, 2018 2:30 AM I'm glad you replied Randy, that's pretty much my thinking but expressed more coherently than I could have managed. *************************************************************** From: Jeff Cousins Sent: Wednesday, October 10, 2018 1:29 PM Speaking to my erstwhile colleagues (well, the few that have any depth of understanding of the language), they quite like conditional expressions as a convenient shorthand, but think that we’ve already gone too far in having ever more complicated forms of expressions, encouraging the writing of code that is not readily debuggable – one can’t (directly) insert a line of Text_IO in an expression, and how would a debugger step through a long expression? And personally, if one of the drivers for having more complicated forms of expressions is to use them in postconditions, then aren’t we going down a slippery slope towards the postcondition just repeating the logic that’s in the body, but in the form of a complicated expression rather than a series of statements, and thus blurring the distinction between specs and bodies? (This is more general to the new expression stuff than specifically declare expressions, but I think declare expressions would encourage the use of over-complicated expressions). *************************************************************** From: Tucker Taft Sent: Wednesday, October 10, 2018 1:58 PM If you are trying to specify functional properties, they inevitably get complicated. A typical approach is to "model" the true data structure with a simpler but less efficient structure, and then the postconditions can be expressed in terms of the model data structure. But it is inevitable that the postconditions will still be relatively complex. Without something like a declare expression, the postcondition is *longer* because you end up repeating the same sub-expression multiple times. So the point of a declare expression is to simplify the postcondtion expression. You still only get one expression, but you want it to be as concise as possible, without repeating the same subexpression multiple times. From the SPARK users, this is actually their highest priority Ada 2020 items, and it is all about making postconditions smaller and more readable. >(This is more general to the new expression stuff than specifically declare >expressions, but I think declare expressions would encourage the use of >over-complicated expressions). The goal was the opposite. It allows you to remove redundant subexpressions and replace them with a nice name. *************************************************************** From: Jeffery Cousins Sent: Wednesday, October 10, 2018 2:26 PM Declare expressions make it easier to write expressions that would otherwise have repeated subexpressions, but I'm sceptical that postconditions that are complicated enough to have repeated sub expressions are a good thing in the first place. I don't think that non-SPARK users were expecting postconditions to be much more than function Square has a non-negative result. Anyway, I was shocked to find a group of users who would rather we hadn't bothered with some of the new features. *************************************************************** From: Arnaud Charlet Sent: Wednesday, October 10, 2018 2:38 PM That's useful input although what would those users want to see in Ada 2020 instead? If the answer is "nothing, everything is fine as is" then this also tells us that these users are not the right target for Ada 2020. *************************************************************** From: Tucker Taft Sent: Wednesday, October 10, 2018 2:40 PM >Declare expressions make it easier to write expressions that would otherwise >have repeated subexpressions, but I'm sceptical that postconditions that are >complicated enough to have repeated sub expressions are a good thing in the >first place. I don't think that non-SPARK users were expecting postconditions >to be much more than function Square has a non-negative result. SPARK users are an important part of the "new" Ada community. From a marketing point of view, the security and safety guarantees that SPARK can provide resonate very strongly in new markets where Ada has not succeeded before. I would agree many existing Ada users will never try to fully specify the full functionality of a subprogram, but if you are trying to build a highly-secure application and formally verify that it implements its specification in some formal way, you will find that the postconditions can be large, and a mechanism to simplify them is important. >Anyway, I was shocked to find a group of users who would rather we hadn't >bothered with some of the new features. Many of the new features were admittedly added to allow the full formal specification of functionality, as used in SPARK. I believe Ada benefits a lot from being tied to SPARK, but for folks not doing formal specification, some of the new features will probably not be used, and may seem like a waste. *************************************************************** From: Ed Schonberg Sent: Wednesday, October 10, 2018 2:57 PM >Declare expressions make it easier to write expressions that would otherwise >have repeated subexpressions, but I'm sceptical that postconditions that are >complicated enough to have repeated sub expressions are a good thing in the >first place. I don't think that non-SPARK users were expecting postconditions >to be much more than function Square has a non-negative result. Anyway, I was >shocked to find a group of users who would rather we hadn't bothered with some >of the new features. Repeated subexpressions ask for functions, and previously this meant an enclosing body and a lack of proximity to the point of use, We now have expression functions that can appear in the same specification, so I don’t see a strong motivation here for declare expressions. *************************************************************** From: Randy Brukardt Sent: Wednesday, October 10, 2018 3:16 PM Which brings us full circle: the comment that started this thread claimed that a declare expression had to be equivalent to a function. If that's the case, one has to wonder why we need the declare expression in the first place. (Luckily for the proponents of this feature, I don't agree with the original comment.) I viewed this as a cheap addition that would help in some very limited circumstances. And clearly, some programmers will misuse it, but Ichbiah said this best when (arguing for user-defined operators) he said something to the effect that just because a feature can be misused doesn't mean that it isn't valuable when used properly. If of course the feature stops being cheap (as in Steve's view), then it's best not to do it at all. *************************************************************** From: Steve Baird Sent: Wednesday, October 10, 2018 9:29 PM > If of course the feature stops being cheap (as in Steve's view) I disagree with that characterization of my position. My main point is that that we don't want to either duplicate or modify the various specific rules that prevent accessibility problems for functions in order to avoid analogous problems with declare expressions. For example, I'm assuming that we don't want to allow this one: type T1 (D : access Integer) is ... ; X1 : T1 := (declare Int1 : aliased Integer; Y1 : T (D => Int'Access); begin Y); We could modify or duplicate the corresponding rule for return statements (6.5(5.9/5)) to handle this case, but I'm arguing against such a piecemeal approach. I think an equivalence rule would be a better way of dealing with this sort of problem. It also resolves most any corner-case question. If you want to know whether some case is legal or how it behaves at runtime, you apply the equivalence rule to transform your program into a program that doesn't have any declare expressions and then ask the corresponding question of the transformed version of your example. That equivalence rule might turn out to have its own problems (exactly where is this implicitly-declared function declared? how does it interact with freezing?). So perhaps an even better solution would be the sort of restrictions Bob and others have mentioned: the type of a declare expression shall be scalar, or shall have no access/task/protected/tagged/private/limited parts, or something along those lines. I'm open to that approach. A restriction disallowing a pragma within a declare expression also seems reasonable to me. So I don't think I am arguing for a more expensive definition for this feature. *************************************************************** From: Randy Brukardt Sent: Thursday, October 11, 2018 5:23 PM > > If of course the feature stops being cheap (as in Steve's view) > > I disagree with that characterization of my position. > > My main point is that that we don't want to either duplicate or modify > the various specific rules that prevent accessibility problems for > functions in order to avoid analogous problems with declare > expressions. This statement is of course fine, but I don't see any such problems inherent in the proposal. I've always imagined that a declare expression would be itself a master (as a controlled object had better be finalized before it ceases to exist), and in that case, 'Access is essentially illegal in all uses except those involving access parameters. I haven't read Bob's latest version carefully to see what he says about masters, but there needs to be some decision on that point (and not just because of accessibility, but also because of finalization). If we want to avoid a declare expression being a master, then we have to prevent declaring objects that have controlled, task, or protected parts (all of which require non-trivial finalization or task waiting). > For example, I'm assuming that we don't want to allow this one: > > type T1 (D : access Integer) is ... ; > > X1 : T1 := (declare > Int1 : aliased Integer; > Y1 : T (D => Int'Access); > begin > Y); I'm pretty sure we're not allowing this one, since Y isn't defined. :-) I presume you meant Y1 at the end here. And in that case, we shouldn't allow this (regardless of the discriminant). See my previous proposed rule. > We could modify or duplicate the corresponding rule for return > statements (6.5(5.9/5)) to handle this case, but I'm arguing against > such a piecemeal approach. I think an equivalence rule would be a > better way of dealing with this sort of problem. It also resolves most > any corner-case question. If you want to know whether some case is > legal or how it behaves at runtime, you apply the equivalence rule to > transform your program into a program that doesn't have any declare > expressions and then ask the corresponding question of the transformed > version of your example. > > That equivalence rule might turn out to have its own problems (exactly > where is this implicitly-declared function declared? > how does it interact with freezing?). > > So perhaps an even better solution would be the sort of restrictions > Bob and others have mentioned: the type of a declare expression shall > be scalar, or shall have no > access/task/protected/tagged/private/limited > parts, or something along those lines. > > I'm open to that approach. > > A restriction disallowing a pragma within a declare expression also > seems reasonable to me. > > So I don't think I am arguing for a more expensive definition for this > feature. You were, because you were arguing for a more complex solution rather than a simpler one (don't allow 'Access, or other restrictions). Most users should use renames here anyway, which is greatly helped by our new simpler syntax for that. So perhaps Tuck is right about allowing "only elementary non-aliased objects". Then there is no problem with returning local objects (they'd be values anyway), and you could still return other existing objects (including a function result). And it gets rid of the need to worry about finalization/task waiting. *************************************************************** From: Randy Brukardt Sent: Friday, October 12, 2018 7:22 PM I've finally gotten around to reading Bob's AI here. (I did post it unchanged except for spacing, a couple of editorial changes detailed below, and fixing the !standard references lest John get mad at me again. :-) Bob Duff wrote last month: ... > > This restricts renaming to elementary types. > > Yes. I also meant to restrict the type of the whole > declare_expression to be elementary. Some of the !discussion doesn't > make sense without that restriction ("If we allow composite types, we > would need to..."). Do you want/intend to make a correction to the AI to this effect? Or would you rather I did it? Or not at all? I realize that subsequent discussion might have changed your intent a bit, so I don't want to assume I know it. If it was me (today), I'd restrict new constants to elementary, not sure about renames (finalization only matters there for function results [as otherwise we're renaming an existing object which won't get finalized here], which are temporaries otherwise inaccessible - there's an argument that leaving them to be finalized with the entire expression is not harmful as only pathological programs could care), and I see no reason to restrict the result (if it works for an if expression, I don't see why it shouldn't work for a declare expression; the restriction to elementary types for local constants ensures that the result can be handled by value if it is local). Ergo, no correction seems needed (except to the !discussion). If we make a correction, we also should correct the pragma rule to disallow embedded pragmas in declare expressions, I'm sure we didn't intend to allow that. (See that mail.) In any case, you're (that is, Bob) the author and I don't want to replace your intent with mine (even if it's better ;-). --- >Add a new section 6.9, following 6.8 "Expression Functions": > >6.9 Declare Expressions This seems like a curious place to put a new kind of subexpression (being in the subprogram section). The closest analogs are conditional expressions and declare blocks, so I would have expected it in one of those places. Probably the most sense IMHO would be 4.5.9 (after quantified expressions, mainly to avoid changing the number on those), as it is just another kind of subexpression. The alternative of putting it after a declare block (that is, as 5.6.1) doesn't work as well since we already have a new kind of block statement (the parallel block) in that slot. --- >Modify the definition of "master" in 7.6.1(3/2): > > master: the execution of a body other than a package_body; the > execution of a statement{; the evaluation of a declare_expression}; or > the evaluation of an expression, function_call, or range that is not > part of an enclosing expression, function_call, range, or > simple_statement other than a simple_return_statement. I would not make this change if you are restricting the types of the local entities to elementary. In that case, it could only affect the result of function calls that occur in initializers or the body_expression in that and I suppose similar aggregates (everything else being an existing object). That would be different than the way parts (informal) of other expressions are handled, would require additional restrictions on the body_expressions compared to the otherwise identical dependent_expressions of an if_expression, and clearly would require some complex implementation gymnastics. If you don't change the definition of master, then the implementation need make no changes to finalization at all. (Besides, my understanding if the restriction to local elementary entities is to get finalization out of the picture; it doesn't help any to then stick it back in only in Bairdian corner-cases.) Specifically, assume F returns an object with a controlled part. The following if_expression is legal and has a well-defined meaning and implementation: (if B then F(1) else F(2)) It would be surprising if a similar declare_expression wasn't legal or meant something different: (declare Foo renames Natural'(...); begin F(Foo)) [If this is a master, the result of F(Foo) would be finalized before it was made the result, which doesn't make any sense.] It would be even more surprising if sticking the if expression into a declare expression would change its meaning/legality: (declare Foo renames Natural'(...); begin (if Foo > 10 then F(1) else F(2)) since this sort of thing seems to be the motivating case for declare_expressions. --- Trivial changes: You didn't put the /3 references into the paragraph numbers for the 3.10.2 changes. That always has to be done for inserted paragraphs as their numbers can change from version to version. (We don't want doubly inserted numbers, so they sometimes changes, as the following paragraphs will after these changes.) >Add after 3.10.2(9.1/3): > > The accessibility level of a declare_expression is > the accessibility level of the *body*_expression. The first forward reference in a subclause should always have a cross-reference "(see 6.9)". You could complain that the conditional expression rule preceding this one doesn't do that -- but that one's wrong, and two wrongs don't make a right. :-) I've added a correction for that wrong to the "clean-up" AI. --- >Add after the penultimate sentence of 3.9.2(3): > > A declare_expression is statically, dynamically, or indeterminately > tagged according to its *body*_expression. It's weird to have this here, and have the similar rule for conditional expressions in 4.5.7. However, given that this 3.9.2 rule reads like a complete listing of possibilities, it seems like it's the conditional expression rule that is misplaced. (It would have helped to show the whole rule in the AI.) Thus, I suggest at least adding a mention of conditional expressions into 3.9.2(3). Perhaps something like: Modify 3.9.2(3): A name or expression of a tagged type is either statically tagged, dynamically tagged, or tag indeterminate, according to whether, when used as a controlling operand, the tag that controls dispatching is determined statically by the operand's (specific) type, dynamically by its tag at run time, or from context. A qualified_expression or parenthesized expression is statically, dynamically, or indeterminately tagged according to its operand. {A declare_expression is statically, dynamically, or indeterminately tagged according to its *body*_expression. A conditional_expression is statically, dynamically, or indeterminately tagged according to rules given in 4.5.7.} For other kinds of names and expressions, this is determined as follows: Moving the 4.5.7 rule here is a bit unpleasant, as it is in multiple sentences and includes a Legality Rule. So it is simpler to use the cop-out suggested above. If we use the cop-out rule, we can add that in this AI, or do that in the clean-up AI. It's easier for me to do it here (no conflict has to be managed, 'cause otherwise we have two separate changes to the same paragraph), but I'll let you (Bob) decide. If you would prefer to fix this right (which means adding text to both 3.9.2(3) and 3.9.2(9/1), and deleting 4.5.7(17/3), then it has to be in the clean-up AI. But that seems to violate the Duff rule of RM changes: shuffling wording around like this is not going to change the behavior of anyone. So I'm not expecting that. --- That's it, I think. *************************************************************** From: Bob Duff Sent: Friday, October 10, 2018 7:56 PM > I've finally gotten around to reading Bob's AI here. OK, thanks. Just so folks know: I have not yet had a chance to read the last 40 or so emails to ARG on this subject, including the one I'm replying to. *************************************************************** From: Randy Brukardt Sent: Friday, October 10, 2018 8:55 PM I've been rereading most of them as I've been filing them in the AI, and approximately none of them (starting with Steve's message on Friday the 5th) have anything significant to do with your actual proposal (which doesn't allow declaring the objects that Steve was using in his example). So I'm biased, but I think you could save quite a bit of time and skip the whole lot (except my message on your actual AI and perhaps Steve's part 1 and my reply to that) and you won't be missing much. (Besides, you could skip the three different suggestions I had for fixing a problem that your AI does not have. :-) *************************************************************** From: Bob Duff Sent: Thursday, December 6, 2018 1:00 PM Here's a new version of AI12-0236-1, declare expressions. [This is version /04 of the AI - Editor.] This includes all the changes recorded in the latest minutes. This completes my homework. *************************************************************** From: Randy Brukardt Sent: Thursday, December 6, 2018 11:35 PM A handful of comments: ... > A declare_item that is an object_declaration shall declare a constant > of nonlimited type. Word missing here. :-) Added it "of {a} nonlimited type". ... > We could have allowed "others" array aggregates, by adding after > 4.3.3(15.1): > > For a declare_expression, the applicable index constraint for > the *body*_expression is that, if any, defined for the > declare_expression. I think we ought to do this (even though it isn't very important); it is the only significant difference between a conditional expression and a declare expression. And remember my transformation -- it applies here, too: Arr : constant Atype := (if B then (1 => <>, others => 0) else (2 => <>, others => 0)); could be changed to Arr : constant Atype := (declare Exp renames <>; begin (if B then (1 => Exp, others => 0) else (2 => Exp, others => 0)); I'll leave this for the meeting. *************************************************************** From: Brad Moore Sent: Monday, December 24, 2018 9:07 AM > A handful of comments: Another comment on this AI. I believe this example from the !examples section, Type_Invariant => (M : constant Integer := Integer'Max(T.A, T.B); begin (if M > 0 then M > T.C else M < T.D)) is missing the "declare" keyword. *************************************************************** From: Randy Brukardt Sent: Tuesday, January 15, 2019 6:46 PM Here are a number of issues that I've noticed with AI12-0236-1 as I prepare to add it to the draft Standard. I don't think any of these are controversial, but since these all but the last two go beyond editorial, I'm asking if anyone has an objections to any of these suggestions. Otherwise, I will go ahead and add declare_expressions to the Standard as outlined here. -------- (1) The syntax non-terminal declare_expression is not used anywhere in the AI. I would expect that it should be added to "primary", just like conditional_expression. It certainly has to be added *somewhere* in the expression grammar. Note that conditional_expression is parenthesized as a primary, we'd do the same here. See also item (2), below. (2) 4.5.7 (conditional expressions) and 4.5.8 (quantified expressions) have rules allowing extra parentheses to be dropped. This AI does not have that rule. This of course ties into the syntax of the use of declare_expression, which is missing from the AI. I intend to add that rule: Wherever the Syntax Rules allow an expression, a declare_expression may be used in place of the expression, so long as it is immediately surrounded by parentheses. An alternative to both (1) and (5) would be to follow the model of raise_expression instead. That is a relation, rather than a primary, and does not require parentheses to combine it with boolean operators. However, that would requiring changing to body_simple_expression to avoid ambiguity, and there might be additional issues (I didn't try to figure that out). It also would be unnecessarily different than the conditional_expression model. Thus, I stuck with the conditional_expression model. (3) Way back when, Steve had a insane example of putting a pragma into a declare_expression. I suggested that we not allow that, as pragmas in the middle of expressions seems like asking for trouble. (The definitions of many types of pragmas assume that they occur between entities.) Steve's example showed an example of an Assert pragma in a default expression that has different assertion policies applied. He of course wanted to know if those expressions conformed. Let's not answer questions like that. :-) The reason it appears to be allowed is the semicolon after the declarations in this syntax. I think that is an accident of the syntax, and the solution is to disallow it explicitly (just as we do in discriminant_parts). So we need to modify 2.8(6): * After a semicolon delimiter, but not within a formal_part{,}[ or] discriminant_part{, or declare_expression. I note that if we decide in the future to allow this sort of thing, we can drop the rule. But if we don't have the rule, then we have to answer every question about pragmas in expressions that someone can dream up. (Pragma Suppress in a declare_expression, anyone?). KISS suggests to disallow it now and see if anyone complains. (4) I raised this in October, but it ended up at the end of a long thread and it never was discussed: >Add a new section 6.9, following 6.8 "Expression Functions": This seems like a curious place to put a new kind of subexpression (being in the subprogram section). About the only relationship this expression has to subprograms is that it can be used in one. :-) The closest analogs are conditional expressions and declare blocks, so I would have expected it in one of those places. Probably the most sense IMHO would be 4.5.9 (after quantified expressions, mainly to avoid changing the number on those), as it is just another kind of subexpression. We'd have to move the reduce_expression to 4.5.10 to make room, but since that's new, it doesn't matter. (Anonymous_functions used to be there; *they* would have had a reason to be in chapter 6.) The alternative of putting it after a declare block (that is, as 5.6.1) doesn't work as well since we already have a new kind of block statement (the parallel block) in that slot. Another alternative would be to move quantified expressions to 4.5.9 and put declare_expression at 4.5.8. This would have the advantage of putting declare_expression next to the closest relative (conditional_expressions), and also letting quantified_expressions be next to reduction_expressions (as Brad has pointed out several times, a quantified_expression is a special case of a reduction). The problem with that is changing the number of an existing subclause, and the side effects from that (I'd have to rename a bunch of ACATS tests, for instance). So I think this is a bridge too far (just a bit too far). So I plan to put it at 4.5.9, pending objections. (5) Another thing I brought up in October: 3.9.2(3) looks like a complete list of possibilities, but it doesn't include conditional_expressions. There is a rule for that in 4.5.7, and it's too tangled to move (it's in multiple sentences and includes a Legality Rule), so I suggested adding a sentence to cross-reference to that to this subclause. That is: Modify 3.9.2(3): A name or expression of a tagged type is either statically tagged, dynamically tagged, or tag indeterminate, according to whether, when used as a controlling operand, the tag that controls dispatching is determined statically by the operand's (specific) type, dynamically by its tag at run time, or from context. A qualified_expression or parenthesized expression is statically, dynamically, or indeterminately tagged according to its operand. {A declare_expression is statically, dynamically, or indeterminately tagged according to its *body*_expression. A conditional_expression is statically, dynamically, or indeterminately tagged according to rules given in 4.5.7.} For other kinds of names and expressions, this is determined as follows: I could fix this separately, but that's just more work for me (and more stuff for you guys to read), so I'd rather do it in AI12-0236-1, "while the hood is up", as Steve sometimes has said. (6) I was asked to come up with an example for the new subclause. I decided to use a Post from the containers as the example. This sort-of violates our usual rules for examples (as it is a forward reference), but I argue that the Ada language-defined packages pre-exists when any reader starts reading the Standard. They don't need any previous declarations to compile the example (just copy some text from the Ada.Containers.Vectors). Here's the example: The postcondition for Ada.Containers.Vectors."&" (see A.18.2) could have been written: with Post => (declare Result renames Vectors."&"'Result; Length : constant Count_Type := Left.Length + Right.Length; begin Result.Length = Length and then not Tampering_With_Elements_Prohibited (Result) and then not Tampering_With_Cursors_Prohibited (Result) and then Result.Capacity >= Length) Note: This is *not* how this is written, as it adds four extra lines to the Post. But the result is a lot easier to read than the original. Note the expanded name for the prefix of Result is required, as an operator symbol alone is not a name and thus cannot be a prefix to an attribute. I considered using abbreviations "Res" and "Len" for the object names, but didn't because it seemed like lousy Ada practice to worry about saving three characters each. (7) The example in the !problem is: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (if The_File (Cur_Position'Old) = EOF_Ch then Cur_Position = Cur_Position'Old and then Result = EOF elsif The_File (Cur_Position'Old) = ASCII.LF then Cur_Position = Cur_Position'Old and then Result = Character'Pos (ASCII.LF) else Cur_Position = Cur_Position'Old + 1 and then Result = Character'Pos (The_File (Cur_Position'Old))); While I realize that this is not showing all of the declarations, I cannot figure out how a meaningful postcondition would not depend on Stream in some way. I'm guessing the The_File and Cur_Position are components (or functions) of type File_Descr, and the Stream prefix is missing here. Adding that makes this a better example anyway, as it makes it longer still: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (if Stream.The_File (Stream.Cur_Position'Old) = EOF_Ch then Stream.Cur_Position = Stream.Cur_Position'Old and then Result = EOF elsif Stream.The_File (Stream.Cur_Position'Old) = ASCII.LF then Stream.Cur_Position = Stream.Cur_Position'Old and then Result = Character'Pos (ASCII.LF) else Stream.Cur_Position = Stream.Cur_Position'Old + 1 and then Result = Character'Pos (Stream.The_File (Stream.Cur_Position'Old))); Then the version using a declare_expression looks like: procedure Fgetc (Stream : File_Descr; Result : out Int) with Post => (declare Old_Pos : constant Position := Stream.Cur_Position'Old; The_Char : constant Character := Stream.The_File (Old_Pos); Pos_Unchg : constant Boolean := Stream.Cur_Position = Old_Pos; begin (if The_Char = EOF_Ch then Pos_Unchg and then Result = EOF elsif The_Char = ASCII.LF then Pos_Unchg and then Result = Character'Pos (ASCII.LF) else Stream.Cur_Position = Old_Pos + 1 and then Result = Character'Pos (The_Char))); which shows the value a bit better. *************************************************************** From: Randy Brukardt Sent: Wednesday, January 16, 2019 12:00 AM Four more: (8) All of the other newish subclauses in 4.5.x have a short introduction. For instance, we have: Quantified expressions provide a way to write universally and existentially quantified predicates over containers and arrays. We probably ought to have such a sentence for declare expressions. Declare expressions provide a way to include declarations in an expression, allowing single expressions of incredible complexity. OK, maybe that's not the best description. I hoped to steal something from block statements, but their introduction basically repeats the syntax. Ugh. Stealing the AI summary: Declare expressions provide a way to declare local objects and object renamings in an expression context. I'll use this until someone has a better idea. (9) We have the following Legality Rule: A declare_item that is an object_declaration shall declare a constant of a nonlimited type. The reason for this, according to the !discussion: The restriction to nonlimited types is to avoid implementation difficulties related to build-in-place and task waiting. But in that case, there needs to be a matching rule for renames, something like: A declare_item that is an object_renaming_declaration shall rename an object of a nonlimited type. Since renaming a function is essentially the same as an object declaration. For instance: Fooey renames Func_returning_Obj_with_Task_Parts; The return object has the same lifetime and semantics as a constant object declaration initialized with the same function call. We could try to allow renaming of existing limited objects only: A declare_item that is an object_renaming_declaration shall not rename an object of a limited type that is a function_call, aggregate, a parenthesized expression of one of these, a conditional_expression that has at least one dependent_expression that is one of these, or a declare_expression whose body_expression is one of these. Did I forget anything? This isn't particularly pleasant. I'll use the simpler rule pending any discussion. (10) Since we *are* allowing the *body_*expression to be limited (it can only be a build-in-place object in that case), we need an update to 7.5(2.1/5): In the following contexts, an expression of a limited type is not permitted unless it is an aggregate, a function_call, a raise_expression, a parenthesized expression or qualified_expression whose operand is permitted by this rule, [or] a conditional_expression all of whose dependent_expressions are permitted by this rule{, or a dependent_expression whose body_expression is permitted by this rule}: Again, this is consistent with conditional_expressions. (And I've bumped the priority of the ACATS C-Tests for conditional expressions. :-). (11) We should write the syntax on two lines to show the recommended break. We did that for conditional_expression. If a declare expression fits on one line, it probably shouldn't have been used. Thus: declare_expression ::= declare {declare_item} begin *body_*expression We could even recommend the best form for longer expressions: declare_expression ::= declare {declare_item} begin *body_*expression But that seems like overkill for a default recommendation. (Of course, that's how the example is formatted, it is too large to fit on two lines.) Whew! Hope that's it. *************************************************************** From: Tucker Taft Sent: Wednesday, January 16, 2019 8:01 AM I agree with everything you propose except the restriction on renaming functions that return a limited type. Within an expression you can pass the result of a function that returns a limited object to another function, and in my view, renaming is not harder than parameter passing. What am I missing? It would be nice to have an example to see if there is a real difficulty here. If there really is a problem, then I would prefer the more complex rule, so you can rename any pre-existing object. *************************************************************** From: Bob Duff Sent: Wednesday, January 16, 2019 11:30 AM > [Hope the title is clear enough for Bob ;-)] Crystal. You got me to read it, anyway. ;-) Thanks for finding all these bugs. I'm OK with your plans, including the later email with "Four more:", except: > (3) ... I think people will want pragmas. But as you point out, that could be added later. So I won't argue the point now. In fact the "could be added later" argument applies to anything anybody might want to add, like limited stuff. So lets be conservative. > (8) > ... > Declare expressions provide a way to declare local objects and > object renamings in an expression context. I suggest changing the first "objects" to "constants". > (10) > ... > dependent_expressions are permitted by this rule{, or a > dependent_expression whose body_expression is permitted by this rule}: Is the "dependent_expression" supposed to be "declare_expression"? *************************************************************** From: Randy Brukardt Sent: Wednesday, January 16, 2019 12:32 PM > You got me to read it, anyway. ;-) Mission accomplished. :-) > Thanks for finding all these bugs. > > I'm OK with your plans, including the later email with "Four more:", > except: > > > (3) ... > > I think people will want pragmas. But as you point out, that could be > added later. So I won't argue the point now. > > In fact the "could be added later" argument applies to anything > anybody might want to add, like limited stuff. > So lets be conservative. My thought, too. I worry that a lot of pragmas aren't written to make sense in the middle of expressions, and that implementations aren't set up to have pragmas in the middle of expressions. Janus/Ada pretty much assumes that the compiler global state (including such things as the suppression state and the symboltable) are constant during expression resolution and evaluation. Like everything, that could be changed, but doing so would likely be a continual source of bugs. > > (8) > > ... > > Declare expressions provide a way to declare local objects and > > object renamings in an expression context. > > I suggest changing the first "objects" to "constants". OK, sounds good to me. I'll change the AI summary, too. > > (10) > > ... > > dependent_expressions are permitted by this rule{, or a > > dependent_expression whose body_expression is permitted by > this rule}: > > Is the "dependent_expression" supposed to be "declare_expression"? Yup. Getting too late, I guess. *************************************************************** From: Tucker Taft Sent: Wednesday, January 16, 2019 1:05 PM >> ... >> In fact the "could be added later" argument applies to anything >> anybody might want to add, like limited stuff. >> So lets be conservative. > > My thought, too. ... I would still like to understand better the restriction against renaming of function calls returning limited types, before agreeing to it. ... *************************************************************** From: Randy Brukardt Sent: Wednesday, January 16, 2019 1:03 PM > I agree with everything you propose except the restriction on renaming > functions that return a limited type. Within an expression you can > pass the result of a function that returns a limited object to another > function, and in my view, renaming is not harder than parameter > passing. What am I missing? It would be nice to have an example to > see if there is a real difficulty here. Sigh. You've said this before, Bob and I both have tried to debunk this thought, and it just doesn't seem to stick. I even gave the problematic case in my e-mail last night, didn't seem to help. OK, let's try to motivate this with a long-winded explanation of something that appears obvious. So, let's assume we have a type that contains one or more tasks. type Type_with_Tasks is limited private; And we have a function that returns this type: function Ugly (A : Integer) return Type_with_Tasks; This function requires build-in-place, so the implementation is likely to be that a block of memory is passed in, along with a representation of the master to use for the tasks. We can use this function to declare a constant: Fooey : constant Type_with_Tasks := Ugly (12); The code here will pass into Ugly a block of local memory and the master of this declarative part. We've postulated that this is too messy to allow in the declare_expression, so we're disallowing a declaration like this. Now consider a similar renames: Humbug renames Ugly (12); To create the return object being renames, the code will passing into Ugly a block of local memory and the master of this declarative part. That is, *exactly* the same code as the constant. Ergo, if we allow this one, we might as well allow the other as well. Another way to look at these declarations is to look at the accessibility of the constant (the enclosing entity) and of the renaming (the enclosing entity). Again, these are semantically the same in all visible properties. (Indeed, with the new shorter syntax, there is never a reason to write a constant rather than a renaming for a function call.) There is never really a difference between renaming a function and declaring a similar constant with the function. Even when the type is elementary, the function then returns a value, and in both cases we have to put that value into local memory. There might be a difference for a nonlimited, non-by-reference type in that the constant might make an extra copy, but even then it isn't possible to see that difference other than by inspecting the code. Now, I can see an argument that the semantics are defined well enough here, so we should allow the renames. But in that case, we should allow the constant, too, since they are semantically identical. There is literally no reason to ban one and allow the other. However, I think that a large part of the reason for this restriction is that it allowed a number of Bairdian cases where the declared object is part of the result of the declare_expression. That is, something like: (declare Bah : constant Type_with_Tasks := Ugly (12); Humbug renames Ugly (4); begin (if Tucker > Taft then Bah else Humbug)) Here you might have to build-in-place across multiple levels, and pass the master of the object across multiple levels as well. Sounds like fun. :-) > If there really is a problem, then I would prefer the more complex > rule, so you can rename any pre-existing object. That's reasonable. In that case, there's no build-in-place nonsense, nor any task waiting issues. You can't declare a constant in that case. And you can't use those as a result of a declare_expression (now that I put declare_expression into the 7.5 wording). *************************************************************** From: Tucker Taft Sent: Wednesday, January 16, 2019 2:11 PM > ... > > Now consider a similar renames: > > Humbug renames Ugly (12); > > To create the return object being renames, the code will passing into > Ugly a block of local memory and the master of this declarative part. > That is, > *exactly* the same code as the constant. Ergo, if we allow this one, > we might as well allow the other as well. I agree to stop beating this dead horse, but I would still like to understand how things are different if we pass the return value as a parameter to another function call. In that case, what is the master of the task, presuming there is one? I'll try to answer my own question... The Heart-of-darkness RM 3.10.2(8) says: "The accessibility level of a view of an object or subprogram defined by a renaming_declaration is the same as that of the renamed view." Then 3.10.2(10.1/3,10.6/3) say: "The accessibility level of the result of a function call is that of the master of the function call, which is determined by the point of call as follows: ... * In other cases, the master of the call is that of the innermost master that evaluates the function call." OK, so according to 7.6.1(3/2), whether we pass the result as a parameter to another call, or we rename the result in a declare_expression, the master is an enclosing expression (or the declare_expression itself), function_call, or range, that is not itself within an expression, function_call, or range. An object_renaming is not considered an object_declaration (by syntax), and according to 9.2(2/2), it is the tasks created by the elaboration of an object_declaration or generic associations of mode IN that are all activated together at the "begin" of the declarative part. The tasks associated with a renamed function call are therefore treated like objects that are *not* created by object_declarations, and according to 9.2(4/2), that means they are activated immediately after completing their initializations, or if being returned by a function, after having been returned from the function. So this implies that the tasks, if any, are activated immediately upon return of the function, whether we rename the result or pass it as a parameter to another subprogram. All in all I don't see much of a difference. Be that as it may, I can accept (grudgingly ;-) that for some implementations, renaming such a function call is somehow more complicated than just passing the result on to another subprogram call, so it is worth disallowing the renaming of such functions in a declare_expression. So long as we are allowed to rename pre-existing limited objects, I don't think we are losing any significant functionality. > Another way to look at these declarations is to look at the > accessibility of the constant (the enclosing entity) and of the > renaming (the enclosing entity). Again, these are semantically the same in > all visible properties. (Indeed, with the new shorter syntax, there is never > a reason to write a constant rather than a renaming for a function call.) I believe the time when task activation occurs is different between these two. You need to put the tasks that are part of a local stand-alone object onto a list for activation at the point of the "begin," whereas with renaming, you can activate immediately, > > There is never really a difference between renaming a function and > declaring a similar constant with the function. I believe there can be a significant performance difference when renaming vs. copying results of an unknown size in some implementations. > ... > Now, I can see an argument that the semantics are defined well enough > here, so we should allow the renames. But in that case, we should > allow the constant, too, since they are semantically identical. There > is literally no reason to ban one and allow the other. I don't agree, because if we allow a stand-alone object with task parts, we would probably need to establish an activation list and do something appropriate at the "begin," as well as probably adjust the wording in 9.2 somewhat since it presumes that a "begin" is followed by a sequence of statements. So I am definitely *not* asking for non-limited constants. > However, I think that a large part of the reason for this restriction > is that it allowed a number of Bairdian cases where the declared > object is part of the result of the declare_expression. That is, something like: > > (declare > Bah : constant Type_with_Tasks := Ugly (12); > Humbug renames Ugly (4); > begin > (if Tucker > Taft then Bah else Humbug)) I don't see how we are avoiding this, since presumably I can write: (if Tucker > Taft then Ugly(12) else Ugly(4)); *************************************************************** From: Randy Brukardt Sent: Wednesday, January 16, 2019 3:11 PM ... > An object_renaming is not considered an object_declaration (by > syntax), and according to 9.2(2/2), it is the tasks created by the > elaboration of an object_declaration or generic associations of mode > IN that are all activated together at the "begin" of the declarative > part. The tasks associated with a renamed function call are therefore > treated like objects that are *not* created by object_declarations, > and according to 9.2(4/2), that means they are activated immediately > after completing their initializations, or if being returned by a > function, after having been returned from the function. So this > implies that the tasks, if any, are activated immediately upon return > of the function, whether we rename the result or pass it as a > parameter to another subprogram. Interesting. I wonder if we do that right? This is the first I've heard of this difference (at least in a long time, perhaps I've forgotten that it exists). > All in all I don't see much of a difference. Be that as it may, I can > accept (grudgingly ;-) that for some implementations, renaming such a > function call is somehow more complicated than just passing the result > on to another subprogram call, so it is worth disallowing the renaming > of such functions in a declare_expression. So long as we are allowed > to rename pre-existing limited objects, I don't think we are losing > any significant functionality. I agree that there isn't much difference, but I claim (still) that there is not much difference with an object_declaration, either. And I'm postulating that that is not work that we want to do. If so, we can't want to do similar work, either. > > Another way to look at these declarations is to look at the > > accessibility of the constant (the enclosing entity) and of the > > renaming (the enclosing entity). Again, these are semantically the > > same in all visible properties. > > (Indeed, with the new shorter syntax, there is never a reason to > > write a constant rather than a renaming for a function call.) > > I believe the time when task activation occurs is different between > these two. You need to put the tasks that are part of a local > stand-alone object onto a list for activation at the point of the > "begin," whereas with renaming, you can activate immediately, Creating that list happens inside the function when the return object is created, so there is going to be a list either way. Precisely where one calls TKActivate is hardly a big deal! > > There is never really a difference between renaming a function and > > declaring a similar constant with the function. > > I believe there can be a significant performance difference when > renaming vs. copying results of an unknown size in some > implementations. But we're talking about functions of a type which requires build-in-place. There's never any copying there. And even for other types, an implementation that is building a separate temporary object is working harder than necessary (there never is a need to have a such an object as there is with :=). In any case, an implementation is allowed to remove such a copy, so there need be no difference. I don't think we should compare the easiest possible implementation here (or anywhere); it matters much more that the semantics are essentially identical. > > ... > > Now, I can see an argument that the semantics are defined well > > enough here, so we should allow the renames. But in that case, we > > should allow the constant, too, since they are semantically > > identical. There is literally no reason to ban one and allow the other. > > I don't agree, because if we allow a stand-alone object with task > parts, we would probably need to establish an activation list and do > something appropriate at the "begin," as well as probably adjust the > wording in 9.2 somewhat since it presumes that a "begin" is followed > by a sequence of statements. So I am definitely *not* asking for > non-limited constants. You always have an activation list, because the tasks are created inside the function and activated outside in every case, and how else are you going to communicate that? As I said before, precisely the location that the list is activated is pretty much a trivial difference. Indeed, Janus/Ada doesn't have any mechanism other than an activation list for tasks. I wasn't aware of any case in Ada where tasks are activated immediately upon creation; there's always a short delay to at least the end of the construct. And, since in almost all such cases (like aggregates and functions) the code is shared with cases that activate much later, it doesn't even make sense to bother with other models (why be more complex than needed? Task activation isn't on the critical performance path for any program). I doubt that most Ada runtimes support anything other than list activation (it has to be part of the runtime, since all of the tasks have to become ready at the same instant - you can't do that with an explicit list walk that activates each task in turn). So, as previously noted, I see virtually no difference here, at least for implementation purposes. The only difference is where the list is activated, which is trivial. (Indeed, based on the semantics above, the renames is marginally more expensive, since we have to create an empty list specifically for each renames; there's only one for all objects.) > > However, I think that a large part of the reason for this > > restriction is that it allowed a number of Bairdian cases where the > > declared object is part of the result of the declare_expression. > > That is, something like: > > > > (declare > > Bah : constant Type_with_Tasks := Ugly (12); > > Humbug renames Ugly (4); > > begin > > (if Tucker > Taft then Bah else Humbug)) > > I don't see how we are avoiding this, since presumably I can write: > > (if Tucker > Taft then Ugly(12) else Ugly(4)); This is a direct build-in-place, which already has to work. It's bringing temporary objects into the picture that causes trouble. The good news is the example I wrote is clearly illegal, as it violates 7.5(2.1). So I don't remember the exact example (or if it was actually legal, or whether it was already plugged). *************************************************************** From: Bob Duff Sent: Wednesday, January 16, 2019 3:40 PM I'm somewhat confused by this thread. Is a declare_expression a master? Does it await termination of nested tasks and/or finalize nested objects? ... > ...all of the tasks have to > become ready at the same instant - you can't do that with an explicit > list walk that activates each task in turn). You can. First sort the list in decreasing order by priority, and raise one's own priority to the max of the activatees. > > > (declare > > > Bah : constant Type_with_Tasks := Ugly (12); > > > Humbug renames Ugly (4); > > > begin > > > (if Tucker > Taft then Bah else Humbug)) > ...The good news is the > example I wrote is clearly illegal, ... OK, good. I was about to ask "Isn't that illegal?!". *************************************************************** From: Tucker Taft Sent: Wednesday, January 16, 2019 3:42 PM >> ... > > This is a direct build-in-place, which already has to work. It's > bringing temporary objects into the picture that causes trouble. This is where I always get confused. You need some sort of temporary object when you call a function, whether you rename its result or pass it along to another subprogram call. We aren't disallowing calling functions that return tasks inside a declare_expression somewhere, we are merely disallowing renaming the result so you can use it multiple times. The object doesn't go away until the whole declare_expression goes away in any case, so the lifetime isn't being expanded. In any case, "uncle." ;-) *************************************************************** From: Tucker Taft Sent: Wednesday, January 16, 2019 3:45 PM > I'm somewhat confused by this thread. > > Is a declare_expression a master? Does it await termination of nested > tasks and/or finalize nested objects? ... I am presuming a declare_expression is not a master unless it is used in a context where any sort of expression would be a master, namely, it is used in a context that is not enclosed by another expression, function_call, range, or simple_statement. *************************************************************** From: Randy Brukardt Sent: Wednesday, January 16, 2019 4:17 PM > I am presuming a declare_expression is not a master unless it is used > in a context where any sort of expression would be a master, namely, > it is used in a context that is not enclosed by another expression, > function_call, range, or simple_statement. That's my understanding of the rules as well. Part of the reason that we wanted to avoid task waiting and the like is that it doesn't make a lot of sense in the declare_expression is not a master scenario. I admit that I didn't realize that task activation is different for renaming of a function result. [This seems like a bug to me, since it is the only semantic difference from the similar object declaration (finalization and task waiting happening at the same point for both cases), and I'd hate to see people renaming function results specifically to get this behavior -- that seems incredibly confusing. I think it's much more likely to be a surprise that the tasks activate early. But way too late to change anything here, since this is an Ada 95 feature reimagined by Ada 2005.] So for the single case of task waiting, there is some reason to treat renames and constants differently. (Not so much for build-in-place, but no one seems to remember the issue for that, so it's hard to discuss!) In any case, I think we all agree that there is no issues with renaming of existing objects (and you can't declare a constant of those anyway), so it probably makes sense to allow that. Otherwise, we'll start out conservative and see how much complaining there is. Bob wrote: >> ...all of the tasks have to >> become ready at the same instant - you can't do that with an explicit >> list walk that activates each task in turn). > >You can. First sort the list in decreasing order by priority, and >raise one's own priority to the max of the activatees. I suppose, but that's complex enough that you're not going to do it in-line. (A list sort of a list of unknown length in-line? Followed by a priority change, a list walk, and another priority change? Nope.) So the general case is going to be in a library subprogram, and what you do inside of that (something like the above would be necessary for a threaded implementation, I presume) isn't very interesting. You could of course optimize it if you somehow know how many tasks are on the list (as in the directly declared case), especially if there is only one task to activate. But it doesn't seem critical enough to spend time/space on, it's hard to imagine a case where the cost to activate a task is on the critical performance path. Seems like there are zillions of more likely things to optimize. *************************************************************** From: Bob Duff Sent: Wednesday, January 16, 2019 4:36 PM > I suppose, but that's complex enough that you're not going to do it in-line. Right. I didn't mean to imply that it should be done inline. Just that you don't need an atomic "make many tasks ready" operation. (Actually, I think I was wrong to say "raise one's own priority to the max of the activatees". I don't think that's necessary.) But anyway, never mind all that, this is of course a side issue. *************************************************************** From: Bob Duff Sent: Wednesday, January 16, 2019 4:19 PM > I am presuming a declare_expression is not a master unless it is used > in a context where any sort of expression would be a master, namely, > it is used in a context that is not enclosed by another expression, > function_call, range, or simple_statement. OK, so: declare X: Integer := (declare Y: T; begin 123); begin ... end; If T has finalization and tasks, when does Y get finalized, and when are the tasks in Y activated and when do we await their termination? Now change it to: declare X: Integer; begin X := (declare Y: T; begin 123); ... end; Does that change things? (Now the decl_expr is enclosed by a simple statement.) *************************************************************** From: Randy Brukardt Sent: Wednesday, January 16, 2019 4:38 PM > OK, so: > > declare > X: Integer := (declare Y: T; begin 123); This is illegal, because of (A) missing "constant" and (B) limited objects aren't allowed. We were only discussing the renames of a function case. > begin > ... > end; > > If T has finalization and tasks, when does Y get finalized, and when > are the tasks in Y activated and when do we await their termination? So let's write an example of interest: function Fun return T; declare X : Integer := (declare Y renames Fun; begin 123); begin ... end; Here Y's tasks are activated as soon as the function returns (according to Tucker's reading of the rules that surprised me), Y gets finalized and task awaited after the completion of the declaration of X. > Now change it to: > > declare > X: Integer; > begin > X := (declare Y: T; begin 123); X := (declare Y renames Fun; begin 123); > ... > end; > > Does that change things? (Now the decl_expr is enclosed by a simple > statement.) Only at the end of the assignment statement. Note that Tucker compare this to the case of parameter passing. An example of what he's talking about: function Real_Fun (X : in T; N : in Natural) return Integer; declare X : Integer := (declare C : constant Natural := 12; begin Real_Fun(Fun, C)); begin ... end; Here, the result of Fun is activated after Fun returns and before Real_Fun is called. The return object is finalized and its tasks awaited when the entire object_declaration for X is finished. For obvious reasons, we can't disallow this sort of thing in a declare_expression, especially as it is allowed in any existing expression. ----- In any case, we've agreed (because I've worn out Tucker :-) to only allow limited renames in the case of existing objects (not function calls). So both of these examples are illegal in the rules as written. That is the conservative choice, and it is hard to imagine that rule causing any new hardships (there are plenty of *old* hardships - like the parameter passing case above, but we're stuck with those). Such a rule would allow everything that seems likely. I think that if people start bumping into this and complaining, we can change that in Corrigendum 1 for Ada 2020. If we allow it and there are implementation issues, we're stuck. *************************************************************** From: Tucker Taft Sent: Wednesday, January 16, 2019 4:53 PM > OK, so: > > declare > X: Integer := (declare Y: T; begin 123); > begin > ... > end; > > If T has finalization and tasks, when does Y get finalized, and when > are the tasks in Y activated and when do we await their termination? We aren't allowing limited types nor variables. But I'll try to answer your question presuming this is legal. I guess the finalization part is allowed, so that is still a relevant question. In any case, the tasks would be activated, probably at the begin if we allowed this. If it were a renaming of a function_call, the tasks would presumably be activated immediately upon return from the function call being renamed. Finalization will happen when we complete the evaluation of the declare_expression, because it is itself a master in this context. If we allowed tasks, they would be awaited then as well. I believe this general timing would apply very similarly for an "if" expression that called a function that returned a task and then passed it to another function. If the declare_expression (or "if" expression) were to return a task itself, then RM 3.10.2(16.1/3) will presumably apply, and the master of that returned object would *not* be the declare_expression, but rather be the same as X. > Now change it to: > > declare > X: Integer; > begin > X := (declare Y: T; begin 123); > ... > end; > > Does that change things? (Now the decl_expr is enclosed by a simple > statement.) The finalization won't happen until the assignment is complete. If we allowed tasks, they would be activated presumably at the same time as in the earlier case, and then awaited when finalization occurs, which is at the end of the statement. This would clearly not be legal if the declare_expression were itself yielding a task object, so we don't need to answer that one. 3.10.2(16.1/3) is pretty critical as far as determining the rules for the "body_expression". But for other stuff happening inside the declare_expression, it all gets finalized pretty quickly. *************************************************************** From: Tucker Taft Sent: Wednesday, January 16, 2019 4:56 PM > So let's write an example of interest: > > function Fun return T; > > declare > X : Integer := (declare Y renames Fun; begin 123); > begin > ... > end; > > Here Y's tasks are activated as soon as the function returns > (according to Tucker's reading of the rules that surprised me), Y gets > finalized and task awaited after the completion of the declaration of X. Actually, I believe it gets finalized before that, just like an "if" expression. It would get finalized when the declare_expression is done, since the declare_expression is the outermost expression/function-call/range/simple-statement. Individual declarations are not masters generally, unlike simple statements. *************************************************************** From: Bob Duff Sent: Wednesday, January 16, 2019 5:01 PM > I think that if people start bumping into this and complaining,... If customers complain to AdaCore, then I can implement it. Don't need permission from ARG. Or if I run out of more important things to do, I'll implement it. Then we can ask ARG to standardize, wait for it, ... "existing practice". ;-) So Tuck should push me to implement it next year, rather than pushing ARG this year. >... If we allow it and there are > implementation issues, we're stuck. Right. *************************************************************** From: Randy Brukardt Sent: Wednesday, January 16, 2019 5:36 PM > > Here Y's tasks are activated as soon as the function returns > > (according to Tucker's reading of the rules that surprised me), Y > > gets finalized and task awaited after the completion of the > > declaration of X. > > Actually, I believe it gets finalized before that, just like an "if" > expression. It would get finalized when the declare_expression is > done, since the declare_expression is the outermost > expression/function-call/range/simple-statement. > > Individual declarations are not masters generally, unlike simple > statements. You're right that I need to reread the rules before answering questions like this. There are cases where you'd be wrong, but this isn't one of them. There definitely are cases where parts of expressions (parameters) essentially get their master an extra level out, and those were the sorts of cases I was thinking about here. Assuming we have the accessibility rules correct, then there are interesting cases. type T2 is tagged ... -- Assume controlled part(s). type T3 is private; -- A composite type. function Fun2(I : in Integer) return T2; function Fun3(X : in out aliased T2) return T3; declare X : T3 := (declare Y renames Fun2 (12); begin Fun3 (Y)); Z : T3 := (declare begin Fun3 (Fun2 (12)); begin ... end; X is illegal, I think, because Y has the accessibility of the expression, while the parameter to Fun3 has to have the accessibility of the "master of the call", which is the master of X. (See 6.4.1.) OTOH Z is legal, I think, because the master of the call of Fun2 is that of Fun3 (we don't define these things in such a way to ensure that they would always fail an accessibility check), so the results of both of these functions get finalized with X. That might be rather late, but at least it isn't wrong. (This is the infamous 3.10.2(10.2/3) and 3.10.2(10.5/3).) Just think, the introduction of declare_expressions will give us many new ways to fail accessibility checks. The ACATS tester in me can't wait. ;-) *************************************************************** From: Randy Brukardt Sent: Monday, January 21, 2019 11:45 PM I've finally gotten back to adding the rest of AI12-0236-1 to the draft Standard. I don't think any of these are controversial, but since these all but the last two go beyond editorial, I'm asking if anyone has an objections to any of these suggestions. Note that all of these will be in the AI that we'll see at our next meeting; hopefully, we can avoid much discussion on this but there are too many changes to treat them all as editorial. P.S. Starting at (12) since that is where I left off last time. Anyone that thinks that it is easy to add a feature to Ada should look at the number of issues that this one has generated (*after* we thought it was done). -------- (12) Add after 3.10.2(32.2/3): * a declare_expression (see 4.5.9); or ...which adds this to "distributed accessibility". However, 3.10.2(32.5/3) assumes that every distributed accessibility expression has a conditional_expression in it somewhere. We could try to fix 3.10.2(32.5/3), but the purpose of this stuff is to deal with the fact that we don't know the accessibility of a conditional expression statically, since we don't know which branch will be executed. That is not a problem with a declare_expression, the rules make it clear that the accessibility is that of the *body_*expression. That means the reason for including declare_expression here is to deal with the case of a declare_expression with a nested conditional_expression: (declare A : Ptr := ... begin (if B > 10 then A else C'access)); So we don't want *any* declare_expression here, but just the ones that have conditional_expressions as their body expression. So we should use wording that echoes that for qualified expressions: * a declare_expression (see 4.5.9) whose *body_*expression has distributed accessibility; or (13) 3.10.2(16.1/3) includes rules for both qualified expressions and conditional expressions, but doesn't have a similar rule for declare_expression. This would matter when the body_expression of a declare_expression is a function call, for instance: P := Ptr(Func); -- The result has the accessibility of Ptr, whatever that is. but without 16.1, P := Ptr(declare A ... begin Func); -- The result has the accessibility of -- the expression, probably will fail an accessibility check. I think this might have been omitted when a declare expression was itself a master, but now that that isn't true, I think we need declare_expression in 3.10.2(16.1/3). Probably: In the above rules, the operand of a view conversion, parenthesized expression or qualified_expression is considered to be used in a context if the view conversion, parenthesized expression or qualified_expression itself is used in that context. Similarly, a dependent_expression of a conditional_expression is considered to be used in a context if the conditional_expression itself is used in that context{, and a body_expression of a declare_expression is considered to be used in a context if the declare_expression itself is used in that context}. P.S. I'm done with this AI now, I think. Only 9 more giant AIs to go... *************************************************************** From: Tucker Taft Sent: Tuesday, January 22, 2019 2:43 PM > ... > > * a declare_expression (see 4.5.9) whose *body_*expression has > distributed accessibility; or Looks good. ... > In the above rules, the operand of a view conversion, parenthesized > expression or qualified_expression is considered to be used in a > context if the view conversion, parenthesized expression or > qualified_expression itself is used in that context. Similarly, a > dependent_expression of a conditional_expression is considered to be > used in a context if the conditional_expression itself is used in that > context{, and a body_expression of a declare_expression is considered > to be used in a context if the declare_expression itself is used in that context}. Looks good. > P.S. I'm done with this AI now, I think. Only 9 more giant AIs to go... Congrats! ***************************************************************