Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2019-06

[] property overloads

73 messages · 7 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Ryan Joseph

I just had some plans for a cool JSON class thwarted because apparently [] properties don’t allow overloading of the parameters. Can we allow multiple default properties as long as their parameters are different? I know there’s not overloading of property names but at least the parameters could be overloadable so the correct property is chosen depending on what the args are.

type
  TMyClass = class
    function GetValueWithInt(index: integer): TValue;
    function GetValueWithString(index: string): TValue;
    property IndexI[index: integer]: TValue read GetValueWithInt; default;
    property IndexS[index: string]: TValue read GetValueWithString; default;
  end;
o := c[index]; // IndexI wins
o := c[‘key’]; // IndexS wins

Regards,
Ryan Joseph

Reply by email

Marcus Marques da Rocha
to Ryan Joseph

It is possible to do that using variant type. I have done it as below:

property DocumentElementChildNodes[Index1: Variant] : TPXMLNode read
getChildNode; default;
function TPXMLNode.getChildNode(Index1 : Variant): TPXMLNode;
begin
   Result := nil;
   if assigned(Self) and assigned(FChildNodes) then
     if (varType(Index1) = varString) or (varType(Index1) = varUString) then
     begin
       if (IndexOf(Index1) = -1) then
         if FAutoCreateChildren then
           result := TPXmlNode(self.addchild(Index1))
         else
           result := nil
       else
         Result := TPXMLNode( FChildNodes.objects[IndexOf(Index1)]);
     end
     else
       Result := TPXMLNode(FChildNodes.objects[Index1]);
end;

Regards

Marcus Rocha.

Ryan Joseph wrote 20 lines
I just had some plans for a cool JSON class thwarted because apparently [] properties don’t allow overloading of the parameters. Can we allow multiple default properties as long as their parameters are different? I know there’s not overloading of property names but at least the parameters could be overloadable so the correct property is chosen depending on what the args are. type TMyClass = class function GetValueWithInt(index: integer): TValue; function GetValueWithString(index: string): TValue; property IndexI[index: integer]: TValue read GetValueWithInt; default; property IndexS[index: string]: TValue read GetValueWithString; default; end; o := c[index]; // IndexI wins o := c[‘key’]; // IndexS wins Regards, Ryan Joseph _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply by email

Michael Van Canneyt
to Ryan Joseph: I just had some plans for a cool JSON class thwarted because apparently [] properties…
Ryan Joseph wrote 18 lines
I just had some plans for a cool JSON class thwarted because apparently [] properties don’t allow overloading of the parameters. Can we allow multiple default properties as long as their parameters are different? I know there’s not overloading of property names but at least the parameters could be overloadable so the correct property is chosen depending on what the args are. type TMyClass = class function GetValueWithInt(index: integer): TValue; function GetValueWithString(index: string): TValue; property IndexI[index: integer]: TValue read GetValueWithInt; default; property IndexS[index: string]: TValue read GetValueWithString; default; end; o := c[index]; // IndexI wins o := c[‘key’]; // IndexS wins

IMHO this goes contrary to what the word 'default' means. There can be only 1 default.

That said, allowing overloading of array properties based on index type has been on my wish list since a long time.

So
   property Values[index: integer]: TValue read GetValue;
   property Values[index: string]: TValue read GetValue;
would be a nice addition.

Since the Values array is then uniquely named, 'Default' makes sense again.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
On Jun 20, 2019, at 2:17 PM, Michael Van Canneyt wrote: IMHO this goes contrary to what the word 'default' means. There can be only 1 default.

I think the name of “default” was probably a mistake and it should have been “nameless” or something similar. Not sure why it was chosen but it’s going to be giving us problems.

Show quoted text 8 lines
That said, allowing overloading of array properties based on index type has been on my wish list since a long time. So property Values[index: integer]: TValue read GetValue; property Values[index: string]: TValue read GetValue; would be a nice addition. Since the Values array is then uniquely named, 'Default' makes sense again.

The values array is uniquely named? Looks the same to me. Properties don’t support duplicate identifiers though and the real issue is being able to do c[0] or c[‘key’] without the property name. It feels to me like we’re being held back by the choice of the word default. How can we get around that? Seems like a silly thing to be getting in our way.

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 19 lines
> On Jun 20, 2019, at 2:17 PM, Michael Van Canneyt wrote: > > IMHO this goes contrary to what the word 'default' means. There can be only > 1 default. I think the name of “default” was probably a mistake and it should have been “nameless” or something similar. Not sure why it was chosen but it’s going to be giving us problems. > > That said, allowing overloading of array properties based on index type has been on my wish list since a long time. > > So > property Values[index: integer]: TValue read GetValue; > property Values[index: string]: TValue read GetValue; would be a nice addition. > > Since the Values array is then uniquely named, 'Default' makes sense again. The values array is uniquely named? Looks the same to me.

The point is that there is in my example only 'Values' which is a unique name. Making that 'Default' just means that the 'Values' keyword can be left out. making the 'Values' array the single default.

Properties don’t support duplicate identifiers though and the real issue is being able to do c[0] or c[‘key’] without the property name.

I am aware of what you're trying to do.

It feels to me like we’re being held back by the choice of the word default. How can we get around that? Seems like a silly thing to be getting in our way.

Allowing multiple index types for array properties should solve the matter nicely, even combined with "default". Since there are always getters and setters for such an array property, the normal overloading rules apply.

So you can do
   C.Values[0];
   C.Values['key'];
or
   C[0];
   C['key'] ;

I always felt it silly that I must do

MyStrings.Items[0]
and
MyStrings.Values['Name']

when the compiler could perfectly see what overload to use when I type

MyStrings.items[0]
MyStrings.items['Name']
And, by extension
MyStrings[0]
MyStrings['Name']

Still you must be careful, because what is meant by MyStrings['0']
... is it a type error or is actually the name meant ?

But maybe the compiler people can see a reason why this should not be allowed.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
On Jun 20, 2019, at 3:58 PM, Michael Van Canneyt wrote: The point is that there is in my example only 'Values' which is a unique name. Making that 'Default' just means that the 'Values' keyword can be left out. making the 'Values' array the single default.

Got it. The default is “values”.

Show quoted text 14 lines
I always felt it silly that I must do MyStrings.Items[0] and MyStrings.Values['Name'] when the compiler could perfectly see what overload to use when I type MyStrings.items[0] MyStrings.items['Name'] And, by extension MyStrings[0] MyStrings['Name'] Still you must be careful, because what is meant by MyStrings['0'] ... is it a type error or is actually the name meant ?

It’s basically the same as any other overloaded method so I don’t see where the problem could be.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Sven Barth

Can anyone test this to see if it’s ok? Not sure I got everything correct and I still need some new error messages that make more sense (see the writeln’s I left in)

Even though overloads are allowed the actual name isn’t overloaded so you MUST access without the name to get overloading affects. I didn’t overload the actual name because properties aren’t really functions and as such don’t use tcallcandiates etc… If you attempt to use the name of the default property the last property declared wins. Is that ok? I hope so because I imagine it’s lots of work to actually make property names behave like real functions and not worth it imo.

https://github.com/genericptr/freepascal/tree/array_prop_overload

{$mode objfpc}
{$modeswitch advancedrecords}
program tarrpropol1;
// https://bugs.freepascal.org/view.php?id=29056
type
  TValue = TObject;
function GetGlobalValue(index: integer): TValue;
begin
  result:=nil;
