Issue 240507.1: Add support for "properties"

Author: Martin Friebe
Champion: Adrian Prantl
Date submitted: 2024-05-07
Date revised: 2024-11-25
Date closed: 2025-03-31
Type: Enhancement
Status: Accepted
DWARF version: 6

Comparing original with 2024-11-25. [ View this version ] [ Return to the latest version ]

## 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 exist in a structure, interface, or on a global level.

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

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

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

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

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

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

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

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

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


### References

- [FreePascal property doc][freepascal]
- [Oxygene][oxygene]
- [Apple extension for Objective-C][objc]

[freepascal]: https://www.freepascal.org/docs-html/ref/refse27.html
[oxygene]: https://docs.elementscompiler.com/Oxygene/Members/Properties/
[objc]: https://github.com/llvm/llvm-project/blob/main/llvm/docs/SourceLevelDebugging.rst#debugging-information-format-1

## Proposed Changes

### `DW_TAG_Property` or `DW_TAG_Property_Pascal`
### Section 2.1: The Debugging Information Entry

This tag can occur anywhere where `DW_TAG_MEMBER` can occur. It can also
occur on a `global scope`.
In Table 2.1, add `DW_TAG_property`, `DW_TAG_property_getter`,
`DW_TAG_property_setter`, and `DW_TAG_property_stored`.

It supports (at least) the following existing attributes:
### Section 5.7: Structure, Union and Class Type Entries

Add the following new subsection after Section 5.7.6 Data Member Entries:
* `DW_AT_Name`
* `DW_AT_Type`
* `DW_AT_Accessibility`
* `DW_AT_external`
* `DW_AT_virtuality`
* `DW_AT_start_scope`
* `DW_AT_decl_column`, `DW_AT_decl_file` and `DW_AT_decl_line`.
* ... others from the var/member tags, except for actual value or
location of value

> 5.7.x Property Entries
>
> [Non-normative] Many object-oriented languages like Pascal and
> Objective-C have properties, which are member functions that
> syntactically behave like data members of an object. Pascal can also
> have global properties.
>
> A property is represented by a debugging information entry with the
> tag `DW_TAG_property`. A property entry has a `DW_AT_name` string
> attribute whose value is the property name. A property entry may have
> a `DW_AT_type` attribute to denote the type of that property.
>
> A property may have `DW_AT_accessibility`, `DW_AT_external`,
> `DW_AT_virtuality`, `DW_AT_start_scope`, `DW_AT_decl_column`,
> `DW_AT_decl_file` and `DW_AT_decl_line` attributes with the respective
> semantics described for these attributes for `DW_TAG_member` (see Section 5.7.6).
>
> A property may have one or several of `DW_TAG_property_getter`,
> `DW_TAG_property_setter`, or `DW_TAG_property_stored` children to
> represent the getter and setter (member) functions, or the Pascal-style
> `stored` accessor for this property. Each of these tags have a
> `DW_AT_property_forward` attribute to point to a (member) function
> declaration or a data member. If they point to a function, they may also
> have `DW_TAG_formal_parameter` children (matching the ones in the
> function) that can have `DW_AT_default_value` attributes to declare
> additional default arguments for when these functions are used as
> property accessors.
>
> Some languages can automatically derive accessors for properties from a
> field in property's parent object. In such cases the
> `DW_AT_property_forward` attribute of the accessor entry points to the
> `DW_TAG_property`'s sibling `DW_TAG_member` entry of field that holds
> the properties underlying storage.
>
> Property accessors may also have any other attributes allowed in a
> `DW_TAG_subprogram` function declaration. If the value of a property can
> be derived by evaluating a DWARF expression, the
> `DW_TAG_property_getter` may have a `DW_TAG_location` holding a DWARF
> expression that may use `DW_OP_push_object_address` to inquire the
> address of the property's parent object.
>
> To change the accessibility of a property in an inherited class, a
> `DW_TAG_property` can be specified with just a name and accessibility.
> For example if in a subclass property a becomes private it is sufficient
> to add the following to the subclass entry:
>
>     DW_TAG_property
>       DW_AT_name            "a"
>       DW_AT_accessibility   DW_ACCESS_private
It will support the following new attribute:

* `DW_AT_Default_Property` flag  
   Specify this is a default property

