Version 1.5 of ai12s/ai12-0155-1.txt
!standard 13.14(3/4) 15-04-02 AI05-0155-1/04
!class binding interpretation 15-02-20
!status work item 15-02-20
!status received 15-02-20
!priority Low
!difficulty Easy
!qualifier Omission
!subject Freezing of operations of incomplete types with completions deferred to a body
!summary
When a profile is frozen at the end of a declarative part or package
specification, any incomplete types that the profile contains are not frozen.
!question
Consider:
package Pack is
type Bag_Type is private;
procedure Add (Bag : in out Bag_Type; Elem : Integer);
private
type Set is tagged;
type Bag_Type is access Set;
package Inner is
--
procedure Add (Elem : Integer; To_Set : in out Set);
procedure Union (Left, Right : in Set; Result : out Set);
procedure Intersection (Left, Right : Set; Result : out Set);
function "=" (Left, Right : Set) return Boolean;
end Inner;
end Pack; --
13.14(3/4) [and earlier versions as well] says that everything excepting
incomplete types are frozen at the end of Pack. This includes the profiles of
any subprograms declared within Pack.
13.14(14/3) tells us that freezing of a profile freezes each subtype of the
profile. That means, for example, that freezing the profile of Union freezes
Set.
But freezing of Set is illegal by 13.14(17/3) [the type is not yet complete].
[This is why there is an exception for incomplete types in 13.14(3/4),
otherwise any incomplete type with the completion deferred to a body would be
illegal.] Thus all of the declarations in package Inner are illegal.
The changes to the usage of incomplete types in Ada 2012 (AI05-0151-1) would
suggest that these subprograms should be legal. They could be called from a
child unit without that child having to see the full type for Set, so they
seem potentially useful. Should such subprograms be allowed? (Yes.)
!recommendation
(See Summary.)
!wording
Modify 13.14(3/4):
The end of a declarative_part, protected_body, or a declaration of a library
package or generic library package, causes freezing of each entity and profile
declared within it, except for incomplete types. A proper_body, body_stub, or
entry_body causes freezing of each entity and profile declared before it within
the same declarative_part that is not an incomplete type; it only causes
freezing of an incomplete type if the body is within the immediate scope of the
incomplete type. {Freezing of a profile in these cases does not freeze an incomplete
subtype of the profile.}
Modify 13.14(10.2/4):
At the place where a generic_instantiation causes freezing of a callable entity,
the profile of that entity is frozen unless the formal subprogram corresponding
to the callable entity has a parameter or result of a formal untagged incomplete
type; if the callable entity is an expression function, the return expression
expression of the expression function causes freezing{; if the formal subprogram
corresponding to the the callable entity has a parameter or result of a formal
incomplete type, freezing of a profile does not freeze an incomplete
subtype of the profile}.
AARM Reason: The last part only can apply if the parameter or result is a formal
tagged incomplete type, as the entire profile is not frozen otherwise. We except
incomplete types in this case so that calls that would be legal in a non-generic
context can be used within a generic for a formal tagged incomplete type as well.
!discussion
13.14(3/4) has an explicit “hole” so that incomplete types whose completion is
given in the body (so-called Taft-Amendment types) do not freeze. Otherwise,
such incomplete types would be illegal as 13.14(17/3) would be violated when
they were frozen at the end of the scope.
Ada 2012 added explicit rules allowing operations like the ones in the
question. It seems bizarre that these operations are always illegal because
of freezing; it reduces the value of the new rules.
Operations like the ones in the question can be useful to provide operations
to child units of a subsystem on a Taft-Amendment type without having to make
the full declaration visible.
Such operations are limited to non-primitive operations as any primitive
operations of a Taft-Amendment type are illegal by 3.10.1(9.3/2). However, as
noted in the question, it's easy to declare non-primitive operations by adding
a nested package; this trick is widely known among expert Ada programmers, so
it's likely to be used in this case.
----
The reason that we need to freeze incomplete types is discussed in AI05-0017-1.
In particular, it's necessary to prevent premature dispatching calls for
ordinary (not Taft-Amendment) incomplete types. (Taft-Amendment incomplete
types can't have primitive operations, so no dispatching calls can be made
on an operations of them.)
Therefore, we only exclude freezing of incomplete types in contexts where
no further premature calls can be made (the "global freezing contexts"), or
dispatching calls are impossible for other reasons.
We have to make similar exceptions for generic instantiations, so that one
can wrap code in a generic without it becoming illegal when instantiated.
Luckily, normal generic formal subprograms are never dispatching, and
abstract formal subprograms do not allow an incomplete controlling type.
Thus, we don't need to freeze incomplete types here.
----
Notes on the 13.14(3/4) wording:
We only need to say that incomplete types of the profile are not frozen. If
the incomplete type is not one that is exempt from freezing, it will
necessarily be frozen as well by one of these "global freezing contexts", and
thus we don't need to specifically include those in profile freezing.
The wording "subtype of a profile" seems a bit non-specific, but it echos the
wording of 13.14(14/3) [which is the normal case for freezing a profile]. There
is no reason for this to be different.
----
Notes on the 13.14(10.2/4) wording:
We don't need to worry about which incomplete types are used in the profile,
as any that don't correspond to a formal incomplete type will work as a
similar parameter in a non-generic call. Either that's OK, or any such call
would necessarily be illegal (if the incomplete type is untagged).
[Note to Tucker: It might be the case that we could simply exclude any
incomplete types from the profile of the actual, as it seems that any
incomplete that would be legal would have had to have been declared outside
of the generic, and that would work normally (whatever normally is). Such a
case seems rather unlikely outside of ACATS tests and ARG examples, so
perhaps the simpler wording is better.]
[Note to Tucker II: The example that requires freezing is:
package Pack is
type T is tagged;
function F return access T'Class;
function G (X : access T) return Integer;
I : Integer := G (F);
type T is tagged null record;
end Pack;
The call on F necessarily would fail an elaboration check (there is no legal
expression that could be used in an expression function; an allocator would be
premature and the prefix of 'Access cannot be incomplete). But the compiler
still would have to generate code for a call without knowing where the tag is.
This quite similar to the case described in the AARM, ending with AARM 13.14(i.u):
"This is silly. If we're going to require compilers to detect the exception at
compile time, we might as well formulate the rule as a legality rule."
However, there might be an alternative. We could adopt a Legality Rule stating
that a dispatching call on an object of an incomplete type is illegal before
a completion for an incomplete type. (We would not want such a rule to apply
for an incomplete view, since those never require freezing, and it's not
possible for any primitives to be imported that way anyway.) We already have a
rule of that nature for Taft-Amendment types (any operations cannot be
primitive), so it's not unprecedented.
I don't think that there is a problem for untagged incomplete types (or, for
that matter with tagged incomplete types that aren't dispatching). Direct uses
of untagged incomplete types as parameters is illegal. Passing
access-to-incomplete doesn't seem to cause any real problems (imagine the above
example without "tagged"); a non-null value would have to have been created
somewhere where the full definition is available, but if that is done, the
call using access-to-incomplete doesn't need to look into it.
[Bairdian aside: what happens if the access type has a discriminant constraint?
Is that allowed? If it is, the check would need to know where the discriminants
are, and that's not known until freezing. Janus/Ada has lots of bugs because of
problems like that; I had to simply guess (based on what is known when the
type is declared) and that got the ACATS to pass. But when I tried to do it
right (raising an exception if an unfrozen discriminant was accessed), I wasted
a week and got nowhere near getting anything to work. Took the check back out.
End aside.]
If we did that, then we could abandon any freezing of incomplete types
(anywhere). Based on the AARM notes, that was the intended model for Ada 95.
That probably would be simpler, but of course we'd have to let Steve tell us
about all of the unlikely things that could happen. And that would be a lot
more radical.]
----
The author created a complete ACATS-style test for an example like the one
in the question. The test uses the operations in a child package where
the completion is unknown. (This is usefully possible because the type
is tagged incomplete, thus calls can be made so long as the object is passed
intact.)
Compiling this program on GNAT shows that GNAT does not detect the freezing
violations. However, the child package containing the calls generates a
lovely internal error message. Because of this, it is not possible to run
the program.
As such, it seems unlikely that anyone is depending on these operations
working; neither the RM nor the only known fielded Ada 2012 implementation
allows them. That's not particularly surprising; the capability was added by
Ada 2012 and Taft-Amendment types aren't that common, so most likely no one
has yet run into the intersection of both.
[Note to Tucker III: I can certainly live with not changing anything here,
but then I certainly would want to have ACATS tests that ensure that the
original example was detected as a freezing error. Else we have an "attractive
hazard", something that looks like it should work, and might work on some
compiler, but actually doesn't work. That probably goes for the entire
instantiation of a generic with an incomplete type, too. (Banning that would
certainly make Ed happier. I don't quite understand why that is so hard to
implement, but of course things that should be easy can turn out hard in an
existing implementation not prepared to handle it. Probably the hardest thing
to make work in Janus/Ada would be "in out" parameters in functions, because
the entire back-end assumes that a function does not need modify parameters
after a call. That would be a very tough assumption to root out of the
optimizer in particular.]
----
** Temporary (?) note **
Calls in other units are OK, assuming that any entity imported via a with
(or from a parent, which is another form of semantic dependence; those should
all be treated equivalently) is already frozen (somewhere). That makes sense,
as some other unit must have the completion or the partition is illegal.
However, the language rules don't make this clear. It's definitely significant
for various kinds of incomplete types -- we surely don't want anyone to think
that incomplete views from a limited with need to have a completion visible
somewhere in order to declare or call a subprogram, and the same is true for
Taft-Amendment incomplete types. With that assumption, we don't need to fix
the generic problem, although it means that a generic cannot be instantiated
in the same scope as these operations. (Which is annoying, but not fatal, as
the instance could have been in the child unit, where no freezing would
apply [we assume!].)
It's not clear to me whether we should attempt to fix this problem.
** End Temporary (?) note **
!corrigendum 13.14(3/4)
Replace the paragraph:
by:
!ASIS
No ASIS effect.
!ACATS test
The problem was originally discovered in ACATS test CC51011, so some version
of that test will test the fix. There also is the proposed test named C3A1005,
a version of CC51011 without the generic units. If this AI is approved, that
test can be issued (it tests some moderate priority 3.10.1 objectives that are
currently on the testing list -- this AI would raise that priority somewhat).
!appendix
From: Randy Brukardt
Sent: Friday, February 28, 2015 5:52 PM
I have had a running conversation with an implementer over a series of tests,
which has led to a question that suggests that there might be a problem
(really an oversight) in the freezing rules. (Imagine that!)
Here's the important part of the example:
package Pack is
type Bag_Type is private;
procedure Add (Bag : in out Bag_Type; Elem : Integer);
private
type Set is tagged;
type Bag_Type is access Set;
package Inner is
-- These can't be primitive for Set, else 3.10.1(9.3/2) is violated.
procedure Add (Elem : Integer; To_Set : in out Set);
procedure Union (Left, Right : in Set; Result : out Set);
procedure Intersection (Left, Right : Set; Result : out Set);
function "=" (Left, Right : Set) return Boolean;
end Inner;
end Pack; -- Freezing here.
The implementer's complaints led me to look at how this freezes.
13.14(3/4) [and earlier versions as well] says that everything exception
incomplete types are frozen at the end of Pack. This includes the profiles of
any subprograms declared within Pack.
13.14(14/3) tells us that freezing of a profile freezes each subtype of the
profile. That means, for example, that freezing the profile of Union freezes
Set.
But freezing of Set is illegal by 13.14(17/3) [the type is not yet complete].
[This is why there is an exception for incomplete types in 13.14(3/4),
otherwise any Taft-Amendment type would be illegal.] Thus all of the
declarations in package Inner are illegal.
This only matters to Taft-Amendment types like Set in the example. For a type
that is completed in the package specification, 13.14(17/3) does not apply at
the end of the package, because the type is in fact complete. For an
incomplete view, one assumes that types that are imported were previously
frozen by the end of the compilation unit that contains them and thus don't
need any freezing. [BTW, I can't find any justification for this view in the
RM wording; it's something that I wish we'd fix because it comes up
periodically, but it also seems to fit the Dewar rule as no other
interpretation makes sense (at least to me). And since it fits the Duff rule
as well (no one would ever change behavior because of the fix), I've never
made an AI for it. Interestingly, I made this very same point in the e-mail
in AI12-0151-1 (June 6, 2009), so maybe it's mainly this case where it
matters. That e-mail specifically argued that there was no freezing problem
for incomplete views from limited withs for this sort of case, in response to
a question from Tucker. It must have satisified everyone, because no further
discussion occurred on that point.]
Note that there is no problem with these profiles freezing without knowing the
full type. We allow calls on similarly defined subprograms when the parameter
type is a tagged incomplete view defined by a limited with. The parameters
have to be passed by reference, and dispatching calls are prevented by
3.10.1(9.3/2) [primitives aren't allowed].
Such routines can be used in child packages without having to make the actual
implementation visible. That seems useful to me. (Maybe not useful enough to
bother changing the language, but certainly useful enough to disqualify this
case from the ARG gallery of pathologies, honoring Steve Baird. :-)
The freezing requirement here says that the expansions in usage of incomplete
types defined in Ada 2012 are completely unusable for Taft-Amendment types.
Any use of a Taft-Amendment incomplete type directly as a parameter or result
will necessarily result in a freezing violation. It's hard to believe we
intended that.
OTOH, AI12-0151-1 talks exclusively about incomplete views coming from limited
views. As such, it's possible that we didn't care at all about any changes that
happened for any other kind of access type.
Either way, I'd like to have a decision. If the freezing rule stands, then a
couple existing tests and a number of objectives are unnecessary, and I need to
clean up the ACATS and its documentation to reflect that. And if it is going to
be relaxed somehow, I'm sure the implementer would be interested, and the test
objectives probably will need a couple of additional cases.
For what it's worth, GNAT appears to accept the example package (and its body)
without complaint. I didn't try to create a runnable example, so I don't know
if the result would actually work or if I would get to see one of GNAT's famous
bug boxes. I'll do that IFF there is some interest in exploring whether the
freezing rules should allow examples like this. (Of course, if we confirm the
language, meaning the above is illegal, I'll make a B-Test out of the example
to ensure that it is getting rejected.)
****************************************************************
Questions? Ask the ACAA Technical Agent