Issue 240507.1: Add support for "properties"

Author: Martin Friebe
Champion: Adrian Prantl
Date submitted: 2024-05-07
Date revised:
Date closed:
Type: Enhancement
Status: Open
DWARF Version: 6

Background

Pascal has a property construct, that allows a "variable like" identifier, which can either point to a field (member variable) or a getter/setter function.

TFoo = class
  FField: integer;
  function GetProp: integer;
  procedure SetProp(AVal: Integer);
  property MyProp: integer read GetProp write SetProp;
  property MyOtherProp: integer read FField;
end;

Properties can exist in a structure, interface, or on a global level.

Properties can be read/write, read only, write only.

Properties can have one or more array like indexes (of any type).

function GetProp(AIdx:word; AIdx2: boolean): integer;
procedure SetProp(AIdx:word; AIdx2: boolean; AVal: Integer);
property MyProp[AIdx:word; AIdx2: boolean]: integer read GetProp write SetProp;

Properties can share a method, and provide an index (constant) to identify which property called the method.

function GetProp(AIndex: Integer): integer;
procedure SetProp(AIndex: Integer; AValue: integer);
property MyProp1: integer index 1 read GetProp write SetProp;
property MyProp2: integer index 2 read GetProp write SetProp;

Properties can have a "default" flag, indicating that the array [] access can omit the property name. I.e. accessing "Object[n]" is a shortcut for the default property. (default for non-array properties is being considered for future addition)

Properties can have "default" and "stored" values for streaming (constant or via function).

Properties can be elevated to a higher visibility (private/public) in inherited classes.

There may be partial overlaps with properties in Objective-C and C#.

Proposed Changes

DW_TAG_Property or DW_TAG_Property_Pascal

This tag can occur anywhere where DW_TAG_MEMBER can occur. It can also occur on a global scope.

It supports (at least) the following existing attributes:

It will support the following new attribute:

It will support the following new tags as children:

DW_TAG_Property_[Setter|Getter|...]

Specifies how the property is accessed for read/write and other access.

Reference to a field/var/function.

The referenced element must have the same DW_AT_Type as the property. Except for "stored" which should be boolean.

function parameters

For shared/indexed properties the order of parameters should default to

For the shared getter/setter index the value of the argument must be specified. For this the list of arguments to a getter/setter can be specified.

For the position of the value in the parameter list

The values for _this, array-index and value (if not specified) are passed to the function remaining parameters.

Other attributes for getter/setter/default/stored

The following attributes should be allowed in DW_TAG_Property_[Setter|Getter|...] to overwrite value given in the DW_TAG_Property

Other usage forms of DW_TAG_Property

To change accessibility (private/public) of a property in an inherited class, DW_TAG_Property will be specified with just a name and accessibility.

DW_TAG_PROPERTY
  DW_AT_NAME
  DW_AT_ACCESSIBILITY

In more generic terms, a property that has no getter or setter, and is not "abstract" is modifying an inherited property. Either accessibility, or addifng "default" or "stored". (This could alternatively be done by specifing a new attribute DW_AT_Property_Inherhit)

Possible compatibility to existing AT_APPLE_property extension

If a property getter/setter (but not stored/default) only needs DW_AT_Property_Forward then instead of having each of them in a DW_TAG_Property_[Setter|Getter] there could be attributes in DW_TAG_Property: DW_AT_Property_Forward_Setter and DW_AT_Property_Forward_Getter

Examples

Property with different visibility

type
  TFoo = class
  private
    MyMember: integer;
    function MyFunc: integer;
  protected
    // "read public" is currently from oxygene, but may be added to FreePascal
    property MyProp: integer read public MyFunc write MyMember; default;
  end;

  TBar = class(TFoo)
  public
    property MyProp; // elevate to public
  end;

  DW_TAG_Structure_type
      DW_AT_Name :  "TFoo"
L1:
    DW_TAG_Member
        DW_AT_Name            :  "MyMember"
        DW_AT_Type            :  <...>
        DW_AT_Member_location :  <...>
L2:
    DW_TAG_subprogram
        DW_AT_Name            :  "MyFunc"
        DW_AT_Type            :  <...>

    DW_TAG_Property
        DW_AT_Name             :  "MyProp"
        DW_AT_Type             :  <...>
        DW_AT_Accessibility    :  DW_ACCESS_protected
        DW_AT_Default_Property :  TRUE
      DW_TAG_Property_Getter
          DW_AT_Property_Forward :  reference to L2
          DW_AT_Accessibility    :  DW_ACCESS_public
      DW_TAG_Property_Setter
          DW_AT_Property_Forward :  reference to L1

  DW_TAG_Structure_type
      DW_AT_Name :  "TBar"
    DW_TAG_Inheritance
      <...>
    DW_TAG_Property
        DW_AT_Name             :  "MyProp"
        DW_AT_Accessibility    :  DW_ACCESS_public

Property with access to nested field

type
  TFoo = class
    OtherData: DWORD;
    FNested: record
      MyMember: integer;
    end;
    property MyProp: integer read FNested.MyMember;
  end;

L1:
   DW_TAG_Structure_type
L2:
     DW_TAG_Member
       DW_AT_Name            :  "MyMember"
       DW_AT_Type            :  <...>
       DW_AT_Member_location :  <...>    ! inside FNested

   DW_TAG_Structure_type
       DW_AT_Name :  "TFoo"
     DW_TAG_Member
       DW_AT_Name :  "OtherData"
     DW_TAG_Member
       DW_AT_Name :  "FNested"
       DW_AT_Type            :  reference to L1
       DW_AT_Member_location :
         DW_OP_plus_uconst 4        ! where 4 == offset of MyMember in the instance data
     DW_TAG_Property
       DW_AT_Name             :  "MyProp"
       DW_AT_Type             :  <...>
       DW_TAG_Property_Getter
         DW_AT_Property_Forward :  reference to L2
         DW_AT_Property_Object  :
           DW_OP_push_object_address  ! maybe should be on stack by default
           DW_OP_plus_uconst 4        ! where 4 == offset of MyMember in the instance data
                                      ! There could be several levels of nesting,
                                      ! so that expression could be more complex

In the example the property does not have a reference to FNested itself. All it needs is the object_address of FNested, so it can calculate the location of the referenced field MyMember (using the member_location).

References