AI22-0123-1

!standard 8.6(27/2)                                          25-03-21  AI22-0123-1/06

!class Binding Interpretation 24-11-26

!status Amendment 1-2022  25-03-21

!status WG9 Approved 25-07-18

!status ARG Approved  11-0-2  25-03-19

!status work item 24-11-26

!status received 23-06-27

!assigned author Randy Brukardt

!submitter Randy Brukardt

!priority Low

!difficulty Easy

!qualifier Clarification

!subject Type of aggregates and raise expressions

!summary

The type determined for the expected type of a construct that requires a single type in some class of types is the type of the construct.

!issue

The type of a raise expression is rather purposely left undefined. It is of "any type", and in the vast majority of circumstances that is sufficient.

But sometimes the type of a (sub)expression is needed to determine the legality of a call (or other expression). For instance, a call on an abstract subprogram has to be a dispatching call, and that is determined by the type of the actual expressions for the controlling parameter(s) of the call.

If we have a raise_expression used as the controlling operand of such a call (an example of this case is given in the !example section), is that legal? (No.)

Since the type of the raise_expression is left unspecified in 11.3, it isn’t clear. If the type is the specific type, then the call is illegal; if the type is the appropriate class-wide type, then the call is legal. Both choices are “any type” that match the type of the controlling parameter. It seems that we need to know the type of a raise_expression.

More generally, what type is chosen for a construct that has an expected type of a single type in some class of types when more than one type would match the type given for the context? That can potentially happen for access types (as various anonymous access types can match), tagged types, and scalar types. The language does not seem clear on this point.

!recommendation

The wording “single type” is in the form of a Legality Rule that is enforced after resolution. We cannot use the requirements of a single type to affect resolution. But that is OK, as we don’t need to know the exact “single type” that was determined until the application of rules after resolution is complete. (See the discussion for more on this point.)

As such, all we need to do is to clearly identify the type of the construct after resolution is complete. The single type rule gives restrictions on how that type is determined, but never really says what it is. We add an extra sentence clarifying that the type is what we think it is.

!wording

Modify 8.6(27/2):

When a construct is one that requires that its expected type be a single type in a given class, the type of the construct shall be determinable solely from the context in which the construct appears, excluding the construct itself, but using the requirement that it be in the given class. Furthermore, the context shall not be one that expects any type in some class that contains types of the given class; in particular, the construct shall not be the operand of a type_conversion.{ For the purposes of the application of subsequent rules, the construct is considered to have the single type so determined.}

[Author’s note: I considered saying “later processing” here, but that seemed too vague. We’re mostly talking about the application of Legality Rules and Dynamic Semantic rules.]

AARM Discussion: (add after the existing AARM notes for this paragraph)

The construct has exactly the single type determined for the construct by resolution. Later processing (including the resolution of the contents of the construct, as in an aggregate) depends on that single type. In particular, the construct cannot be considered to have some other type that happens to be compatible with the single type. For instance, if the single type is a specific type, the construct cannot be considered to have some class-wide type that covers the specific type. This can be important for the application of Legality Rules.

!discussion

Modifying the carefully crafted existing wording is not appealing, so we just add a new sentence at the end. Arguably, the existing wording is enough, since it talks about limitations on how the type of the construct is determined. That implies that the construct has a type determined by resolution and we are just checking that it is appropriate. Thus, we could have just added the AARM notes and made this a Ramification. But we thought that the extra clarification was valuable.

----

Note that in many of these cases, the expected type is a member of some class of types, and the qualifier “single” is associated with a Legality Rule, not any Name Resolution Rule. Thus the term “expected type” does not, by itself, identify a unique type for the construct.

We sidestep this by simply using the “single type” as defined in the Legality Rule, and do not attempt to say what the type of the construct is while it is being resolved.

----

We talk about “application of subsequent rules” rather than just Legality Rules. We do this for two reasons. First, the type of an aggregate certainly has an effect on the Dynamic Semantics of the aggregate, we certainly want to include that in the wording. Second, for an aggregate, “application of subsequent rules” includes the resolution of the interior of the aggregate. That’s because the inside of constructs requiring a single type may need to know that type before doing any processing (in particular, resolution) on the contents of that inside.

An example may make this clearer:

    A := Func ((B => C, D => E));

If Func is overloaded, one cannot determine how to resolve the contents of the aggregate (in this case, B, C, D, and E) until the specific Func being called is determined. Indeed, we don’t even know the kind of constructs (and thus their scope) we are dealing with: B could be an expression for an array aggregate, or it could be the name of a record component.

