Initial support for Custom Attributes

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Initial support for Custom Attributes

Free Pascal - General mailing list
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
_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Initial support for Custom Attributes

Dennis


Sven Barth via fpc-pascal wrote:
> 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.
>
Thanks for the contribution.

Dennis
_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Initial support for Custom Attributes

dmitry boyarintsev
In reply to this post by Free Pascal - General mailing list
On Fri, Jul 12, 2019 at 6:10 PM Sven Barth via fpc-pascal <[hidden email]> wrote:
The wiki pages New Features Trunk and User Changes Trunk will be updated
soon with the new information.

Here's a stand-alone page too

 

_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Initial support for Custom Attributes

Graeme Geldenhuys-6
In reply to this post by Free Pascal - General mailing list
On 12/07/2019 11:09 pm, Sven Barth via fpc-pascal wrote:
> 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.


Fantastic to hear that news! Well done to everybody involved in bringing
this to FPC.


Regards,
  Graeme

--
fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal
http://fpgui.sourceforge.net/

My public PGP key:  http://tinyurl.com/graeme-pgp
_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Initial support for Custom Attributes

Graeme Geldenhuys-6
In reply to this post by Free Pascal - General mailing list
On 12/07/2019 11:09 pm, Sven Barth via fpc-pascal wrote:

> 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;


Nothing to do with the actual implementation of Custom Attributes, but
rather about using it.

I know attributes are TCustomAttribute descendants, but for the sake of
code readability, my personal opinion would be to NOT name custom
attributes with the 'T' prefix like is normally done in Object Pascal.

A visual comparison. So imagine a RESTful class could be decorated as
follows:


[Path('/troopers')]
[Produces('application/json')]
TStormtrooperResource = class(TObject)
  published
    [Path('/{id}')]
    [GET]
    function GetStormtrooper([PathParam('id')] id: string): TStormtrooper;
    [POST]
    function CreateTrooper(trooper: TStormTrooper): TStormtrooper;
end;


... instead of the traditional Object Pascal naming conversion....


[TPath('/troopers')]
[TProduces('application/json')]
TStormtrooperResource = class(TObject)
  published
    [TPath('/{id}')]
    [TGET]
    function GetStormtrooper([TPathParam('id')] id: string): TStormtrooper;
    [TPOST]
    function CreateTrooper(trooper: TStormTrooper): TStormtrooper;
end;


Just my observation..... Your mileage may vary. ;-)


Regards,
  Graeme

--
fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal
http://fpgui.sourceforge.net/

My public PGP key:  http://tinyurl.com/graeme-pgp
_______________________________________________
fpc-pascal maillist  -  [hidden email]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal