Recent

Author Topic: Custom attributes on properties and methods: how?  (Read 1916 times)

Ștefan-Iulian Alecu

  • New Member
  • *
  • Posts: 30
Custom attributes on properties and methods: how?
« on: August 02, 2025, 02:10:28 pm »
Hello! I have been interested in FPC trunk as of late for a project I am doing (Version=3.3.1-18324-g831e6c6d75-dirty, Windows 11 64 bit, although I don't believe the hash is as important here). I was experimenting with custom attributes, hoping they'd be at least somewhat usable. So I made this example:

Code: Pascal  [Select][+][-]
  1. program AttributeDateDemo;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeswitch prefixedattributes}
  5.  
  6. uses
  7.   SysUtils, Rtti;
  8.  
  9. type
  10.   DateTimeAttribute = class(TCustomAttribute)
  11.   private
  12.     FValue: TDateTime;
  13.   public
  14.     constructor Create(const RawDate: String);
  15.     property Value: TDateTime read FValue;
  16.   end;
  17.  
  18. constructor DateTimeAttribute.Create(const RawDate: String);
  19. var
  20.   TempDate: TDateTime;
  21. begin
  22.   ShortDateFormat := 'yyyy/mm/dd';
  23.   DateSeparator := '/';
  24.   if not TryStrToDate(RawDate, TempDate) then
  25.     raise Exception.CreateFmt('Invalid date: "%s"', [RawDate]);
  26.   FValue := TempDate;
  27. end;
  28.  
  29. type
  30.   [DateTimeAttribute({$I %DATE%})]
  31.   TFoo = class
  32.   private
  33.     FProp: String;
  34.   public
  35.     [DateTime({$I %DATE%})]
  36.     procedure DoSomething;
  37.  
  38.     [DateTime({$I %DATE%})]
  39.     property MyProp: String read FProp write FProp;
  40.   end;
  41.  
  42. procedure TFoo.DoSomething;
  43. begin
  44.   Writeln('Doing something...');
  45. end;
  46.  
  47. procedure PrintAttributes(AType: TRttiType);
  48. var
  49.   Attr: TCustomAttribute;
  50.   Meth: TRttiMethod;
  51.   Prop: TRttiProperty;
  52. begin
  53.   Writeln('Class attributes:');
  54.   for Attr in AType.GetAttributes do
  55.     if Attr is DateTimeAttribute then
  56.       Writeln('  ', FormatDateTime('dd.mm.yyyy', DateTimeAttribute(Attr).Value));
  57.  
  58.   Writeln('Method attributes:');
  59.   for Meth in AType.GetMethods do
  60.     if Meth.Name = 'DoSomething' then
  61.       for Attr in Meth.GetAttributes do
  62.         if Attr is DateTimeAttribute then
  63.           Writeln('  ', FormatDateTime('dd.mm.yyyy', DateTimeAttribute(Attr).Value));
  64.  
  65.   Writeln('Property attributes:');
  66.   for Prop in AType.GetProperties do
  67.     if Prop.Name = 'MyProp' then
  68.       for Attr in Prop.GetAttributes do
  69.         if Attr is DateTimeAttribute then
  70.           Writeln('  ', FormatDateTime('dd.mm.yyyy', DateTimeAttribute(Attr).Value));
  71. end;
  72.  
  73. var
  74.   Ctx: TRttiContext;
  75.   Typ: TRttiType;
  76. begin
  77.   Typ := Ctx.GetType(TFoo);
  78.   PrintAttributes(Typ);
  79.   Readln;
  80. end.
  81.  
Ignoring ShortDateFormat and DateSeparator there, which aren't the main point, I was expecting to see the same date on all attributes, however I got this instead:
Code: Text  [Select][+][-]
  1. Class attributes:
  2.   02.08.2025
  3. Method attributes:
  4. Property attributes:
  5.  
which caught me by surprise. Am I doing something wrong in the PrintAttributes procedure?

P.S. Is there a way to have attributes on parameters too? An example would be:
Code: Pascal  [Select][+][-]
  1. TMyClass = class
  2.   class procedure MyMethod([NotNull] Address: Pointer); static;
  3. end;
  4.  
which is at least possible in Delphi, after someone showed me how they would access it. My use case would be to have [NonNull] and [NonEmpty] attributes. I don't see a mention of them on the Free Pascal wiki page on custom attributes, and I have reasons to believe that's not in testing yet. Maybe it's a missing feature currently? :)

If you consider this topic to not be appropriate for this board, feel free to move it. Thanks for your attention!

PascalDragon

  • Hero Member
  • *****
  • Posts: 6396
  • Compiler Developer