end;
property Values[index: integer]: TValue read GetGlobalValue;
//property Values[index: string]: TValue read GetGlobalValue;
type
  TList = record
    function GetFirst: TValue;
    property First: TValue read GetFirst;
    function GetValueWithInt(index: integer): TValue;
    function GetValueWithString(index: string): TValue;
    function GetValueWithPair(index: integer; key: string): TValue;
    { all default properties must share the same name }
    //property Values0[index: integer]: TValue read GetValueWithInt; default;
    //property Values1[index: string]: TValue read GetValueWithString; default;
    { default properties must have unique parameters }
    //property Values[index: integer]: TValue read GetValueWithInt; default;
    //property Values[index: integer]: TValue read GetValueWithInt; default;
    { parametered properties still can't have duplicate names }
    //property Values[index: integer]: TValue read GetValueWithInt;
    //property Values[index: string]: TValue read GetValueWithString;
    { ok-defaults }
    property Values[index: integer]: TValue read GetValueWithInt; default;
    property Values[index: string]: TValue read GetValueWithString; default;
    property Values[index: integer; key: string]: TValue read GetValueWithPair; default;
  end;
function TList.GetFirst: TValue;
begin
  result:=nil;
end;
function TList.GetValueWithInt(index: integer): TValue;
begin
  writeln('GetValueWithInt');
  result := nil;
end;
function TList.GetValueWithString(index: string): TValue;
begin
  writeln('GetValueWithString');
  result := nil;
end;
function TList.GetValueWithPair(index: integer; key: string): TValue;
begin
  writeln('GetValueWithPair');
  result := nil;
end;
var
  c: TList;
  v: TValue;
begin
  v := c[1,'f'];
end.

Regards,
Ryan Joseph

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Ryan Joseph
On Jun 21, 2019, at 1:59 PM, Ryan Joseph wrote: Even though overloads are allowed the actual name isn’t overloaded so you MUST access without the name to get overloading affects. I didn’t overload the actual name because properties aren’t really functions and as such don’t use tcallcandiates etc… If you attempt to use the name of the default property the last property declared wins. Is that ok? I hope so because I imagine it’s lots of work to actually make property names behave like real functions and not worth it imo.

Thought about this more and it doesn’t make sense. Actually I think the name should overload also so all these should work:

a[1];
a[‘aa’];
a[1, ‘aaa'];
a.values[1];
a.values[‘aa’];
a.values[1, ‘aaa'];

I’ll fix that once I’m sure what I’ve done so far is correct.

Regards,
Ryan Joseph

Reply by email

Ondrej Pokorny
to Ryan Joseph: I just had some plans for a cool JSON class thwarted because apparently [] properties…
Ryan Joseph wrote
I just had some plans for a cool JSON class thwarted because apparently [] properties don’t allow overloading of the parameters. Can we allow multiple default properties as long as their parameters are different?

Properties do allow overloading of the parameters.

You only must not declare the overloaded properties but declare overloaded getters/setters. The compiler will silently accept them.

https://bugs.freepascal.org/view.php?id=28949

Your example:

program Project1;
{$mode objfpc}
type
   TValue = record A: Integer end;
   TMyClass = class
     function GetValue(index: integer): TValue;
     function GetValue(index: string): TValue;
     property Index[aindex: integer]: TValue read GetValue; default;
   end;

{ TMyClass }

function TMyClass.GetValue(index: integer): TValue;
begin
   Result.A := 0;
end;
function TMyClass.GetValue(index: string): TValue;
begin
   Result.A := 1;
end;
var
   c: TMyClass;
begin
   c := TMyClass.Create;
   Writeln(c[0].A);
   Writeln(c['key'].A);
end.

Best
Ondrej

Reply by email

Ryan Joseph
to Ondrej Pokorny: Properties do allow overloading of the parameters. You only must not declare the…
Show quoted text 9 lines
On Jul 1, 2019, at 7:56 AM, Ondrej Pokorny wrote: type TValue = record A: Integer end; TMyClass = class function GetValue(index: integer): TValue; function GetValue(index: string): TValue; property Index[aindex: integer]: TValue read GetValue; default; end;

And btw what was decided about the syntax? I think it was determined it was valid in Delphi but is it going to be allowed in ObjFPC mode? If that’s how Delphi does it I don’t see why they allow the [] to contain any parameters at all since they don’t map to anything.

Regards,
Ryan Joseph

Reply by email

Ondrej Pokorny
to Ryan Joseph
Ryan Joseph wrote 10 lines
> On Jul 1, 2019, at 7:56 AM, Ondrej Pokorny wrote: > > type > TValue = record A: Integer end; > TMyClass = class > function GetValue(index: integer): TValue; > function GetValue(index: string): TValue; > property Index[aindex: integer]: TValue read GetValue; default; > end; And btw what was decided about the syntax? I think it was determined it was valid in Delphi but is it going to be allowed in ObjFPC mode? If that’s how Delphi does it I don’t see why they allow the [] to contain any parameters at all since they don’t map to anything.

I don't know. I just reported it as a bug.

Ondrej

Reply by email

Ondrej Pokorny
to Ryan Joseph: Yes, I’ve made a patch to allow overriding the actual property…
Ryan Joseph wrote
Yes, I’ve made a patch to allow overriding the actual property (https://bugs.freepascal.org/view.php?id=35772).

Very good! Just a short question: does your solution allow one overload without array indexes? It is very useful as a for-in enumerator of the array property:

   TTest = class
   public
     // ...
     property StringArray[Index: Integer]: string read GetString;
     property StringArray: TTestObjectEnumerator read GetString;
   end;
procedure DoTest(a: TTest);
var
   s: string;
begin
   for s in a.StringArray do
     Writeln(s);
end;

See full code:
https://bugs.freepascal.org/file_download.php?file_id=23296&type=bug (File arraypropenum.lpr in https://bugs.freepascal.org/view.php?id=28820

Ondrej

Reply by email

Mattias Gaertner
to Ryan Joseph: And btw what was decided about the syntax? I think it was determined it was valid in…
Show quoted text 13 lines
> On Jul 1, 2019, at 7:56 AM, Ondrej Pokorny > wrote: > > type > TValue = record A: Integer end; > TMyClass = class > function GetValue(index: integer): TValue; > function GetValue(index: string): TValue; > property Index[aindex: integer]: TValue read GetValue; default; > end; And btw what was decided about the syntax? I think it was determined it was valid in Delphi but is it going to be allowed in ObjFPC mode?

Write one thing, mean another? Come on, even Embarcadero can't call that bug a feature.

Mattias

Reply by email

Ryan Joseph
to Ondrej Pokorny: Very good! Just a short question: does your solution allow one overload without array…
Show quoted text 10 lines
On Jul 1, 2019, at 12:08 PM, Ondrej Pokorny wrote: Very good! Just a short question: does your solution allow one overload without array indexes? It is very useful as a for-in enumerator of the array property: TTest = class public // ... property StringArray[Index: Integer]: string read GetString; property StringArray: TTestObjectEnumerator read GetString; end;

No, it’s just for [] properties. Is that allowed in Delphi?

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ondrej Pokorny: Very good! Just a short question: does your solution allow one overload without array…
Ondrej Pokorny wrote 14 lines
On 01.07.2019 15:57, Ryan Joseph wrote: > Yes, I’ve made a patch to allow overriding the actual property (https://bugs.freepascal.org/view.php?id=35772). Very good! Just a short question: does your solution allow one overload without array indexes? It is very useful as a for-in enumerator of the array property:   TTest = class   public     // ...     property StringArray[Index: Integer]: string read GetString;     property StringArray: TTestObjectEnumerator read GetString;   end;

I really don't think this should be allowed. You can perfectly do

property EnumStringArray: TTestObjectEnumerator read GetString;

For S in Mytest.EnumStringArray

Typing this extra letters is not going to hurt.

Your proposal violates the rule that the resulting type of an expression must be known when evaluating it.

with
Mytest.StringArray
it is not clear what the Mytest.StringArray should evaluate to.

It can be 2 things:

- An array, in which case an index is needed.
- An enumerator.

The compiler does not know what to choose, except by looking at the context and that is definitely not OK.

Michael.

Reply by email

Ondrej Pokorny
to Ryan Joseph: No, it’s just for [] properties. Is that allowed in Delphi? Ryan Joseph
Ryan Joseph wrote 11 lines
> On Jul 1, 2019, at 12:08 PM, Ondrej Pokorny wrote: > > Very good! Just a short question: does your solution allow one overload without array indexes? It is very useful as a for-in enumerator of the array property: > > TTest = class > public > // ... > property StringArray[Index: Integer]: string read GetString; > property StringArray: TTestObjectEnumerator read GetString; > end; No, it’s just for [] properties. Is that allowed in Delphi?

No, it is not allowed in Delphi.

Ondrej

Reply by email

Ondrej Pokorny
to Michael Van Canneyt: I really don't think this should be allowed. You can perfectly do property…
Michael Van Canneyt wrote 36 lines
On Mon, 1 Jul 2019, Ondrej Pokorny wrote: > On 01.07.2019 15:57, Ryan Joseph wrote: >> Yes, I’ve made a patch to allow overriding the actual property > (https://bugs.freepascal.org/view.php?id=35772). > > Very good! Just a short question: does your solution allow one > overload without array indexes? It is very useful as a for-in > enumerator of the array property: > >   TTest = class >   public >     // ... >     property StringArray[Index: Integer]: string read GetString; >     property StringArray: TTestObjectEnumerator read GetString; >   end; I really don't think this should be allowed. You can perfectly do property EnumStringArray: TTestObjectEnumerator read GetString; For S in Mytest.EnumStringArray Typing this extra letters is not going to hurt. Your proposal violates the rule that the resulting type of an expression must be known when evaluating it. with   Mytest.StringArray it is not clear what the Mytest.StringArray should evaluate to. It can be 2 things: - An array, in which case an index is needed. - An enumerator.

From the above example "Mytest.StringArray" can only evaluate to TTestObjectEnumerator. Nothing else.

The compiler does not know what to choose, except by looking at the context and that is definitely not OK.

Yes, you are right that there can be some clashes when finding the right overloads - the compiler needs some rules for resolving them. But that has always been like this with overloads...

When the rules are there, the proposal above is not different to any other type of overload.

A problematic example would be e.g.:
     property StringArray[Index: Integer]: string read GetString;
     property StringArray: TStringArray read GetString;
- but again, this can be solved with compiler rules.

Ondrej

Reply by email

Michael Van Canneyt
to Ondrej Pokorny
Ondrej Pokorny wrote 40 lines
On 01.07.2019 18:21, Michael Van Canneyt wrote: > On Mon, 1 Jul 2019, Ondrej Pokorny wrote: > >> On 01.07.2019 15:57, Ryan Joseph wrote: >>> Yes, I’ve made a patch to allow overriding the actual property >> (https://bugs.freepascal.org/view.php?id=35772). >> >> Very good! Just a short question: does your solution allow one >> overload without array indexes? It is very useful as a for-in >> enumerator of the array property: >> >>   TTest = class >>   public >>     // ... >>     property StringArray[Index: Integer]: string read GetString; >>     property StringArray: TTestObjectEnumerator read GetString; >>   end; > > I really don't think this should be allowed. You can perfectly do > > property EnumStringArray: TTestObjectEnumerator read GetString; > > For S in Mytest.EnumStringArray > > Typing this extra letters is not going to hurt. > > Your proposal violates the rule that the resulting type of an > expression must be known when evaluating it. > > with >   Mytest.StringArray > it is not clear what the Mytest.StringArray should evaluate to. > > It can be 2 things: > > - An array, in which case an index is needed. > - An enumerator. From the above example "Mytest.StringArray" can only evaluate to TTestObjectEnumerator. Nothing else.

Well, this is IMHO not correct. The array is also a possibility.

The compiler encounters Mytest.StringArray - at that point he has 2 possibilities. It's an array (and an index must expected) or it is an enumerator (and nothing must be expected), but at this point the compiler cannot decide without looking at the context.

Michael.

Reply by email

Ondrej Pokorny
to Michael Van Canneyt
Michael Van Canneyt wrote 19 lines
On Mon, 1 Jul 2019, Ondrej Pokorny wrote: > On 01.07.2019 18:21, Michael Van Canneyt wrote: >> On Mon, 1 Jul 2019, Ondrej Pokorny wrote: >>> On 01.07.2019 15:57, Ryan Joseph wrote: >>>   TTest = class >>>   public >>>     // ... >>>     property StringArray[Index: Integer]: string read GetString; >>>     property StringArray: TTestObjectEnumerator read GetString; >>>   end; > From the above example "Mytest.StringArray" can only evaluate to > TTestObjectEnumerator. Nothing else. Well, this is IMHO not correct. The array is also a possibility. The compiler encounters Mytest.StringArray - at that point he has 2 possibilities. It's an array (and an index must expected) or it is an enumerator (and nothing must be expected), but at this point the compiler cannot decide without looking at the context.

There is no array. "property StringArray[Index: Integer]" is not an array and cannot be evaluated as an array.

If the compiler encounters "Mytest.StringArray" it can be evaluated only as the non-indexed overload.

Ondrej

Reply by email

Michael Van Canneyt
to Ondrej Pokorny
Ondrej Pokorny wrote 23 lines
On 01.07.2019 19:38, Michael Van Canneyt wrote: > On Mon, 1 Jul 2019, Ondrej Pokorny wrote: >> On 01.07.2019 18:21, Michael Van Canneyt wrote: >>> On Mon, 1 Jul 2019, Ondrej Pokorny wrote: >>>> On 01.07.2019 15:57, Ryan Joseph wrote: >>>>   TTest = class >>>>   public >>>>     // ... >>>>     property StringArray[Index: Integer]: string read GetString; >>>>     property StringArray: TTestObjectEnumerator read GetString; >>>>   end; >> From the above example "Mytest.StringArray" can only evaluate to >> TTestObjectEnumerator. Nothing else. > > Well, this is IMHO not correct. The array is also a possibility. > > The compiler encounters Mytest.StringArray - at that point he has 2 > possibilities. It's an array (and an index must expected) or it is an > enumerator (and nothing must be expected), but at this point the compiler > cannot decide without looking at the context. There is no array. "property StringArray[Index: Integer]" is not an array and cannot be evaluated as an array.

Please, you know what I mean... It is an "array property".

If the compiler encounters "Mytest.StringArray" it can be evaluated only as the non-indexed overload.

Well... We can of course discuss forever, and still not agree. I suggest we let the compiler people have the last word on this. They know best. And if you turn out to be right: so much the better.

Michael.

Reply by email

Ondrej Pokorny
to Michael Van Canneyt
Michael Van Canneyt wrote 6 lines
> If the compiler encounters "Mytest.StringArray" it can be evaluated > only as the non-indexed overload. Well... We can of course discuss forever, and still not agree. I suggest we let the compiler people have the last word on this. They know best. And if you turn out to be right: so much the better.

Yes - either I don't see something obvious here or you :)

But I really don't know a situation where

Mytest.StringArray;

(without the []-brackets) is valid syntax for an array property:

   TTest = class
   public
     property StringArray[Index: Integer]: string read GetString;
   end;

- and if such a code is always invalid there is free space for the non-indexed overload. Or can you think of some valid code where an indexed (array) property can be accessed without the []-brackets?

It's basically the same as method overloads:

   TTest = class
   public
     procedure Test(index: integer);
     procedure Test;
   end;

Only the brackets change from [] to ().

Yes, there is the new situation when the non-indexed overload is an array itself (or is an object with a default property) - but this can be solved by simply saying that the indexed property has precedence before resolving the array of the non-indexed property.

Ondrej

Reply by email

Ondrej Pokorny
to Michael Van Canneyt: Please, you know what I mean... It is an "array property". Well... We can of course…
Michael Van Canneyt wrote 20 lines
On Mon, 1 Jul 2019, Ondrej Pokorny wrote: > On 01.07.2019 19:38, Michael Van Canneyt wrote: >> On Mon, 1 Jul 2019, Ondrej Pokorny wrote: >>> On 01.07.2019 18:21, Michael Van Canneyt wrote: >>>> On Mon, 1 Jul 2019, Ondrej Pokorny wrote: >>>> From the above example "Mytest.StringArray" can only evaluate to >>>> TTestObjectEnumerator. Nothing else. >> >> Well, this is IMHO not correct. The array is also a possibility. >> >> The compiler encounters Mytest.StringArray - at that point he has 2 >> possibilities. It's an array (and an index must expected) or it is an >> enumerator (and nothing must be expected), but at this point the >> compiler >> cannot decide without looking at the context. > > There is no array. "property StringArray[Index: Integer]" is not an > array and cannot be evaluated as an array. Please, you know what I mean... It is an "array property".

Sorry - I didn't mean it bad/ironic/whatsoever. I just thought you meant that:

MyArray := Mytest.StringArray;

might be a valid assignment for

   TTest = class
   public
     property StringArray[Index: Integer]: string read GetString;
   end;
   TMyArray = array of string;

If it was a valid assignment, there would be 2 possibilities indeed and the compiler would need to check the left side to resolve the overload:

MyArray := Mytest.StringArray;
MyEnumerator := Mytest.StringArray;

But such syntax is invalid for indexed (array) properties.

Ondrej

Reply by email

Ondrej Pokorny
to Michael Van Canneyt: Please, you know what I mean... It is an "array property". Well... We can of course…
Michael Van Canneyt wrote 7 lines
On Mon, 1 Jul 2019, Ondrej Pokorny wrote: > If the compiler encounters "Mytest.StringArray" it can be evaluated > only as the non-indexed overload. Well... We can of course discuss forever, and still not agree. I suggest we let the compiler people have the last word on this. They know best. And if you turn out to be right: so much the better.

Just an info: the non-indexed/indexed property overload resolving is currently supported in FPC:
https://bugs.freepascal.org/file_download.php?file_id=23296&type=bug (File arraypropenum.lpr in https://bugs.freepascal.org/view.php?id=28820 )

Yes, currently it's a bug because the overloads are taken from the methods and not from property definitions. But if the overloads were taken from property definitions, it would be a valid feature, IMO. (The bug in FPC is that indexed property information is not stored in the PPU
- only the link "property name"->"getter name"+"setter name" is stored and therefore the information about the valid property definition is lost. The parameters and results are consequently taken from the getter/setter. I.e., the compiler can cope with the non-indexed/indexed property overloads since the very beginning.

This bug allows you to have some serious fun. For example this syntax is allowed (!!!):

   c := TMyClass.Create;
   Writeln(c[]); // c[] returns a Double
   c[] := 'oh my lord'; // c[] accepts a string

A compilable example:

program Project1;
{$mode objfpc}
type
   TValue = record A: Integer end;
   TMyClass = class
   private
     function GetValue: Double;                                   //
result := x[]; syntax (without an index)
     function GetValue(index: integer): TValue;                   //
result := x[999];
     function GetValue(index: string): string;                    //
result := x['abc'];
     procedure SetValue(aindex: integer; const aValue: Double);   //
x[999] := 9.9;
     procedure SetValue(aindex: integer; const aValue: TValue);   //
x[999] := MyValue;
     procedure SetValue(aindex: string; const aValue: string);    //
x['abc'] := 'xyz';
     procedure SetValue(const aValue: string);                    // x[]
:= 'oh god'; syntax (without an index)
   public
     property Index[aindex: integer]: TValue read GetValue write
SetValue; default;
   end;

{ TMyClass }

function TMyClass.GetValue(index: integer): TValue;
begin
   Result.A := index;
end;
function TMyClass.GetValue(index: string): string;
begin
   Result := index+index;
end;
function TMyClass.GetValue: Double;
begin
   Result := Pi;
end;
procedure TMyClass.SetValue(aindex: integer; const aValue: Double);
begin
   Writeln(aindex, ' ', aValue);
end;
procedure TMyClass.SetValue(aindex: integer; const aValue: TValue);
begin
   Writeln(aindex, ' ', aValue.A);
end;
procedure TMyClass.SetValue(const aValue: string);
begin
   Writeln(aValue);
end;
procedure TMyClass.SetValue(aindex: string; const aValue: string);
begin
   Writeln(aindex, ' ', aValue);
end;
var
   c: TMyClass;
begin
   c := TMyClass.Create;
   Writeln('Pi: ', c[]);
   Writeln(c[999].A);
   Writeln(c['key']);
   c['key'] := 'hello';
   c[] := 'oh my lord';
   c.Index := 'property fun';
   ReadLn;
end.

Ondrej

Reply by email

Michael Van Canneyt
to Ondrej Pokorny: Sorry - I didn't mean it bad/ironic/whatsoever. I just thought you meant that: MyArray :=…
Ondrej Pokorny wrote 42 lines
On 01.07.2019 21:04, Michael Van Canneyt wrote: > On Mon, 1 Jul 2019, Ondrej Pokorny wrote: >> On 01.07.2019 19:38, Michael Van Canneyt wrote: >>> On Mon, 1 Jul 2019, Ondrej Pokorny wrote: >>>> On 01.07.2019 18:21, Michael Van Canneyt wrote: >>>>> On Mon, 1 Jul 2019, Ondrej Pokorny wrote: >>>>> From the above example "Mytest.StringArray" can only evaluate to >>>>> TTestObjectEnumerator. Nothing else. >>> >>> Well, this is IMHO not correct. The array is also a possibility. >>> >>> The compiler encounters Mytest.StringArray - at that point he has 2 >>> possibilities. It's an array (and an index must expected) or it is an >>> enumerator (and nothing must be expected), but at this point the >>> compiler >>> cannot decide without looking at the context. >> >> There is no array. "property StringArray[Index: Integer]" is not an >> array and cannot be evaluated as an array. > > Please, you know what I mean... It is an "array property". Sorry - I didn't mean it bad/ironic/whatsoever. I just thought you meant that: MyArray := Mytest.StringArray; might be a valid assignment for   TTest = class   public     property StringArray[Index: Integer]: string read GetString;   end;   TMyArray = array of string; If it was a valid assignment, there would be 2 possibilities indeed and the compiler would need to check the left side to resolve the overload: MyArray := Mytest.StringArray; MyEnumerator := Mytest.StringArray; But such syntax is invalid for indexed (array) properties.

I understand. But all depends on how the compiler parses and evaluates this.

Let me put brackets to make it more clear: is

MyTest.StringArray[i]

parsed & evaluated as

(MyTest.StringArray)([(i)])

or as

(MyTest.StringArray[(i)])

In the former case, the compiler cannot know what the result type is of the first set of brackets in your proposal. In the latter case, it can be OK.

But I simply do not know, someone with more intimate knowledge of the compiler needs to shed light on this.

Michael.

Reply by email

Ondrej Pokorny
to Michael Van Canneyt
Michael Van Canneyt wrote 21 lines
I understand. But all depends on how the compiler parses and evaluates this. Let me put brackets to make it more clear: is MyTest.StringArray[i] parsed & evaluated as (MyTest.StringArray)([(i)]) or as (MyTest.StringArray[(i)]) In the former case, the compiler cannot know what the result type is of the first set of brackets in your proposal. In the latter case, it can be OK. But I simply do not know, someone with more intimate knowledge of the compiler needs to shed light on this.

I happened to study this part of FPC code back in 2015 when I worked on issue #28820. I can say that FPC directly transfers indexed properties to method calls with the parameters from []-brackets without checking if the property definition exists - and even without checking if the []-brackets are there. That means, in case StringArray is an array property:

MyTest.StringArray[i]

is always evaluated as

MyTest.StringArrayGetter(i)

and never as

(MyTest.StringArray)[i]

the same goes for

MyTest.StringArray -> MyTest.StringArrayGetter()
MyTest.StringArray[] -> MyTest.StringArrayGetter() MyTest.StringArray['abc'] -> MyTest.StringArrayGetter('abc') MyTest.StringArray['abc', 123, 3.14] -> MyTest.StringArrayGetter('abc', 123, 3.14)

But you can explicitly add the brackets around MyTest.StringArray - the compiler allows this.

Let me show you some code to prove myself:

program Project1;
{$mode objfpc}
type
   TValue = record A: Integer end;
   TMyClass = class
   private
     function GetValue: string;
     function GetValue(aindex: string): Double;
   public
     property Index[aindex: string]: Double read GetValue;
   end;
{ TMyClass }
function TMyClass.GetValue(aindex: string): Double;
begin
   Writeln('double indexed overload called');
   Result := Length(aindex);
end;
function TMyClass.GetValue: string;
begin
   Writeln('string not-indexed overload called.');
   Result := 'abc';
end;
var
   c: TMyClass;
begin
   c := TMyClass.Create;
   Writeln(c.Index);        // allowed - string overload
   Writeln(c.Index[]);      // allowed - string overload
   Writeln(c.Index[2]);     // not allowed - overload does not exist
(comment out to compile the program)
   Writeln(c.Index[][2]);   // allowed - string overload
   Writeln((c.Index)[2]);   // allowed - string overload
   Writeln(c.Index['abc']); // allowed - double overload
   ReadLn;
end.

---

So actually, what you call as "my proposal" is not really a proposal - the whole property overload feature is already present in FPC. But now it's just by accident and with wrong syntax. We only need 2 steps to convert this bug into a feature:
1.) Check the indexed property definition before calling the getter/setter + allow indexed property overloads.
2.) Forbid the empty-[]-brackets-syntax so that "c.Index[]" and "c.Index[][2]" from example above will become invalid.

Ondrej

Reply by email

Michael Van Canneyt
to Ondrej Pokorny
Ondrej Pokorny wrote 25 lines
On 01.07.2019 23:25, Michael Van Canneyt wrote: > I understand. But all depends on how the compiler parses and evaluates > this. > > Let me put brackets to make it more clear: is > > MyTest.StringArray[i] > > parsed & evaluated as > > (MyTest.StringArray)([(i)]) > > or as > > (MyTest.StringArray[(i)]) > > In the former case, the compiler cannot know what the result type is > of the > first set of brackets in your proposal. In the latter case, it can be OK. > > But I simply do not know, someone with more intimate knowledge of the > compiler needs to shed light on this. I happened to study this part of FPC code back in 2015 when I worked on issue #28820. I can say that FPC directly transfers indexed properties

Stop... How does FPC decide it is an indexed property ?

Because 'directly transfers indexed properties ' implies the compiler *already decided* that it is an indexed property and needs to convert to calls.

to method calls with the parameters from []-brackets without checking if the property definition exists - and even without checking if the []-brackets are there.

If so, and I have no reason not to believe you, that is very worrying :-)

Show quoted text 10 lines
--- So actually, what you call as "my proposal" is not really a proposal - the whole property overload feature is already present in FPC. But now it's just by accident and with wrong syntax. We only need 2 steps to convert this bug into a feature: 1.) Check the indexed property definition before calling the getter/setter + allow indexed property overloads. 2.) Forbid the empty-[]-brackets-syntax so that "c.Index[]" and "c.Index[][2]" from example above will become invalid.

You could very well be right.

Can someone of the compiler team please comment ?

Michael.

Reply by email

Michael Van Canneyt
to Michael Van Canneyt
Michael Van Canneyt wrote 35 lines
On Tue, 2 Jul 2019, Ondrej Pokorny wrote: > On 01.07.2019 23:25, Michael Van Canneyt wrote: >> I understand. But all depends on how the compiler parses and evaluates >> this. >> >> Let me put brackets to make it more clear: is >> >> MyTest.StringArray[i] >> >> parsed & evaluated as >> >> (MyTest.StringArray)([(i)]) >> >> or as >> >> (MyTest.StringArray[(i)]) >> >> In the former case, the compiler cannot know what the result type is >> of the >> first set of brackets in your proposal. In the latter case, it can be OK. >> >> But I simply do not know, someone with more intimate knowledge of the >> compiler needs to shed light on this. > > I happened to study this part of FPC code back in 2015 when I worked on > issue #28820. I can say that FPC directly transfers indexed properties Stop... How does FPC decide it is an indexed property ? Because 'directly transfers indexed properties ' implies the compiler *already decided* that it is an indexed property and needs to convert to calls.

I had a quick peek in the compiler sources.

In pexpr.pas, before processing the [, the compiler calls this:

         { we need the resultdef }
         do_typecheckpass_changed(p1,nodechanged);

In my opinion, this routine will/should error out if there are 2 symbols called 'StringArray' : one with type 'array property' and one with type 'enumerator'.

But someone of the compiler team should confirm/deny this.

Michael.

Reply by email

Ondrej Pokorny
to Michael Van Canneyt: Stop... How does FPC decide it is an indexed property ? Because 'directly transfers…
Michael Van Canneyt wrote 6 lines
On Tue, 2 Jul 2019, Ondrej Pokorny wrote: > I happened to study this part of FPC code back in 2015 when I worked > on issue #28820. I can say that FPC directly transfers indexed > properties Stop... How does FPC decide it is an indexed property ?

First of all I have to correct my notation (because I now re-checked FPC sources).

"Indexed property" from FPC POV is a property with an index defined ( https://www.freepascal.org/docs-html/ref/refsu34.html ): property X : Longint index 1 read GetCoord Write SetCoord;
- the property above is referred to as "indexed property" in FPC source comments and it has the ppo_indexed option in tpropertyoptions.

Delphi documentation understands with "indexed property" the "array property" - see
http://docwiki.embarcadero.com/RADStudio/Rio/en/Properties_(Delphi) "Array properties are indexed properties":
property Objects[Index: Integer]: TObject read GetObject write SetObject;

I will now use the notation "array property" instead of the ambiguous "indexed property".

---

Q: How does FPC decide it is an array property ?

This is simple. If the compiler encounters

MyTest.StringArray

it looks into the symtable. If a property is found (tpropertysym), it calls the "compiler property handler procedure" handle_propertysym. See
procedure handle_propertysym(propsym : tpropertysym;st : TSymtable;var p1 : tnode);
in in pexpr.pas.

handle_propertysym checks if it is an array property (ppo_hasparameters in tpropertysym.propoptions) - in this case the compiler expects some parameters (even empty parameters are allowed) - see that the "try_to_consume(_LECKKLAMMER)"=false result is silently ignored and also it is not checked if paras are empty.

The compiler passes then the "paras" parameters to the getter/setter call (that automatically searches for a valid method overload with the used parameters).

Ondrej

Reply by email

Ondrej Pokorny
to Michael Van Canneyt: I had a quick peek in the compiler sources. In pexpr.pas, before processing the [, the…
Michael Van Canneyt wrote 10 lines
I had a quick peek in the compiler sources. In pexpr.pas, before processing the [, the compiler calls this:         { we need the resultdef }         do_typecheckpass_changed(p1,nodechanged); In my opinion, this routine will/should error out if there are 2 symbols called 'StringArray' : one with type 'array property' and one with type 'enumerator'.

This code is never executed for an array property. The "[" for an array property is processed directly in handle_propertysym:

     procedure handle_propertysym(propsym : tpropertysym;st :
TSymtable;var p1 : tnode);
       var
          paras : tnode;
          p2    : tnode;
          membercall : boolean;
          callflags  : tcallnodeflags;
          propaccesslist : tpropaccesslist;
          sym: tsym;
       begin
          { property parameters? read them only if the property really }
          { has parameters                                             }
          paras:=nil;
          if (ppo_hasparameters in propsym.propoptions) then
            begin
              if try_to_consume(_LECKKLAMMER) then
                begin
                  paras:=parse_paras(false,false,_RECKKLAMMER);
                  consume(_RECKKLAMMER);
                end;
            end;

These 3 lines are crucial:

          if (ppo_hasparameters in propsym.propoptions) then
            begin
              if try_to_consume(_LECKKLAMMER) then

Ondrej

Reply by email

Michael Van Canneyt
to Ondrej Pokorny
Ondrej Pokorny wrote 14 lines
On 02.07.2019 09:10, Michael Van Canneyt wrote: > I had a quick peek in the compiler sources. > > In pexpr.pas, before processing the [, the compiler calls this: > >         { we need the resultdef } >         do_typecheckpass_changed(p1,nodechanged); > > In my opinion, this routine will/should error out if there are 2 symbols > called 'StringArray' : one with type 'array property' and one with > type 'enumerator'. This code is never executed for an array property. The "[" for an array property is processed directly in handle_propertysym:

Well, I think you are mistaken because I think the code I pointed to is executed before handle_propertysym is called. But I will not argue the point, you may still be right, it just costs me too much time to trace this.

So I still think the compiler people have the final word.

Michael.

Reply by email

Ryan Joseph
to Ondrej Pokorny: This code is never executed for an array property. The "[" for an array property is…
Show quoted text 25 lines
On Jul 2, 2019, at 3:20 AM, Ondrej Pokorny wrote: This code is never executed for an array property. The "[" for an array property is processed directly in handle_propertysym: procedure handle_propertysym(propsym : tpropertysym;st : TSymtable;var p1 : tnode); var paras : tnode; p2 : tnode; membercall : boolean; callflags : tcallnodeflags; propaccesslist : tpropaccesslist; sym: tsym; begin { property parameters? read them only if the property really } { has parameters } paras:=nil; if (ppo_hasparameters in propsym.propoptions) then begin if try_to_consume(_LECKKLAMMER) then begin paras:=parse_paras(false,false,_RECKKLAMMER); consume(_RECKKLAMMER); end; end;

btw, I did need to change this for my patch to work. What happens now is that [] is parsed before handle_propertysym so the parameters can be evaluated and a proper property sym chosen and then passed to handle_propertysym. It’s probably going to be weeks or months before they can apply the patch anyways so I don’t want to make it more complicated than it has to be by adding more stuff. If anything it would be best to fix some of these weird bugs that we found with properties (did anyone file a bug report yet?).

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Michael Van Canneyt: Well, I think you are mistaken because I think the code I pointed to is executed before…

Sorry to ask again but has it been decided that this code should *not* be allowed for ObjFPC mode? I’ll file a bug report right now if so. It will break existing code but when my patch gets applied it can be added back using proper properties.

      function GetValue(k0: variant): TJSON; overload;
      function GetValue(k0,k1: variant): TJSON; overload;
      function GetValue(k0,k1,k2: variant): TJSON; overload;
      function GetValue(k0,k1,k2,k3: variant): TJSON; overload;
      property Values[key: variant]: TJSON read GetValue; default;

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 7 lines
Sorry to ask again but has it been decided that this code should *not* be allowed for ObjFPC mode? I’ll file a bug report right now if so. It will break existing code but when my patch gets applied it can be added back using proper properties. function GetValue(k0: variant): TJSON; overload; function GetValue(k0,k1: variant): TJSON; overload; function GetValue(k0,k1,k2: variant): TJSON; overload; function GetValue(k0,k1,k2,k3: variant): TJSON; overload; property Values[key: variant]: TJSON read GetValue; default;

Why should this declaration not be allowed ?

Only the first GetValue() should be called, of course.

i.e. statement 1:
A:=Values[MyVariant]
must work

but e.g. statement 2:
A:=Values[MyVariant1,MyVariant2]
must not work.

Only if overloaded array properties are allowed
    property Values[key: variant]: TJSON read GetValue; default;
    property Values[key1,key2: variant]: TJSON read GetValue; default;
may the second statement be accepted.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
Show quoted text 18 lines
On Jul 6, 2019, at 12:14 PM, Michael Van Canneyt wrote: Why should this declaration not be allowed ? Only the first GetValue() should be called, of course. i.e. statement 1: A:=Values[MyVariant] must work but e.g. statement 2: A:=Values[MyVariant1,MyVariant2] must not work. Only if overloaded array properties are allowed property Values[key: variant]: TJSON read GetValue; default; property Values[key1,key2: variant]: TJSON read GetValue; default; may the second statement be accepted.

Yeah that’s correct.

Here’s my bug report:

https://bugs.freepascal.org/view.php?id=35809

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Ondrej Pokorny
Show quoted text 10 lines
On Jul 6, 2019, at 4:09 PM, Ondrej Pokorny wrote: On 06.07.2019 20:03, Ryan Joseph wrote: > Yeah that’s correct. > > Here’s my bug report: > > https://bugs.freepascal.org/view.php?id=35809 Did you just create a duplicate bug report to the one I linked in this thread before ( https://lists.freepascal.org/pipermail/fpc-devel/2019-July/041350.html ) or did you have something different in mind with your bug report?

I’m sorry, I didn’t know about your report. I see Sven says it is indeed a bug and will be fixed. If an admin sees this please close my duplicate report.

Regards,
Ryan Joseph

Reply by email

Ondrej Pokorny
to Ryan Joseph
Ryan Joseph wrote 10 lines
> On Jul 6, 2019, at 4:09 PM, Ondrej Pokorny wrote: > > On 06.07.2019 20:03, Ryan Joseph wrote: >> Yeah that’s correct. >> >> Here’s my bug report: >> >> https://bugs.freepascal.org/view.php?id=35809 > Did you just create a duplicate bug report to the one I linked in this thread before ( https://lists.freepascal.org/pipermail/fpc-devel/2019-July/041350.html ) or did you have something different in mind with your bug report? I’m sorry, I didn’t know about your report.

I though you checked it when you answered upon it. But no problem - it happens quite often that people answer my posts without actually reading them. On the other hand it is quite exhausting to keep repeating myself ;)

I see Sven says it is indeed a bug and will be fixed.

Yes, I reported it as bug indeed - the notes about existing property overload support was a little bit ironic (just a little bit ;) ).

Ondrej

Reply by email

Ryan Joseph
to Ondrej Pokorny
Show quoted text 5 lines
On Jul 6, 2019, at 4:28 PM, Ondrej Pokorny wrote: > I’m sorry, I didn’t know about your report. I though you checked it when you answered upon it. But no problem - it happens quite often that people answer my posts without actually reading them. On the other hand it is quite exhausting to keep repeating myself ;)

I just read back and found your first post:

"Properties do allow overloading of the parameters.

You only must not declare the overloaded properties but declare overloaded getters/setters. The compiler will silently accept them.”

I indeed didn’t notice the bug report and just thought this was accepted behavior.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Ryan Joseph

I just encountered something to consider. Should a property be overloadable if the names/args are the same but the return type is different? I think if they are not the same access type (i.e. read/write) then it could work (not sure about the parsing though) but if both are the same access then they should not be allowed.

======

property Values[key: variant]: TValue read GetValue; default;
property Values[key: variant]: variant write SetValue; default;

======

property Values[key: variant]: TValue read GetValue; default;
property Values[key: variant]: string read GetValue; default;

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph

Ryan Joseph schrieb am Mo., 8. Juli 2019, 16:10:

Show quoted text 6 lines
I just encountered something to consider. Should a property be overloadable if the names/args are the same but the return type is different? I think if they are not the same access type (i.e. read/write) then it could work (not sure about the parsing though) but if both are the same access then they should not be allowed.

As Pascal does not allow differentiating between result types this indeed needs to be/stay forbidden.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Jul 8, 2019, at 11:07 AM, Sven Barth via fpc-devel wrote: As Pascal does not allow differentiating between result types this indeed needs to be/stay forbidden.

There actually is a need to distinguish between getters/setters but the way property syntax works we’re forced for the return type to be the same as the input for the setter. This is a problem I faced before where I want the getter to return a pointer but I want the setter to take a direct value.

It could be solved by overloading but the syntax for the setter doesn’t make as much sense.

type
  generic TList<T> = class
    public type
      TReference = ^T;
    private
     function GetValue(index: integer): TReference;
     property Values[index: integer]: TReference read GetValue; default;
     procedure SetValue(index: integer; value: T);
     property Values[index: integer]: T write SetValue; default;
  end;

Regards,
Ryan Joseph

Reply by email

Ben Grasset
to Ryan Joseph
PM Ryan Joseph wrote 5 lines
There actually is a need to distinguish between getters/setters but the way property syntax works we’re forced for the return type to be the same as the input for the setter. This is a problem I faced before where I want the getter to return a pointer but I want the setter to take a direct value.

One thing I might point out as a general tip is that you don't really necessarily *need* an array-style index for the kind of access you seem to be going for, especially if using something like TValue which has a lot of assignment operator overloads in place by default.

Here's a little example I threw together:

program Example;
{$Mode ObjFPC}{$H+}
{$ModeSwitch AdvancedRecords}
{$ImplicitExceptions Off}
{$Assertions On}
uses RTTI;
  operator :=(const RHS: TValue): TObject; inline;
  begin
    if RHS.IsObject() then
      Result := RHS.AsObject()
    else
      Result := nil;
  end;
type
  TMyClass = class end;
  TMyRecord = record
  strict private
    FValue: TValue;
  public
    procedure FreeIfNecessary; inline;
  private
    function GetValue: TValue; inline;
    procedure SetValue(const V: TValue); inline;
  public
    property Value: TValue read GetValue write SetValue;
  end;
  procedure TMyRecord.FreeIfNecessary;
  var O: TObject = nil;
  begin
    // Unfortunate misnomer.. IsObject() really returns true if
    // the value is a class instance, while IsClass()
    // returns true if the value is a class reference.
    // Ironically, there is no function for actual TP-style objects.
    if FValue.IsObject() then
    begin
      O := FValue.AsObject();
      if O  nil then
        O.Free();
    end;
  end;
  function TMyRecord.GetValue: TValue;
  begin
    case FValue.Kind of
      tkSString: Result := FValue.AsString();
      tkAString: Result := FValue.AsAnsiString();
      tkUString: Result := FValue.AsUnicodeString();
      tkFloat: Result := FValue.AsExtended();
      tkInteger, tkInt64: Result := FValue.AsOrdinal();
      tkQWord: Result := FValue.AsUInt64();
      tkBool: Result := FValue.AsBoolean();
      tkClass: Result := FValue.AsObject();
      else
        Assert(False, 'Unsupported data type!');
    end;
  end;
  procedure TMyRecord.SetValue(const V: TValue);
  begin
    FValue := V;
  end;
var
  V: TMyRecord;
  C: TObject = nil;
begin
  V.Value := 2;
  V.Value := 'Hey';
  V.Value := 14.567;
  V.Value := False;
  V.Value := TObject.Create();
  V.FreeIfNecessary();
  V.Value := TMyClass.Create();
  C := V.Value;
  WriteLn(C.ClassName());
  V.FreeIfNecessary();
end.

Reply by email

Ryan Joseph
to Ben Grasset
On Jul 8, 2019, at 2:16 PM, Ben Grasset wrote: One thing I might point out as a general tip is that you don't really necessarily *need* an array-style index for the kind of access you seem to be going for, especially if using something like TValue which has a lot of assignment operator overloads in place by default.

That was just an example, nothing to do with TValue. We need pointer getters for “array of record” so we can do:

list[0]^.x := 100;

and it will actually write to the actual record in the array and not a returned copy. However due to how the properties are currently structured this means we can’t use the setter without passing pointers:

list[0] := @vec;

That’s why I think it’s worthing considering the overloading properties to solve this.

Regards,
Ryan Joseph

Reply by email

Ben Grasset
to Ryan Joseph
PM Ryan Joseph wrote
and it will actually write to the actual record in the array and not a returned copy. However due to how the properties are currently structured this means we can’t use the setter without passing pointers

Ah, I see what you mean. Note you can at least work around this for now with operator overloading:

program Example;
{$mode Delphi}{$H+}
uses SysUtils;
type
  PVec3F = ^TVec3F;
  TVec3F = record
    X, Y, Z: Single;
    class operator Implicit(constref From: TVec3F): PVec3F; inline;
  end;
  class operator TVec3F.Implicit(constref From: TVec3F): PVec3F;
  begin
    Result := @From;
  end;
type
  TList<T> = record
  public type
    PT = ^T;
  strict private
    FData: array of T;
  private
    function GetItem(const I: PtrUInt): PT; inline;
    procedure SetItem(const I: PtrUInt; const Val: PT); inline;
    function GetLength: PtrInt; inline;
    procedure SetLength(const I: PtrInt); inline;
  public
    property Items[const I: PtrUInt]: PT read GetItem write SetItem;
default;
    property Length: PtrInt read GetLength write SetLength;
  end;
  function TList<T>.GetItem(const I: PtrUInt): PT;
  begin
    if I < System.Length(FData) then
      Result := @FData[I]
    else
      Result := nil;
  end;
  procedure TList<T>.SetItem(const I: PtrUInt; const Val: PT);
  begin
    if I < System.Length(FData) then
      FData[I] := Val^;
  end;
  function TList<T>.GetLength: PtrInt;
  begin
    Result := System.Length(FData);
  end;
  procedure TList<T>.SetLength(const I: PtrInt);
  begin
    System.SetLength(FData, I);
  end;
var
  I: PtrUInt;
  Vec: TVec3F = (
    X: 0.0;
    Y: 0.0;
    Z: 0.0;
  );
  VecList: TList<TVec3F>;
begin
  VecList.Length := 1;
  VecList[0] := Vec;
  with VecList[0]^ do begin
    X := 2.0;
    Y := 4.0;
    Z := 6.0;
  end;
  // Access it again, separately...
  with VecList[0]^ do
    WriteLn(Format('[%f %f %f]', [X, Y, Z]));
end.

Reply by email

Sven Barth
to Ryan Joseph: There actually is a need to distinguish between getters/setters but the way property…

Ryan Joseph schrieb am Mo., 8. Juli 2019, 18:04:

Show quoted text 14 lines
> On Jul 8, 2019, at 11:07 AM, Sven Barth via fpc-devel < > wrote: > > As Pascal does not allow differentiating between result types this indeed needs to be/stay forbidden. > There actually is a need to distinguish between getters/setters but the way property syntax works we’re forced for the return type to be the same as the input for the setter. This is a problem I faced before where I want the getter to return a pointer but I want the setter to take a direct value.

Well, in theory you could use a read only index property that returns a pointer and a write only index property that takes the type as is. But overloading only based on the result type is not supported.

Regards,
Sven

Reply by email

Ben Grasset
to Ben Grasset: Ah, I see what you mean. Note you can at least work around this for now with operator…
PM Ben Grasset wrote 10 lines
On Mon, Jul 8, 2019 at 2:22 PM Ryan Joseph wrote: > and it will actually write to the actual record in the array and not a > returned copy. However due to how the properties are currently structured > this means we can’t use the setter without passing pointers > Ah, I see what you mean. Note you can at least work around this for now with operator overloading:

Also, here's a longer (but much better because it doesn't require the data type provided by the user to itself be directly assignable to a pointer) version of that:

program Example;
{$mode Delphi}{$H+}
uses SysUtils;
type
  PVec3F = ^TVec3F;
  TVec3F = record
    X, Y, Z: Single;
  end;
type
  TList<T> = record
  public type
    PT = ^T;
    TConverter = record
      V: PT;
      class operator Implicit(constref From: T): TConverter; inline;
      class operator Implicit(const From: PT): TConverter; inline;
      class operator Implicit(constref From: TConverter): PT; inline;
    end;
  strict private
    FData: array of TConverter;
  private
    function GetItem(const I: PtrUInt): TConverter; inline;
    procedure SetItem(const I: PtrUInt; const Val: TConverter); inline;
    function GetLength: PtrInt; inline;
    procedure SetLength(const I: PtrInt); inline;
  public
    property Items[const I: PtrUInt]: TConverter read GetItem write
SetItem; default;
    property Length: PtrInt read GetLength write SetLength;
  end;
  class operator TList<T>.TConverter.Implicit(constref From: T): TConverter;
  begin
    Result.V := @From;
  end;
  class operator TList<T>.TConverter.Implicit(const From: PT): TConverter;
  begin
    Result.V := From;
  end;
  class operator TList<T>.TConverter.Implicit(constref From: TConverter):
PT;
  begin
    Result := From.V;
  end;
  function TList<T>.GetItem(const I: PtrUInt): TConverter;
  begin
    if I < System.Length(FData) then
      Result := FData[I]
    else
      Result := nil;
  end;
  procedure TList<T>.SetItem(const I: PtrUInt; const Val: TConverter);
  begin
    if I < System.Length(FData) then
      FData[I] := Val;
  end;
  function TList<T>.GetLength: PtrInt;
  begin
    Result := System.Length(FData);
  end;
  procedure TList<T>.SetLength(const I: PtrInt);
  begin
    System.SetLength(FData, I);
  end;
const
  DEFAULT_VEC3F: TVec3F = (
    X: 0.0;
    Y: 0.0;
    Z: 0.0;
  );
var
  PVec: PVec3F;
  VecList: TList<TVec3F>;
begin
  VecList.Length := 2;
  // So, you can directly assign to the list by value...
  VecList[0] := DEFAULT_VEC3F;
  // Or via a pointer...
  VecList[1] := @DEFAULT_VEC3F;
  // And directly assign *from* the list to a pointer...
  PVec := VecList[0];
  with PVec^ do begin
    X := 2.0;
    Y := 4.0;
    Z := 6.0;
  end;
  with PVec^ do
    WriteLn(Format('[%f %f %f]', [X, Y, Z]));
  // Or just do this
  with VecList[0].V^ do begin
    X := 2.0;
    Y := 4.0;
    Z := 6.0;
  end;
  //And this
  with VecList[0].V^ do
    WriteLn(Format('[%f %f %f]', [X, Y, Z]));
end.

Reply by email

Ryan Joseph
to Ben Grasset
On Jul 8, 2019, at 6:11 PM, Ben Grasset wrote: Also, here's a longer (but much better because it doesn't require the data type provided by the user to itself be directly assignable to a pointer) version of that:

That’s getting creative, I like it, thanks.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Sven Barth: Ryan Joseph schrieb am Mo., 8. Juli 2019, 18:04: Well, in theory you could use a read…
Show quoted text 5 lines
On Jul 8, 2019, at 5:27 PM, Sven Barth via fpc-devel wrote: Well, in theory you could use a read only index property that returns a pointer and a write only index property that takes the type as is. But overloading only based on the result type is not supported.

I was thinking more along the lines of making read/write access part of the overloading but that’s kind of pushing it in terms of what overloading means.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Ryan Joseph

Another thing regarding array properties. If I use constref on the setter I get an error "Illegal symbol for property access”. Is there any reason why the setter can’t be constref? If not I’d like to change this with my array properties patch. Making it inline properly avoids the copy but you may not want inline and just want to avoid the copy during setting.

     function GetValue(index: integer): T; inline;
     procedure SetValue(index: integer; constref value: T); inline; // “T” is a record so we want constref here
     property PrivateValues[index: integer]: T read GetValue write SetValue; default; // ERROR: Illegal symbol for property access

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph

Ryan Joseph schrieb am Di., 9. Juli 2019, 15:39:

Show quoted text 5 lines
Another thing regarding array properties. If I use constref on the setter I get an error "Illegal symbol for property access”. Is there any reason why the setter can’t be constref? If not I’d like to change this with my array properties patch. Making it inline properly avoids the copy but you may not want inline and just want to avoid the copy during setting.

It was probably an oversight when constref was introduced.

I wouldn't stuff everything and the kitchen sink into your overload patch however. This and the fix regarding signature checks might be interesting to merge into 3.2 while property overloads might have to "mature" a bit inside trunk.

Regards,
Sven

Reply by email

Ben Grasset
to Ryan Joseph: Another thing regarding array properties. If I use constref on the setter I get an error…
AM Ryan Joseph wrote
Another thing regarding array properties. If I use constref on the setter I get an error "Illegal symbol for property access”. Is there any reason why the setter can’t be constref?

It works for constref if you use {$VARPROPSETTER ON}.

The docs for that directive are slightly incorrect, I just noticed, also:

https://www.freepascal.org/docs-html/current/prog/progsu121.html Where it says "Enable use of var/out/const parameters for property setters", it should say "Enable use of var/out/constref parameters for property setters", because "const" is always allowed, and does not *guarantee* by-reference passing the way "constref" does.

Reply by email

Ryan Joseph
to Ben Grasset
Show quoted text 9 lines
On Jul 9, 2019, at 12:09 PM, Ben Grasset wrote: It works for constref if you use {$VARPROPSETTER ON}. The docs for that directive are slightly incorrect, I just noticed, also: https://www.freepascal.org/docs-html/current/prog/progsu121.html Where it says "Enable use of var/out/const parameters for property setters", it should say "Enable use of var/out/constref parameters for property setters", because "const" is always allowed, and does not *guarantee* by-reference passing the way "constref" does.

Why does that require a special switch? Also, I thought it was determined that allowed “out” in array properties was a bug?

Regards,
Ryan Joseph

Reply by email

Ben Grasset
to Ryan Joseph
PM Ryan Joseph wrote
Why does that require a special switch? Also, I thought it was determined that allowed “out” in array properties was a bug?

Not sure about the exact history of VARPROPSETTER. It's an old Delphi thing, rarely used nowadays though.

As far as the "out", I'd say that is a lexical/parser bug certainly, although it does not seem to actually *behave* like "out" in the case of something like

function GetThing(out I: PtrUInt): TThing;
property Thing[out I: PtrUInt]: TThing read GetThing;

as if it did it couldn't possibly compile with a PtrUInt literal like "MyThing := BlahBlah.Thing[27]". The compiler seems to just act as though it was "var" there.

Reply by email

Ryan Joseph
to Ben Grasset
On Jul 9, 2019, at 12:28 PM, Ben Grasset wrote: As far as the "out", I'd say that is a lexical/parser bug certainly, although it does not seem to actually *behave* like "out" in the case of something like

So do I need to open another bug report for for this out/var/constref stuff? We already have https://bugs.freepascal.org/view.php?id=28949 but that’s just about the overloading bugs.

I think it needs to be something like var/out should be be blocked in properties (unless VARPROPSETTER is on?) and constref should be allowed. Please confirm if that’s correct and I’ll make another bug report.

Regards,
Ryan Joseph

Reply by email

Ben Grasset
to Ryan Joseph
PM Ryan Joseph wrote 8 lines
So do I need to open another bug report for for this out/var/constref stuff? We already have https://bugs.freepascal.org/view.php?id=28949 but that’s just about the overloading bugs. I think it needs to be something like var/out should be be blocked in properties (unless VARPROPSETTER is on?) and constref should be allowed. Please confirm if that’s correct and I’ll make another bug report.

Well, I just checked, and with "out" and "var" for a getter with an array property it actually doesn't compile if you pass a literal. Which is good as at least it behaves logically.

That said, I'm unsure why setters need a directive, but getters do not. (Even constref works fine, with no directives, currently, for getters.)

If anything, what would make the most sense is to just have a {$VARPROP} directive that enabled all of the "by reference" stuff for both getters *and* setters, while otherwise restricting them both to only "const", "constref", or "unprefixed" parameters.

{$VARPROPSETTER} presumably needs to also be left as-is for Delphi
compatibility, but that's not really a big deal either way.

Reply by email

Ben Grasset
to Ben Grasset
PM Ben Grasset wrote 13 lines
On Tue, Jul 9, 2019 at 12:32 PM Ryan Joseph wrote: > So do I need to open another bug report for for this out/var/constref > stuff? We already have https://bugs.freepascal.org/view.php?id=28949 but > that’s just about the overloading bugs. > > I think it needs to be something like var/out should be be blocked in > properties (unless VARPROPSETTER is on?) and constref should be allowed. > Please confirm if that’s correct and I’ll make another bug report. > - snip -

Also, the {$VARPROPSETTER} docs should be amended to not say "const", because again, "const" is allowed in all circumstances and does not guarantee by-reference passing the way "var", "out", and "constref" do.

Reply by email

Ryan Joseph
to Ben Grasset: Well, I just checked, and with "out" and "var" for a getter with an array property it…
Show quoted text 6 lines
On Jul 9, 2019, at 12:59 PM, Ben Grasset wrote: Well, I just checked, and with "out" and "var" for a getter with an array property it actually doesn't compile if you pass a literal. Which is good as at least it behaves logically. That said, I'm unsure why setters need a directive, but getters do not. (Even constref works fine, with no directives, currently, for getters.)

Ok, I'm confused now. I thought that var/out were accepted but I just tested myself and indeed they are rejected. As for the directive I understand that’s just a calling convention change for compatibility with other systems.

That means the only bug I’m interested in is allowing array property setters to use constref.

Regards,
Ryan Joseph

Reply by email

Ben Grasset
to Ryan Joseph
PM Ryan Joseph wrote
Ok, I'm confused now. I thought that var/out were accepted but I just tested myself and indeed they are rejected.

Well, they *are* accepted for things that would normally be valid with "var" and "out" in other scenarios (by which I mean, variables, not numeric literals / e.t.c)

Reply by email

Ryan Joseph
to Ben Grasset
On Jul 9, 2019, at 3:15 PM, Ben Grasset wrote: Well, they *are* accepted for things that would normally be valid with "var" and "out" in other scenarios (by which I mean, variables, not numeric literals / e.t.c)

Please post an example of the var/out bug if you don’t mind. Just to be sure.

Regards,
Ryan Joseph

Reply by email

Ben Grasset
to Ryan Joseph
PM Ryan Joseph wrote
Please post an example of the var/out bug if you don’t mind. Just to be sure.

I don't know if it necessarily *is* a bug.

It would definitely be if it worked with literals as input, which is the impression I had based on what someone reported previously for some reason, but again, after testing it myself that is not the case.

program Example;
{$mode ObjFPC}
{$modeswitch AdvancedRecords}
type
  TRecord = record
  private
    function GetBoolOut(out Index: PtrUInt): Boolean;
    function GetBoolVar(var Index: PtrUInt): Boolean;
  public
    property OutBools[out Index: PtrUInt]: Boolean read GetBoolOut;
    property VarBools[var Index: PtrUInt]: Boolean read GetBoolVar;
  end;
  function TRecord.GetBoolOut(out Index: PtrUInt): Boolean;
  begin
    Result := Index > 5000;
  end;
  function TRecord.GetBoolVar(var Index: PtrUInt): Boolean;
  begin
    Result := Index > 5000;
  end;
var
  R: TRecord;
  I: PtrUInt = 5000;
begin
  // Error: Variable identifier expected
  WriteLn(R.OutBools[5001]);
  // Error: Variable identifier expected
  WriteLn(R.VarBools[5001]);
  // Works fine
  WriteLn(R.OutBools[I]);
  // Works fine
  WriteLn(R.VarBools[I]);
end.

So the behavior as far as making sense based on the definition of "out" and "var" is correct. The only thing that's really "strange" to me is the fact that getters are completely unrestricted in that regard, while setters explicitly need {$VARPROPSETTER ON}.

Reply by email

Ryan Joseph
to Ben Grasset
Show quoted text 6 lines
On Jul 9, 2019, at 4:09 PM, Ben Grasset wrote: I don't know if it necessarily *is* a bug. It would definitely be if it worked with literals as input, which is the impression I had based on what someone reported previously for some reason, but again, after testing it myself that is not the case.

Yeah that doesn’t make any sense to me and it goes against the idea of properties anyways. To the compiler team, is this a bug? If it is I will make a report along with setters not allowing constref.

Regards,
Ryan Joseph

Reply by email

Ben Grasset
to Ryan Joseph
PM Ryan Joseph wrote
it goes against the idea of properties anyways.

I don't know if I completely agree with this part in a general sense. There's plenty of perfectly valid uses for properties that are nothing like the straightforward "make something act like an array" stuff:

program Example;
{$mode Delphi}
uses SysUtils;
type
  StringGetter = function: String;
  function my: String; begin Exit('my'); end;
  function favorite: String; begin Exit('favorite'); end;
  function color: String; begin Exit('color'); end;
  function &is: String; begin Exit('is'); end;
  function red: String; begin Exit('red'); end;
  function green: String; begin Exit('green'); end;
  function blue: String; begin Exit('blue'); end;
type
  SentenceBuilder = record
  strict private
    class function GetSentence(constref A: array of StringGetter): String;
static;
  public
    class property Sentence[constref A: array of StringGetter]: String read
GetSentence; default;
  end;
  class function SentenceBuilder.GetSentence(constref A: array of
StringGetter): String;
  var Fn: StringGetter;
  begin
    Result := '';
    for Fn in A do Result := Result + Fn() + ' ';
    Result := Result.TrimRight() + '.';
    Result[1] := UpCase(Result[1]);
  end;

begin
WriteLn(SentenceBuilder[[
My,
favorite,
color,
&is,
red

  ]]);
  WriteLn(SentenceBuilder[[
    Blue,
    &is,
    my,
    favorite,
    color
  ]]);

end.

Reply by email

Ryan Joseph
to Ben Grasset
On Jul 9, 2019, at 5:32 PM, Ben Grasset wrote: I don't know if I completely agree with this part in a general sense. There's plenty of perfectly valid uses for properties that are nothing like the straightforward "make something act like an array" stuff:

I guess I could see people using var/out for this:

if b[v] then
  v.doSomething;

I’m not sure if it’s a bug or not so I’ll wait for the compiler teams response and I don’t care either way, I just want constref to work. :)

Regards,
Ryan Joseph

Reply by email

Ben Grasset
to Ryan Joseph: I guess I could see people using var/out for this: if b[v] then v.doSomething; I’m not…
PM Ryan Joseph wrote
I’m not sure if it’s a bug or not so I’ll wait for the compiler teams response and I don’t care either way, I just want constref to work. :)

I looked into this some more, and it's almost certainly completely intentional behavior as Delphi does exactly the same thing for all of it. <https://gist.github.com/Akira13641/2ec32459e904932ea4299addfd42e51b>

Reply by email