As such, most (all?) compilers do not even try to resolve the interior of an aggregate until the expression in which it is embedded has been completely resolved. In this case, that means that we will have determined a unique interpretation for A and Func before doing anything with the aggregate. It is only after those outer parts of the expression are completely resolved that we even consider the type of the aggregate, check its Legality, and then finally attempt to resolve the contents.

Indeed, in the Janus/Ada implementation, resolution of an aggregate is performed when the enclosing expression is being checked for Legality violations. Along with resolution, legality checks are performed simultaneously at this point. So “application of subsequent rules” might include additional (separate) resolution, as well as the application of Legality Rules and Dynamic Semantics such as language-defined checks.

!example

The type of a raise_expression can matter for enforcing Legality Rules. For instance, calls on abstract operations have to be dispatching calls. A raise_expression does not have the correct type for that. Consider:

 

   package P is
      type T is abstract tagged null record;

      function Foo (X : in T) return Boolean is abstract;
   end P;

   package TBD_Pack is
      TBD_Error : exception;
      subtype TBD_Type is Natural;
      function TBD_Cond return Boolean is (False);
      procedure TBD is null;
   end TBD_Pack;

   with P, TBD_Pack; use P, TBD_Pack;
   procedure Q is
   begin
      if Foo (X => (raise TBD_Error)) then -- (A)
         TBD;
      elsif Foo (X => T'Class'(raise TBD_Error)) then -- (B)
         TBD;
      end if;
   end Q;

The type of the raise_expression at (A) is the specific type T, as that is the type of the parameter and thus will be the single type determined for the raise_expression. As such, the call is not a dispatching call and thus is illegal.

The usage can be written with a qualified expression as illustrated at (B), so this error is more annoying than limiting.

!corrigendum 8.6(27/2)

@drepl

When a construct is one that requires that its expected type be a @i{single} type in a given class, the type of the construct shall be determinable solely from the context in which the construct appears, excluding the construct itself, but using the requirement that it be in the given class. Furthermore, the context shall not be one that expects any type in some class that contains types of the given class; in particular, the construct shall not be the operand of a @fa{type_conversion}.

@dby

When a construct is one that requires that its expected type be a @i{single} type in a given class, the type of the construct shall be determinable solely from the context in which the construct appears, excluding the construct itself, but using the requirement that it be in the given class. Furthermore, the context shall not be one that expects any type in some class that contains types of the given class; in particular, the construct shall not be the operand of a @fa{type_conversion}. For the purposes of the application of subsequent rules, the construct is considered to have the single type so determined.

!ACATS test

An ACATS B-Test should be constructed to check that the example is properly rejected. The type of an aggregate is assumed by many existing tests, there doesn’t seem to be any value to including any additional tests.

!appendix

From: Randy Brukardt (privately)

Sent: Tuesday, June 27, 2023  4:44 PM

[Edited reply/rephrasing of a question originally posed (badly) by Steve

Baird.]

The type of a raise expression is rather purposely left undefined. It is of "any type", and in the vast majority of circumstances that is sufficient.

 

But sometimes the type of a (sub)expression is needed to determine the legality of a call (or other expression). Consider:

 

   package P is
           type T is abstract tagged null record;

           function Foo (X : in P) return Boolean is abstract;
  end P;

  package TBD_Pack is
           TBD_Error : exception;
           subtype TBD_Type is Natural;
           function TBD_Cond return Boolean is (False);
           procedure TBD is null;
  end TBD_Pack;

  with P, TBD_Pack; use P, TBD_Pack;
  procedure Q is
  begin
           if Foo (X => (raise TBD_Error)) then -- (A)
               TBD;
           elsif Foo (X => T'Class'(raise TBD_Error)) then -- (B)
               TBD;
           end if;
  end Q;

 

Is the call of Foo at (A) legal? In order to be a legal call, it needs to be a dispatching call. That would be true if the type of the raise expression is T'Class and false otherwise. Since I can't find any justification for assuming that a raise expression has some specific type (rather than "any type" which would allow the call to resolve), and even if there was, one would expect the type to be the "closest match" (in this case, T), one has to conclude that this call is illegal. That's mildly uncomfortable, since a TBD usage as given above seems reasonable. OTOH, (B) definitely is legal so one can write what is needed.

 

Fixing it so this case would work would seem to require some sort of hack, and it doesn't seem critical to have this work (especially as an explicit qualification of the raise expression as in (B)), so I tend to think it isn't worth solving this at this time. But it is true that the Legality of (A) is in

a bit of a grey area, since nothing is truly clear that the raise expression has type T, either.

 

