[fpc-announce] Feature announcement: Generic type constraints

Sven Barth pascaldragon at googlemail.com
Sun Dec 16 14:45:27 CET 2012


Hello Free Pascal community!

I'm pleased to announce the addition of generic type constraints to Free 
Pascal (beginning from revision 23158).

Overview:

Generic type constraints allow to restrict the set of possible types 
that can be used to specialize a given generic. So for example one can 
restrict a type parameter to only allow descendants of a given class 
type that implement a specific interface.

Syntax:

A type parameter declaration now looks like this: a set of comma 
seperated generic parameter names is either followed by a ";" or a ":". 
If the former than another list of generic type parameters follows. 
Otherwise after the ":" follows a comma seperated list of the following 
constraints:

- "record"
- "class"
- a class identifer
- an interface identifier
[- "constructor" (only allowed in mode Delphi; for the reason see 
"Delphi compatibility")]

After such a constraint list follows either a ">" which means that this 
is the last parameter list or a ";" after which another list of generic 
type parameters follows.

Semantics:

The rules for the above mentioned constraints are as follows:

- "record" can only be used by itself and is mutually exclusive to each 
other constraint; only record types can be used to satisfy this constraint
- "class" can be used together with any other constraint except "record" 
and a class identifier; it defines that the specialization type is a 
class type that's either "TObject" or a descendant of it
- multiple interface identifiers can be used to denote interfaces that 
the given identifier must implement; they are mutually exclusive to 
"record"; giving multiple interfaces implicitly requires the 
specialization parameter to be a class type; if only a single interface 
is given the specialization type can be an interface derived from that 
interface as well
- one class identifier can be given (mutually exclusive to "class" and 
"record") to denote that the specialization parameter must be either of 
the given class type or a descendant of it
[- "constructor" is mutually exclusive to "record" and behaves like 
"class" ]

The order of the constraints is not important.

Examples:

type
   { only class types deriving from TObject can be used }
   generic TExample1<T: class> = class
   end;

   { the same as the above with the exception that this is not valid 
code in Delphi }
   generic TExample2<T: TObject> = class
   end;

   { the parameter must derive from a "TStrings" class; valid types are 
for example "TStrings" or "TStringList" }
   generic TExample3<T: TStrings> = class
   end;

   { valid types are for example "IInterface", "IUnknown", 
"TInterfacedObject" or basically anything that derives from "TComponent" 
as that class implemnts "IInterface" as well }
   generic TExample4<T: IInterface> = class
   end;

   { unlike above interface types ("IInterface", "IUnknown", etc.) are 
not valid here }
   generic TExample5<T: class, IInterface> = class
   end;

   { same as above; the order is not important }
   generic TExample6<T: IInterface, class> = class
   end;

   { a class that implements the two given interfaces though this does 
not need to be the case on the same level; e.g. a class that derives 
from "TInterfacedObject" that implements "ISomeOtherInterface" is valid 
as well }
   generic TExample7<T: IInterface, ISomeOtherInterface> = class
   end;

   { the same as above; the "class" is redundant }
   generic TExample8<T: class, IInterface, ISomeOtherInterface> = class
   end;

   { the type must be a record }
   generic TExample9<T: record> = class
   end;

   { "T1" must be a descendant of "TObject" while "T2" can be of any type }
   generic TExample10<T1: class; T2> = class
   end;

   { the inverse of the above: "T1" can be any type while "T2" needs to 
be a descendant of "TObject" }
   generic TExample11<T1; T2: class> = class
   end;

   { "T1" and "T2" need to be a descendant of "TStrings", "T3" can be of 
any type and "T4" needs to be either a descendant of "IInterface" or a 
descendant of a class that implements "IInterface" }
   generic TExample12<T1, T2: TStrings; T3; T4: IInterface> = class
   end;

Delphi compatibility:

The compiler can correctly handle Delphi compatible type constraints in 
mode Delphi. It does not check though that the constraint ": TObject" is 
not used which is not allowed in Delphi.

Also in mode Delphi the constraint "constructor" is available as well. 
This constraint is not available in other modes because it is handled 
rather strangely in Delphi and in concept behaves like a complicated 
"class" constraint and is very likely a relict of Delphi.Net.

Delphi's behavior is as follows:
If the "constructor" constraint is not given (but e.g. "class" or class 
identifier is) one can NOT call ".Create" on that type (even if the 
constructor would need parameters). What is allowed however is to call a 
constructor that is not named "Create", e.g. "constructor 
CreateFromGeneric".

It would be understandable if the "constructor" constraint would require 
the programmer to have a parameterless constructor "Create" defined in 
the given class type, but the compiler does not care where in the class 
hierarchy of the type the constructor is implemented and thus 
"constructor" behaves the same as "class" with the exception of the 
check for ".Create" which is not implemented in Free Pascal.

Future work:

Adjust generics provided by FPC so that e.g. "fgl.TFPGObjectList" 
requires the type to be a "class".

Currently only the specialization itself is influenced by the 
constraints as the parsing of a generic is - compared to Delphi - very 
lax. In future commits this will be changed and for example the calling 
of methods (like ".Free" or ".Create") will be forbidden if no such 
method is available for the type (e.g. through a class/record/type 
helper declared inside the generic).

This of course might lead to the necessity to extend the capabilites of 
constraints. There are currently no extensions planned, but for example 
one could think of the following additions which would allow specific 
operations on the types (e.g. usage of "Low"):

- "enum": the type must be a enumerationn type
- "ordinal": the type must be one of the ordinal types (Integer, 
LongWord, etc.)
- "array": the type must be an array type
- "string": the type must be one of the string types
- "object": the type must be an "object" type (analogous to "record")
- object identifier: the type must derive from the given object type
- support for Objective Pascal types like objcclass, objcprotcol, etc.
- "operator X": the type must support the operator "X" (available for 
all other constraints)

Conclusion:

I hope generic constraints will be a viable addition to the Free Pascal 
language.

Currently no changes by users are required, but as mentioned in "Future 
work" this is likely to change and thus I'd like to invite you to check 
your own generics whether they'd fullfill stricter rules and if not add 
constraints or request the addition of new constraints (together with 
valid usecases).

Also I'd invite you to test type constraints thoroughly so that errors 
can be eliminated before the next major release of FPC.

Regards,
Sven


More information about the fpc-announce mailing list