!standard 3.9(7.4/2) 10-10-16 AI05-0173-1/03 !standard 3.9(12.4/2) !class Amendment 09-10-22 !status Amendment 2012 10-06-03 !status ARG Approved 6-0-1 10-06-20 !status work item 09-10-22 !status received 09-08-10 !priority Low !difficulty Easy !subject Testing if tags represent abstract types !summary The function Is_Abstract is added to Ada.Tags. !problem 3.9 (25.2/2) says 'An instance of Tags.Generic_Dispatching_Constructor raises Tag_Error if The_Tag does not represent a concrete descendant of T or if the innermost master (see 7.6.1) of this descendant is not also a master of the instance....', but there appears to be no way of determining if a given tag represents a non-concrete (i.e., abstract) type until an attempt is made to create an object from it. There should be a way to test this. !proposal Add a function Is_Abstract to Ada.Tags. !wording Add after 3.9(7.4/2): function Is_Abstract (T : Tag) return Boolean; Add after 3.9(12.4/2): The function Is_Abstract returns True if the type whose tag is T is abstract, and False otherwise. !discussion The name of the function Is_Abstract is a bit dubious: the parameter is a tag, but it is not the tag that is abstract; it is the type that the tag represents. However, existing routines like Is_Descendant have the same problem, so we use the simplest name. Since other rules require raising of Tag_Error for tags that represent abstract types in Generic_Dispatching_Constructor, it must be the case that this information is present in some form in the tag, so implementing the function should be trivial. Note that the only properties that could be queried on a tag are those that are invariant for the type. Specifically, privacy cannot be queried, as it depends on the view of the type. !example If one implements a registration function for a factory, it makes sense for that registration to test whether the tags would legitimately be able to create an object. That would put the error much closer to the cause compared to waiting until the factory is used to create an object. procedure Register (Factory : in out Factory_Type; Selector : in Selector_Type; Type_Tag : in Ada.Tags.Tag) is begin -- Check that the tag can be used to create an object of Root_Type'Class: if not Ada.Tags.Is_Descendant_At_Same_Level (Type_Tag, Root_Type'Tag) then raise Type_Error; elsif not Ada.Tags.Is_Abstract (Type_Tag) then raise Type_Error; end if; -- Check that the selector hasn't been previously registered: if Factory.Map.Contains (Selector) then raise Selector_Error; end if; Factory.Map.Insert (Selector, Type_Tag); end Register; !corrigendum 3.9(7.4/2) @dinsa @xcode<@b Interface_Ancestor_Tags (T : Tag) @b Tag_Array;> @dinst @xcode<@b Is_Abstract (T : Tag) @b Boolean;> !corrigendum 3.9(12.4/2) @dinsa The function Interface_Ancestor_Tags returns an array containing the tag of each interface ancestor type of the type whose tag is T, other than T itself. The lower bound of the returned array is 1, and the order of the returned tags is unspecified. Each tag appears in the result exactly once. If the type whose tag is T has no interface ancestors, a null array is returned. @dinst The function Is_Abstract returns True if the type whose tag is T is abstract, and False otherwise. !ACATS test Add an ACATS C-Test to test the new function. !appendix !topic Generic_Dispatching_Constructor and abstract derived types. !reference Ada 2005 RM3.9 !from Lee Clements 09-08-09 !keywords Generic_Dispatching_Constructor abstract Tag !discussion Should Ada.Tags define an Is_Abstract function? AARM 3.9 (25.2/2) says 'An instance of Tags.Generic_Dispatching_Constructor raises Tag_Error if The_Tag does not represent a concrete descendant of T or if the innermost master (see 7.6.1) of this descendant is not also a master of the instance....', but there appears to be no way of determining if a given tag represents a non-concrete (i.e., abstract) type until an attempt is made to create an object from it. I propose that a new function is required in Ada.Tags to allow better determination of the allowability of a tag in a call to an instance of generic_dispatching_constructor: function Is_Abstract (T: in Ada.Tags.Tag) return Boolean; Is_Abstract has the obvious sematics: returns True if the type represented by T is abstract, False otherwise. What is necessary to determine whether the type's master is compatible, I leave to others to determine. Also, is there any particular reason why the root type T is explicitly exluded from the types allowed to be created by Generic_Dispatching_Constructor? As I read AARM 12.5.1 (17) the actual type corresponding to a formal abstract tagged type 'can' (I read - 'may, but doesn't have to') be abstract? Constructor is an abstract subprogram and thus always dispatches to a concrete type (in this case through the 'magic' of Generic_Dispatching_Construtor, which raises Tag_Error is an invalid tag is specified). Excluding T seems to force users in some circumstances to create a root type below that strictly needed to model the problem, just to allow use with this facility. Furthermore, without having thought about it enough to come up with specific motivating examples, it would seem to me that there may be benefits to providing additional interrogative functions (based on the definition of a tagged type in 3.9 (2/2)), e.g.: function Is_Private (T: in Ada.Tags.Tag) return Boolean; --??? function Is_Limited (T: in Ada.Tags.Tag) return Boolean; function Is_Interface (T: in Ada.Tags.Tag) return Boolean; function Is_Task_Interface (T: in Ada.Tags.Tag) return Boolean; function Is_Protected_Interface (T: in Ada.Tags.Tag) return Boolean; function Is_Synchronized_Interface (T: in Ada.Tags.Tag) return Boolean; function Is_Protected_Derived_From_Interface (T: in Ada.Tags.Tag) return Boolean; function Is_Task_Derived_From_Interface (T: in Ada.Tags.Tag) return Boolean; ... again, with the 'obvious' semantics. I'd be interested to know whether the ARG considers these worth adding to the language specification. Motivating example: Inspired by the examples given in the Ada 2005 Rationale for the use of Ada.Tags.Generic_Dispatching_Constructor (2.6 Object factory functions), I thought I'd implement a generic factory: generic type Root_Type (<>) is abstract tagged limited private; type Selector_Type (<>) is private; type Parameters_Type (<>) is limited private; with function "<" (Left, Right : in Selector_Type) return Boolean is <>; with function Construct (Params : not null access Parameters_Type) return Root_Type is abstract; package Generic_Factory is Type_Error : exception; Selector_Error : exception; type Factory_Type is tagged private; procedure Register (Factory : in out Factory_Type; Selector : in Selector_Type; Type_Tag : in Ada.Tags.Tag); function Is_Creatable (Factory : in Factory_Type; Type_Tag : in Ada.Tags.Tag) return Boolean; function Is_Registered (Factory : in Factory_Type; Selector : in Selector_Type) return Boolean; procedure Deregister (Factory : in out Factory_Type; Selector : in Selector_Type); function Produce (Factory : in Factory_Type; Selector : in Selector_Type; Params_Ptr : not null access Parameters_Type) return Root_Type'Class; function Create (Factory : in Factory_Type; Type_Tag : in Ada.Tags.Tag; Params_Ptr : not null access Parameters_Type) return Root_Type'Class; private package Selector_Maps is new Ada.Containers.Indefinite_Ordered_Maps (Selector_Type, Ada.Tags.Tag, "<", Ada.Tags."="); type Factory_Type is new Ada.Finalization.Controlled with record Map : Selector_Maps.Map; end record; end Generic_Factory; package body Generic_Factory is function Dispatching_Constructor is new Ada.Tags.Generic_Dispatching_Constructor (Root_Type, Parameters_Type, Construct); procedure Register (Factory : in out Factory_Type; Selector : in Selector_Type; Type_Tag : in Ada.Tags.Tag) is begin if (not Ada.Tags.Is_Descendant_At_Same_Level (Type_Tag, Root_Type'Tag)) then raise Type_Error; end if; if (Factory.Map.Contains (Selector)) then raise Selector_Error; end if; Factory.Map.Insert (Selector, Type_Tag); end Register; function Is_Creatable (Factory : in Factory_Type; Type_Tag : in Ada.Tags.Tag) return Boolean is begin if (Ada.Tags.Is_Descendant_At_Same_Level (Type_Tag, Root_Type'Tag)) then return True; else return False; end if; end Is_Creatable; function Is_Registered (Factory : in Factory_Type; Selector : in Selector_Type) return Boolean is begin return Factory.Map.Contains (Selector); end Is_Registered; procedure Deregister (Factory : in out Factory_Type; Selector : in Selector_Type) is begin if (Factory.Map.Contains (Selector)) then Factory.Map.Delete (Selector); end if; end Deregister; function Produce (Factory : in Factory_Type; Selector : in Selector_Type; Params_Ptr : not null access Parameters_Type) return Root_Type'Class is begin if (Factory.Map.Contains (Selector)) then return Dispatching_Constructor (Factory.Map.Element (Selector), Params_Ptr); else raise Selector_Error; end if; end Produce; function Create (Factory : in Factory_Type; Type_Tag : in Ada.Tags.Tag; Params_Ptr : not null access Parameters_Type) return Root_Type'Class is begin if (Ada.Tags.Is_Descendant_At_Same_Level (Type_Tag, Root_Type'Tag)) then return Dispatching_Constructor (Type_Tag, Params_Ptr); else raise Type_Error; end if; end Create; end Generic_Factory; Unfortunately the use of Is_Descendant_At_Same_Level in Register, Is_Creatable and Create does not cover all issues. Consider: type Params is null record; type Root is tagged null record; function Construct (Params_Ptr : not null access Params) return Root; type Abstract_Derived is abstract new Root with null record; function Construct (Params_Ptr : not null access Params) return Abstract_Dervied; package Test_Factory is new Generic_Factory (Root, Integer, Params, Standard."<", Construct); Dummy: aliased Params; F : Test_Factory.Factory_Type; F.Register (1, Abstract_Derived'Tag); -- Allowed; F.Is_Creatable (Abstract_Derived'Tag); -- returns True; F.Produce(1, Dummy'access); -- Tag_Error raised by Generic_Dispatching_Constructor. F.Create (Abstract_Derived'Tag, Dummy'access); -- Tag_Error raised by Generic_Dispatching_Constructor. Attempting to use an abstract type in the way shown in the code above is obviously wrong and the program is in error, but It would seem to me that it would be better to trap the error at the earliest opportunity, hence the attempts at checks in Register and Is_Creatable. By allowing the registration of the type, a latent failure is introduced that could be encountered at less well defined times (compared to registration) in program execution. I would think that the Generic_Factory package, or something like it, is likely to find itself implemented many times by different people, especially as the Rationale seems to suggest that this is the correct type of solution to certain classes of problems. **************************************************************** From: Adam Beneschan Sent: Tuesday, August 11, 2009 12:09 PM > function Is_Abstract (T: in Ada.Tags.Tag) return Boolean; > > Is_Abstract has the obvious sematics: returns True if the type > represented by T is abstract, False otherwise. Seems simple enough to me. Since no object can have a 'Tag whose type is abstract, in practice the only way you'd get such a tag would be from a call to Internal_Tag or Descendant_Tag (which take string parameters), but I can envision how that might be possible (due to an input error, perhaps) and how it might be desirable to provide a way to avoid an exception. Or by explicitly taking the 'Tag of an abstract type, as you did in your example, but this is a clear (and silly) programming error. If that were the only motivation for adding this check, it probably wouldn't be worth changing the language (even a seemingly simple change like this, which might not be all that simple for some implementations). But to me, the possibility of errors in tag strings in input files is a better motivation. > What is necessary to determine whether the type's master is > compatible, I leave to others to determine. Me too. :-) > Also, is there any particular reason why the root type T is explicitly > exluded from the types allowed to be created by > Generic_Dispatching_Constructor? It isn't, at least as far as I can tell. Where does it appear to you that T is explicitly excluded? (If you're confused by the term "descendant", note that T is a descendant of T: see 3.4.1(10). If there's something else in 3.9 that says that the root type is excluded, I sure can't find it.) > As I read AARM 12.5.1 (17) the actual > type corresponding to a formal abstract tagged type 'can' (I read - > 'may, but doesn't have to') be abstract? Constructor is an abstract > subprogram and thus always dispatches to a concrete type (in this case > through the 'magic' of Generic_Dispatching_Construtor, which raises > Tag_Error is an invalid tag is specified). Excluding T seems to force > users in some circumstances to create a root type below that strictly > needed to model the problem, just to allow use with this facility. > > Furthermore, without having thought about it enough to come up with > specific motivating examples, it would seem to me that there may be > benefits to providing additional interrogative functions (based on the > definition of a tagged type in 3.9 (2/2)), e.g.: > > function Is_Private (T: in Ada.Tags.Tag) return Boolean; --??? > function Is_Limited (T: in Ada.Tags.Tag) return Boolean; function > Is_Interface (T: in Ada.Tags.Tag) return Boolean; function > Is_Task_Interface (T: in Ada.Tags.Tag) return Boolean; function > Is_Protected_Interface (T: in Ada.Tags.Tag) return Boolean; function > Is_Synchronized_Interface (T: in Ada.Tags.Tag) return Boolean; > function Is_Protected_Derived_From_Interface (T: in Ada.Tags.Tag) > return Boolean; function Is_Task_Derived_From_Interface (T: in > Ada.Tags.Tag) return Boolean; ... again, with the 'obvious' semantics. I'd definitely be opposed to Is_Private. Whether a type is private or not (more accurately, whether the full or partial view of a type is visible) is more a property of where you are in the program in relation to the type's declaration, rather than a property of the type itself. I cannot see any legitimate use of a function like this. I can't see any legitimate use for Is_Limited, either; whether a type is limited or not *statically* affects what operations can be performed on objects of the type (or its associated class-wide type), particularly assignment, but I don't think there's any case where this is checked at runtime. The only case where it could possibly matter is if you have a limited interface type Lim_Int; descendants of that type are nonlimited. But supposing you had an object X of type Lim_Int'Class, and you could determine from X'Tag that the concrete type is nonlimited. What would that gain you? You still wouldn't be able to do an assignment, or do anything else that you could do with nonlimited types. For Is_Interface: if Is_Abstract is implemented, I don't see any particular use for Is_Interface. If a Tag is the tag of an abstract type, you can't use it for much; in fact, it probably indicates an input error of some sort (like a bad name passed to Descendant_Tag). Given that, I don't see any advantage in distinguishing useless tags of interface types from useless tags of non-interface abstract types. I don't know much about task/protected interfaces so I won't say anything about the other suggested functions. ****************************************************************