Thoughts (especially from Tucker)?

 


 

From: Tucker Taft (privately)

Sent: Wednesday, June 28, 2023  9:12 AM

RM 11.3(3.2/4) says:

  The expected type for a raise_expression shall be any single type.

RM 8.6(27/2) says:

  When a construct is one that requires that its expected type be a single type

  in a given class, the type of the construct shall be determinable solely from

  the context in which the construct appears, excluding the construct itself, but

  using the requirement that it be in the given class.

So one question is whether the context represented by a controlling operand in a call on a dispatching operation, determines a single expected type.  The answer is yes, since 6.4.1(3) is crystal clear:

  The expected type for an actual parameter is the type of the corresponding

  formal parameter.

This is true even though class-wide operands are permitted by 8.6(23).  Be that as it may, I think the only reasonable interpretation of 11.3(3.2/4) is that the type of a raise expression is its single expected type.  If you want a raise expression to be interpreted as some other type, you need to use a construct that determines that other type as the expected type (such as a qualified expression).

To resolve the ambiguity, we could augment 11.3(3.2/4) as:

  The expected type for a raise_expression shall be any single type{, and the type of the raise_expression is that type}.

I also think we could make this an AARM TBH note, though it seems simple enough to add the suggested phrase to the RM.


 

From: Steve Baird (privately)

Sent: Wednesday, June 28, 2023  1:26 PM

Do we have similar issues (or non-issues, if this all seems insignificant) with other cases where there is a single expected type?

For example, we've got

  For an attribute_reference with attribute_designator Access (or

  Unchecked_Access — see 13.10), the expected type shall be a single access type A

   such that: ...

Given

   type Ref is access all Integer;
  Int : aliased Integer;
  Ptr : Ref := Int'Access;

one *could* argue that 8.6(25.1) could apply, in which case the type of the attribute_reference would be an anonymous type instead of the named type Ref.

If the response is "what anonymous type? there isn't any in this example!", then add the following declaration at the start of the example:

        Unrelated : access Integer;

My point is that if there is any problem at all here, then perhaps it is not limited to raise expressions.

Would it make sense to have a preference rule in 8.6 something like

        If two acceptable interpretations of a constituent of a complete context

        which has a single expected type differ only in that the type of the

        constituent is the expected type for exactly one of the two, then that

        interpretation is preferred.

?

