[fpc-announce] Initial support for Custom Attributes

Sven Barth pascaldragon at googlemail.com
Sat Jul 13 00:09:58 CEST 2019


Hello together!

Today FPC has finally gained initial support for Custom Attributes. The 
work had initially been done by Joost van der Sluis almost 6 years ago 
and Svetozar Belic had adjusted the code for trunk. So thank you both 
for that work even if it took quite some time until it was finally 
integrated into trunk.

What are attributes?

Custom Attributes allow you to decorate (currently) type definitions and 
published properties of classes with additional metadata that can be 
queried using the RTTI. What can attributes be used for? You can use 
them to mark classes with the name of its corresponding database table 
or ithe base path for a web service class.

How are attributes declared?

Attributes are simply classes that descend from the new System type 
TCustomAttribute. The important part are the constructors of the class. 
These can be used to pass additional parameters to the attribute (like 
the table name or path).

How are attributes used?

Attributes are bound to a type or property by using one or multiple 
attribute clauses in front of the type or property. For types it must be 
a type definition (e.g. a class, a record, an enum, etc.) or a unique 
type redeclaration (e.g. "TLongInt = type LongInt"). Mere type renames 
(e.g. "TLongInt = LongInt") are not allowed.

Attribute clauses are only available if the new modeswitch 
PREFIXEDATTRIBUTES is set which is the default in mode Delphi and 
DelphiUnicode.

The syntax of a attribute clause is the following:

ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']'
ATTRIBUTELIST::=ATTRIBUTE [, ATTRIBUTELIST ]
ATTRIBUTE::=IDENTIFIER [ ( PARAMLIST ) ]
PARAMLIST::=CONSTEXPR [, PARAMLIST ]

The IDENTIFIER is either the name of the attribute class as is or the 
attribute class' name can end in "Attribute" (casing irrelevant) and 
then the name may be used without the "Attribute" suffix.

Take the following example:

=== code begin ===

program tcustomattr;

{$mode objfpc}{$H+}
{$modeswitch prefixedattributes}

type
   TMyAttribute = class(TCustomAttribute)
     constructor Create;
     constructor Create(aArg: String);
     constructor Create(aArg: TGUID);
     constructor Create(aArg: LongInt);
   end;

   {$M+}
   [TMyAttribute]
   TTestClass = class
   private
     fTest: LongInt;
   published
     [TMyAttribute('Test')]
     property Test: LongInt read fTest;
   end;
   {$M-}

   [TMyAttribute(1234)]
   [TMy('Hello World')]
   TTestEnum = (
     teOne,
     teTwo
   );

   [TMyAttribute(IInterface), TMy(42)]
   TLongInt = type LongInt;

constructor TMyAttribute.Create;
begin
end;

constructor TMyAttribute.Create(aArg: String);
begin
end;

constructor TMyAttribute.Create(aArg: LongInt);
begin
end;

constructor TMyAttribute.Create(aArg: TGUID);
begin
end;

begin

end.

=== code end ===

Querying attributes:

Attributes can be accessed by both the TypInfo and Rtti units.

For the TypInfo unit the ways to access attributes are as follows:

For types:
- use the AttributesTable field in TTypeData
- use GetAttributeTable on a PTypeInfo

- use GetAttribute on the attribute table together with an index to get 
a TCustomAttribute instance

For properties:
- use the AttributesTable of TPropInfo
- use GetAttribute on the attribute table together with a nindex to get 
a TCustomAttribute instance

- use GetPropAttribute on the PPropInfo together with an index to get a 
TCustomAttribute instnace

For the Rtti unit the ways to access attributes are as follows:

For types:
- use GetAttributes on the TRttiType of the type in question

For properties:
- use GetAttributes on the TRttiProperty of the property in question

How is the compatibility of the attributes feature:

The feature itself is Delphi compatible except FPC is much more 
unforgiving regarding unbound properties: if the attribute class is not 
known or the attribute clauses are not bound to a valid type or property 
the compiler will generate an error.

The RTTI however is not considered Delphi compatible, but it covers the 
same functionality. Contrary to Delphi which uses Invoke to create the 
attribute instance FPC uses a constructor function which has the 
advantage that it works on systems that don't have full Invoke support.

Additionally using the PREFIXEDATTRIBUTES modeswitch disables the 
directive clauses for functions, methods and procedure/method types:
The following is not allowed anymore with the modeswitch enabled:

=== code begin ===

procedure Test; [cdecl];
begin
end;

=== code end ===

Just in case: this feature won't be part of 3.2.

The wiki pages New Features Trunk and User Changes Trunk will be updated 
soon with the new information.

Regards,
Sven


More information about the fpc-announce mailing list