[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