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
_______________________________________________
fpc-announce maillist  -  fpc-announce@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-announce

Reply via email to