[I'm trying to follow the wording of the other 8.6 preference rules here.]


 

From: Tucker Taft (privately)

Sent: Wednesday, June 28, 2023  1:30 PM

> Do we have similar issues (or non-issues, if this all seems insignificant)

> with other cases where there is a single expected type?

I think this gets a bit silly at some point.

...

> Would it make sense to have a preference rule in 8.6 something like

>        If two acceptable interpretations of a constituent of a complete context

>        which has a single expected type differ only in that the type of the

>        constituent is the expected type for exactly one of the two, then that

>        interpretation is preferred.

> ?

I suppose, though I find the suggested wording a bit convoluted.  I think a TBH might be the right answer, rather than opening this can of worms.


 

From: Steve Baird (privately)

Sent: Wednesday, June 28, 2023  5:40 PM

I like the TBH solution, but I think it belongs in 8.6.

Something like

   TBH: If an expression that has an expected type can be interpreted as being

   of that type, then that's how it is interpreted. For example, if

   a raise_expression is passed as a non-access controlling operand in a call

   to a dispatching operation then its type is the specific type and not the

   class-wide type.


 

From: Tucker Taft (privately)

Sent: Wednesday, June 28, 2023  10:54 PM

Fine with me.


 

From: Randy Brukardt (privately)

Sent: Thursday, June 29, 2023  1:25 AM

>>I think this gets a bit silly at some point.

>

>Yeah, I wondered about that. Note the subject line of my original message to

>Randy.

The issue is whether the type chosen matters for some reason. If not, the discussion is beyond silly, it's getting close to harmful because there is no point in answering questions that don't matter (and the attempt to do so can introduce bugs).

But Steve has pointed out at least one case where Legality depends upon the type chosen.

There doesn't seem to be any such case for 'Access. The only Legality Rules (3.10.2(27-27.2)) depend on the properties of the expected type (not the type that it is ultimately resolved to). So the type of X'Access is ultimately irrelevant and we don't need to answer this question.

One wonders about the case for operands of type conversions, but those don't require single types (they require "any type"). As such raise expressions aren't allowed there (the context doesn't identify a "single type"), and I can't think of any case that would have ambiguity *and* would be legal.

The things that require single types are (at least the ones I can find with a search):

        raise expressions

        aggregates

        'Access

        reduction expressions

I think is pretty clear that for aggregates and reduction expressions, we need to know the type of the entity in order to determine its semantics (both for legality and dynamically). So I don't think there is any real question here, as we've been assuming the answer for a long time (else no aggregates would work).

For instance, we never interpret a tagged record aggregate as having T'Class when the expected type is T, since such an aggregate is illegal. It would be silly to do so.

Ergo, I don't think there is a real question here, and thus a TBH certainly would suffice. The TBH proposed seems OK.


 

From: Steve Baird (privately)

Sent: Thursday, June 29, 2023  1:22 PM

> The TBH proposed seems OK.

That's the main point IMO.

> But Steve has pointed out at least one case where Legality depends upon the

> type chosen.

>

> There doesn't seem to be any such case for 'Access.

It doesn't really matter, but I disagree with this point (although my argument relies on a very unfriendly, unintuitive reading of the RM).

Based on my (admittedly far-fetched) point that 8.6(25.1) *could* arguably apply in some unexpected cases, consider

        type Ref is access all Integer;
        Ptr :
Ref;
        X :
aliased Integer;
 
begin
         
declare
       
type Rec is record F : access Integer; end record;
         
begin
        Ptr :=
Ref[a]'Access; -- legal?
         
end;

         

If we interpret 8.6(25.1) to mean that the type of Ref'Access could be the anonymous type of Rec.F, then we do or don't get a legality violation (of 4.6(24.17)) depending on which type is chosen for Ref'Access.

The proposed TBH note resolves this issue.

This is just another supporting argument for something that we have already decided is a good idea (that is, the TBH note) so this argument really doesn't matter.


 

From: Randy Brukardt (privately)

Sent: Tuesday, November 26, 2024  1:48 AM

[Another very late reply...]

> Do we have similar issues (or non-issues, if this all seems insignificant)

> with other cases where there is a single expected type?

>

> For example, we've got

> For an attribute_reference with attribute_designator Access (or

> Unchecked_Access — see 13.10), the expected type shall be a single

> access type A such that: ...

>

>Given

>   type Ref is access all Integer;

>   Int : aliased Integer;

>   Ptr : Ref := Int'Access;

>one *could* argue that 8.6(25.1) could apply, in which case

>the type of the attribute_reference would be an anonymous type instead of

>the named type Ref.

You could, but you would be wrong, as 3.10.2 goes on to say:

"The type of X'Access is an access-to-object type, as determined by the expected type."

Now, I suppose you could try to argue about the meaning of "determined" here, but let's get real.

The problem noted in the original thread is the lack of wording like that for raise_expressions. It's also the case that aggregates don't have a clear statement of the type. Reduction expressions have one indirectly (through the definition of Accum_Subtype). Character literals also have one indirectly through the definition of the associated function's return type. (Although that would be clearer if it said "that" character type rather than "the" character type.)

The proposal was:

> Something like

>   TBH: If an expression that has an expected type can be interpreted as being

>   of that type, then that's how it is interpreted. For example, if

>   a raise_expression is passed as a non-access controlling operand in a call

>   to a dispatching operation then its type is the specific type and not the

>   class-wide type.

But that's overly broad; all of the cases we're interested in are when an expected type is some kind of single type.

A TBH if we were to have one should appear directly after 8.6(27/2), and would say something like:

TBH: When the expected type of a construct is some single type, the construct has that single type. For example, if a raise_expression is passed as a non-access controlling operand in a call to a dispatching operation then its type is the specific type and not some class-wide type. Legality rules are enforced on the given type, so if the example dispatching operation is abstract,

this example call is illegal.

The problem with this is two-fold:

(1) 8.6(27/2) already says something very close to this note:

  When a construct is one that requires that its expected type be a single type

  in a given class, the type of the construct shall be determinable solely from

  the context in which the construct appears,  ...

As such, the note is more of a Ramification than a TBH.

(2) Given that three out of the five known constructs already explicitly say the type of the construct is that of the expected type, it would probably be better to simply fix the two that don't.

Ergo:

Add to the end of 4.3(3/6):

   The type of the aggregate is that determined by the expected type.

   

[Arguably, we should add this to the more specific Name Resolution Rules in each of 4.3.1-4.3.5. But it seems obvious and unnecessary to repeat.]

Modify 11.3(3.2/4):

   The expected type for a raise_expression shall be any single type{; the

   type of the raise_expression is that determined by the expected type.}


 

[a]Attribute cannot be applied to a type. X'Access?