It will support the following new tags as children:

* `DW_TAG_Property_Setter`
* `DW_TAG_Property_Reader`
* `DW_TAG_Property_Default`
* `DW_TAG_Property_Stored`

### `DW_TAG_Property_[Setter|Getter|...]`

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

#### Reference to a field/var/function.

* `DW_AT_Property_Forward`  reference/constant  
   A reference to an existing the field or function.
    * This could be a constant for the reader/default/stored accessors.
    * This could be a string to support the current `AT_APPLE_property_setter` extension

* `DW_AT_Property_Object`  reference/expression/constant  
   The object on which the value is stored (value for
`DW_OP_push_object_address`). This can be omitted for inherited classes,
if it computes to the same address as the current class.

* `DW_TAG_Member`, `DW_TAG_Variable`, `DW_TAG_subprogram`.
   For inlining the accessor. E.g. subprogram for C#

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

#### function parameters

* A getter should:
    * take one parameter, which is the `_this` value of the object. For
global properties no parameters are given
* A setter should
    * take `_this` as the first parameter (except for global properties)
    * the `value` as the 2nd parameter.

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

* `_this`
* indexes from array-like access
* the `value` (setter only)

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.

* `DW_TAG_Property_Argument_List`
* containing a list of `DW_TAG_Property_Argument`
* each having the following attributes.
    * `DW_AT_Property_Argument_Number`
      zero-based number of the function parameter for which a value is given
    * `DW_AT_Property_Argument_Value`  reference or constant or expression
 (returning value)
      The value that should be passed

For the position of the `value` in the parameter list

* `DW_AT_Property_Value_Argument_Number` const/expression

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
### Appendix D: Examples

Add a new subsection:
The following attributes should be allowed in
`DW_TAG_Property_[Setter|Getter|...]` to overwrite value given in the
`DW_TAG_Property`

* `DW_AT_Accessibility`
* `DW_AT_external`
* `DW_AT_virtuality`
* `DW_AT_start_scope`
> D.x Properties
>
> The properties in the Pascal object in this example are represented by
> the following DWARF.
>
>     TClass = class
>          FField: integer;
>          function GetProp: integer;
>          procedure SetProp(AVal: Integer);
>          property MyProp: integer read GetProp write SetProp;
>          property MyOtherProp: integer read FField;
>          function GetFoo(x: word; AIndex: Integer): char;
>          property Foo[x: word]: char index 1 read GetFoo;
>     end;
>
>
>     DW_TAG_class_type
>       DW_AT_name "TClass"
>       DW_TAG_member
>         DW_AT_name "FField"
>         DW_AT_type <ref to integer>
>       DW_TAG_subprogram
>         DW_AT_name "GetProp"
>         ...
>       DW_TAG_subprogram
>         DW_AT_name "SetProp"
>         ...
>
>       DW_TAG_property
>         DW_AT_name "MyProp"
>         DW_TAG_property_getter
>           DW_AT_property_forward <ref to GetProp>
>         DW_TAG_property_setter
>           DW_AT_property_forward <ref to SetProp>
>
>       DW_TAG_property
>         DW_AT_name "MyOtherProp"
>         DW_TAG_property_getter
>           DW_AT_property_forward <ref to FField>
>
>       DW_TAG_subprogram
>         DW_AT_name "GetFoo"
>         ...
>
>       DW_TAG_property
>         DW_AT_name "Foo"
>
>         DW_TAG_property_getter
>           DW_AT_property_forward <ref to GetFoo>
>
>           DW_TAG_formal_parameter  ; _this (no default specified, details inherited from GetFoo
>           DW_TAG_formal_parameter  ; x (no default specified)
>           DW_TAG_formal_parameter
>             DW_AT_default_value <DW_OP_lit 1> ; property index

### Other usage forms of `DW_TAG_Property`
---

2024-10-12: Revised after online discussion.
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

- [FreePascal property doc][1]
[1]: https://www.freepascal.org/docs-html/ref/refse27.html

- [Oxygene][2]
[2]: https://docs.elementscompiler.com/Oxygene/Members/Properties/

- [APPLE extension for Objective-C][3]
[3]: https://github.com/llvm/llvm-project/blob/main/llvm/docs/SourceLevelDebugging.rst#debugging-information-format-1