Re: Custom attributes on properties and methods: how?
« Reply #1 on: August 03, 2025, 02:05:42 pm »
Hello! I have been interested in FPC trunk as of late for a project I am doing (Version=3.3.1-18324-g831e6c6d75-dirty, Windows 11 64 bit, although I don't believe the hash is as important here). I was experimenting with custom attributes, hoping they'd be at least somewhat usable.

The visibilities for custom attributes for fields, methods and properties are still set to None, so you need to manually enable them by adding {$RTTI EXPLICIT PROPERTIES([vcPublic])} after the uses-clause.

P.S. Is there a way to have attributes on parameters too? An example would be:
Code: Pascal  [Select][+][-]
  1. TMyClass = class
  2.   class procedure MyMethod([NotNull] Address: Pointer); static;
  3. end;
  4.  
which is at least possible in Delphi, after someone showed me how they would access it. My use case would be to have [NonNull] and [NonEmpty] attributes. I don't see a mention of them on the Free Pascal wiki page on custom attributes, and I have reasons to believe that's not in testing yet. Maybe it's a missing feature currently? :)

These are currently not supported.

Ștefan-Iulian Alecu

  • New Member
  • *
  • Posts: 30
Re: Custom attributes on properties and methods: how?
« Reply #2 on: August 03, 2025, 03:29:27 pm »
Hello! I have been interested in FPC trunk as of late for a project I am doing (Version=3.3.1-18324-g831e6c6d75-dirty, Windows 11 64 bit, although I don't believe the hash is as important here). I was experimenting with custom attributes, hoping they'd be at least somewhat usable.

The visibilities for custom attributes for fields, methods and properties are still set to None, so you need to manually enable them by adding {$RTTI EXPLICIT PROPERTIES([vcPublic])} after the uses-clause.

Thank you so much. Perhaps this should be documented in the wiki page, at least so that people know it is the same syntax as Delphi.

These are currently not supported.

Oh well. Is there a way to at least "fake" them? Perhaps something like
Code: Pascal  [Select][+][-]
  1. type
  2.   TMyClass = class
  3.     [NotNull('Address')]
  4.     class procedure MyMethod(Address: Pointer); static;
  5.   end;
  6.  
but it's uncertain to me how I could wire this up so it works. I tried this:
Code: Pascal  [Select][+][-]
  1. program AttributeNotNullDemo;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeswitch prefixedattributes}
  5.  
  6. uses
  7.   SysUtils,
  8.   Rtti; {$RTTI EXPLICIT METHODS([vcPublic])}
  9.  
  10. type
  11.   NotNullAttribute = class(TCustomAttribute)
  12.   private
  13.     FParamName: String;
  14.   public
  15.     constructor Create(const AParamName: String);
  16.     property ParamName: String read FParamName;
  17.   end;
  18.  
  19. type
  20.   TMyClass = class
  21.     [NotNull('Address')]
  22.     class procedure MyMethod(Foo: Pointer); static;
  23.   end;
  24.  
  25.   { NotNullAttribute }
  26.  
  27. constructor NotNullAttribute.Create(const AParamName: String);
  28. begin
  29.   FParamName := AParamName;
  30. end;
  31.  
  32. class procedure TMyClass.MyMethod(Foo: Pointer);
  33. begin
  34.   if Foo = nil then
  35.     raise Exception.Create('Address must not be nil');
  36.   Writeln('Address OK!');
  37. end;
  38.  
  39. procedure PrintMethodAttributes(AType: TRttiType);
  40. var
  41.   Meth: TRttiMethod;
  42.   Attr: TCustomAttribute;
  43. begin
  44.   for Meth in AType.GetMethods do
  45.     if Meth.Name = 'MyMethod' then
  46.     begin
  47.       Writeln('Attributes on method MyMethod:');
  48.       for Attr in Meth.GetAttributes do
  49.         if Attr is NotNullAttribute then
  50.           Writeln('  NotNull: ', NotNullAttribute(Attr).ParamName);
  51.     end;
  52. end;
  53.  
  54. var
  55.   Ctx: TRttiContext;
  56.   Typ: TRttiType;
  57.   i:   Integer;
  58. begin
  59.   Typ := Ctx.GetType(TMyClass);
  60.   PrintMethodAttributes(Typ);
  61.  
  62.   i := 10;
  63.   try
  64.     TMyClass.MyMethod(@i);
  65.     TMyClass.MyMethod(nil);
  66.   except
  67.     on E: Exception do
  68.       Writeln('Runtime check: ', E.Message);
  69.   end;
  70.  
  71.   Readln;
  72. end.
  73.  
but it totally ignores the name. I am fine with this being a numerical index as well.

Thaddy

  • Hero Member
  • *****
  • Posts: 19245
  • Glad to be alive.
Re: Custom attributes on properties and methods: how?
« Reply #3 on: August 03, 2025, 05:29:32 pm »
Did you compile the class with {$M+} ?
objects are fine constructs. You can even initialize them with constructors.

PascalDragon

  • Hero Member
  • *****
  • Posts: 6396
  • Compiler Developer
Re: Custom attributes on properties and methods: how?
« Reply #4 on: August 05, 2025, 10:16:53 pm »
Hello! I have been interested in FPC trunk as of late for a project I am doing (Version=3.3.1-18324-g831e6c6d75-dirty, Windows 11 64 bit, although I don't believe the hash is as important here). I was experimenting with custom attributes, hoping they'd be at least somewhat usable.

The visibilities for custom attributes for fields, methods and properties are still set to None, so you need to manually enable them by adding {$RTTI EXPLICIT PROPERTIES([vcPublic])} after the uses-clause.

Thank you so much. Perhaps this should be documented in the wiki page, at least so that people know it is the same syntax as Delphi.

If someone thinks that's a useful information for that article they can add it. It's a public wiki after all. It's however not the official documentation. And as main has no upcoming release currently there simply is no documentation for that feature.

These are currently not supported.

Oh well. Is there a way to at least "fake" them?

You can fake them as much as you want, the compiler won't support you there and this will not change. Either attributes for parameter will come or nothing along that line will come, simple as that.

 

TinyPortal © 2005-2018