Lazarus

Programming => General => Topic started by: ALLIGATOR on December 19, 2024, 09:36:19 am

Title: Question about the use of internal types in generics
Post by: ALLIGATOR on December 19, 2024, 09:36:19 am
Can someone please explain why the use of internal type in generic is prohibited (for now at least)?
At the same time there are no such restrictions on methods
Example code:
Code: Pascal  [Select][+][-]
  1. program test;
  2. {$mode objfpc}
  3.  
  4. type
  5.   generic TMyClass<T> = class
  6.   type
  7.     //TSomeType=T.InnerType; // Error
  8.   public
  9.     procedure test;
  10.   end;
  11.  
  12. procedure TMyClass.test;
  13. begin
  14.   T.Method; // OK
  15. end;
  16.  
  17. begin
  18. end.
Title: Re: Question about the use of internal types in generics
Post by: Warfley on December 19, 2024, 01:02:29 pm
I think this is more a bug than intentional. I do not have the FPC source available right now, but from my understanding this should be principally possible
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on December 19, 2024, 04:25:21 pm
It's encouraging to hear that )
Such a feature would add a lot of flexibility to the code I'm trying to write
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on December 20, 2024, 03:54:56 am
Do I need to create a Feature Request issue for this functionality? 🤔
Title: Re: Question about the use of internal types in generics
Post by: Warfley on December 20, 2024, 01:07:23 pm
You can create an issue on the gitlab: https://gitlab.com/freepascal.org/fpc/source/-/issues
Title: Re: Question about the use of internal types in generics
Post by: TRon on December 20, 2024, 02:49:08 pm
Pardon me but, how is the type definition to know what type T is at compilation time when there is not specialization. T literally could be anything at the time of compilation in the posted example.
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on December 20, 2024, 03:57:02 pm
@TRon
And yet any method can be called - why can't we do the same with types?
Besides, you can do it like this:
Code: Pascal  [Select][+][-]
  1. type
  2.   generic TMyClass<T>=class
  3.   type
  4.     T2 = T;
  5.   end;
only for some reason you can't do the same with the internal type, I don't see a logical obstacle to be honest... only perhaps there is some difficulty of implementation from the compiler's side

@Warfley

Yes, I can create an issue, but I thought I'd consult the forum first, because I think I've been getting a bit of a gitlab issues lately
Title: Re: Question about the use of internal types in generics
Post by: TRon on December 20, 2024, 10:10:11 pm
@TRon
And yet any method can be called - why can't we do the same with types?
Ok, in that case please show me that you can call the method named Foo of my class (without specialization to my class TFoo)  :-\

Quote
Besides, you can do it like this:
Yes, because T2 get its actual type during specialization  :)

Ps: the reason the compiler does not seem to barf an error on the method is because of the missing specialization. I would consider accepting that without  specialization to be an actual error. fwiw in which case that only works when you specialize on the class and the method is a class method. Which is the behaviour that I expected (unless there is some hidden feature buried in generics that I am not aware of).
Title: Re: Question about the use of internal types in generics
Post by: Warfley on December 21, 2024, 03:26:41 am
Generics run basically in two passes, first the parsing of the generic structure, where some basic error checking is performed. The main point here is that any accesses onto the generic parameters are (or should not) be checked.

The second pass then is the specialization pass. Here the placeholder generic parameters are replaced with the actual values. Basically the parser acts here exactly as in non generic code, meaning here all errors will be thrown.

Taking the flowing example:
Code: Pascal  [Select][+][-]
  1. generic procedure AddToList<T>(aList: T);
  2. begin
  3.   aList.Add(42);
  4. end;
When parsing this the FPC does the first pass. When it reaches the aList.Add it tries to perform the member lookup, but notices that aList is of an undefined type, so it just completely skips all error checking for member accesses and just does basic Syntax checking.

Once there is a specialization like:
Code: Pascal  [Select][+][-]
  1. specialize AddToList<TStringList>(sl);
It parses the second pass, where it gets to the .Add, does the lookup, finds there is no overloaded member function for Integer and throws an error.

The problem now of this thread is that when accessing member types, it tries to resolve them in the first pass, instead of the specialization pass. I think it's just a bug, because there is no reason to just make the resulting typedef also undefined, and have everything resolved in the specialization pass.
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on December 21, 2024, 08:18:27 am
@TRon

Code: Pascal  [Select][+][-]
  1. program test;
  2. {$mode objfpc}
  3.  
  4. type
  5.   TFoo=class
  6.     class procedure Foo1; virtual; abstract;
  7.     procedure Foo2; virtual; abstract;
  8.     procedure Foo3; virtual; abstract;
  9.   end;
  10.  
  11.   generic TMyClass<T>=class
  12.     procedure TRon;
  13.   end;
  14.  
  15. procedure TMyClass.TRon;
  16. begin
  17.   {v1} TFoo.Foo1;
  18.   {v2} TFoo.Create.Foo2;
  19.   {v3} T.Foo3;
  20. end;
  21.  
  22. begin
  23. end.
  24.  

Easy-peasy!  ;) :P
Title: Re: Question about the use of internal types in generics
Post by: TRon on December 21, 2024, 03:52:42 pm
Generics run basically in two passes, first the parsing of the generic structure, where some basic error checking is performed. The main point here is that any accesses onto the generic parameters are (or should not) be checked.
Thank you for the explanation and yes it should not be checked because it would be impossible to check at that time (e.g. compiler does not know at that time what the user has in mind). In that regards I might be wrong with my remark that it should be flagged an error when not being specialized. I would at least expect some warning (but might be wrong about that as well).


Quote
The second pass then is the specialization pass. Here the placeholder generic parameters are replaced with the actual values. Basically the parser acts here exactly as in non generic code, meaning here all errors will be thrown.
Ok. Bringing ALLIGATOR's latest example into that equation (thank you ALLIGATOR) the example will show its colours when specializing.
Code: Pascal  [Select][+][-]
  1. var
  2.   MyClass : specialize TMyClass<TFoo>;
  3.  

I realize that this probably should be done with trunk compiler but since there is many testing/experimental code in there with regards to generics I can only be certain with latest release (3.2 fixes in my case) what direction generics are heading. That results for me in:
Code: Bash  [Select][+][-]
  1. test.pas(18,19) Warning: Constructing a class "TFoo" with abstract method "Foo1"
  2. test.pas(18,19) Warning: Constructing a class "TFoo" with abstract method "Foo2"
  3. test.pas(18,19) Warning: Constructing a class "TFoo" with abstract method "Foo3"
  4. test.pas(18,19) Warning: Constructing a class "TFoo" with abstract method "Foo1"
  5. test.pas(18,19) Warning: Constructing a class "TFoo" with abstract method "Foo2"
  6. test.pas(18,19) Warning: Constructing a class "TFoo" with abstract method "Foo3"
  7. test.pas(19,14) Error: Only class methods, class properties and class variables can be accessed in class methods
  8. test.pas(19,14) Error: Only class methods, class properties and class variables can be referred with class references
  9. test.pas(26) Fatal: There were 2 errors compiling module, stopping
  10.  
Which in my (inexperienced) view wrt generics seem to indicate that this is something not intended to be supported other than mentioned by the compiler generated errors.

Hence my initial response  :)
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on December 21, 2024, 05:03:03 pm
Here's a fully working version

Code: Pascal  [Select][+][-]
  1. program test;
  2. {$mode objfpc}
  3.  
  4. type
  5.   TFoo=class
  6.     class procedure Foo1;
  7.     procedure Foo2;
  8.     procedure Foo3;
  9.   end;
  10.  
  11.   generic TMyClass<T>=class
  12.     procedure TRon;
  13.   end;
  14.  
  15. class procedure TFoo.Foo1;
  16. begin
  17.   WriteLn({$I %CURRENTROUTINE%})
  18. end;
  19.  
  20. procedure TFoo.Foo2;
  21. begin
  22.   WriteLn({$I %CURRENTROUTINE%})
  23. end;
  24.  
  25. procedure TFoo.Foo3;
  26. begin
  27.   WriteLn({$I %CURRENTROUTINE%})
  28. end;
  29.  
  30. procedure TMyClass.TRon;
  31. begin
  32.   {v1} TFoo.Foo1;
  33.   {v2} TFoo.Create.Foo2;
  34.   {v3} T.Create.Foo3;
  35. end;
  36.  
  37. begin
  38.   specialize TMyClass<TFoo>.Create.TRon;
  39.   ReadLn;
  40. end.
Title: Re: Question about the use of internal types in generics
Post by: Martin_fr on December 21, 2024, 06:36:52 pm
A potential workaround....  (complies at least with 3.2.3)

In the unit in which you have the generic, create a dummy class that satisfies the compilers needs.
Code: Pascal  [Select][+][-]
  1. type
  2.   TDummy = class
  3.   private type Innertype = integer; // must be private
  4.   end;
  5.  
  6.   generic TMyClass<T:TDummy> = class
  7.   type
  8.     TSomeType=T.InnerType; // Now works
  9. ...
  10.  

Downside, you can only specialize with subclasses of TDummy.

And, if you want to replace the InnerType, then you need to declare the subclass (and consequently the specialization) in another unit.

So, in an other unit you can do
Code: Pascal  [Select][+][-]
  1. type
  2.   TDummy2 = class(TDummy)
  3.   type Innertype = boolean;
  4.   end;
  5.  
  6. var
  7.   a: specialize TMyClass<TDummy2>;
  8. begin
  9.   a.Foo := true;
  10. end.
  11.  

If you don't do that in another unit, then "InnerType" is a duplicate identifier to the compiler.

Haven't tested it. Don't know if it is useful for your case.
Title: Re: Question about the use of internal types in generics
Post by: TRon on December 21, 2024, 09:13:50 pm
Here's a fully working version
Thank you very much for the example ALLIGATOR. Seems that initially I misinterpreted your example.

But no matter, you have though me something new that I wasn't aware of (very much obliged). Until your example I had the notion things should be done the way Martin_fr showed.

Never too old to learn something new :) (thank you Warfley and ALLIGATOR)

fwiw: in that case your initial question/report has actual meaning.
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on December 21, 2024, 09:42:46 pm
@Martin_fr
Amazing hack!  ;)

Haven't fully tested everything yet, but I can see from the initial test that it seems to be what I need! It doesn't look as pretty as I'd like, but at least it works now!

What do you think - is this a bug in FPC and is it worth creating an issue or should I just leave it as it is?  ::)

@TRon
I have also learned a lot from reading other people on the forum and in the telegram channel
Title: Re: Question about the use of internal types in generics
Post by: Martin_fr on December 21, 2024, 09:47:56 pm
I don't know if it is bug or design, but the best way to find out is to create a report.

My initial thought was, that unlike for data (values, functions), the compiler needs full type info to pre-parse the generic. But then "<T>" is an unknown type itself, so InnerType could be the same (from the compiler points of view)... But I have no idea what other internals in the compiler may be at play...
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on January 03, 2025, 04:36:56 am
Hello, Martin!

I am using your hack and overall it is good.
But I think I found another way that doesn't require creating a separate unit

Code: Pascal  [Select][+][-]
  1. program test;
  2. {$mode delphi}
  3.  
  4. type
  5.   TDummy = class
  6.   type
  7.     TInner = byte;
  8.   end;
  9.   TMyClass1 = class(TDummy)
  10.   type
  11.     TInner = word;
  12.   end;
  13.  
  14.   TGen<T: TDummy> = class
  15.   type
  16.     TType = T.TInner;
  17.   class procedure print;
  18.   end;
  19.  
  20. class procedure TGen<T>.print;
  21. begin
  22.   WriteLn(SizeOf(TType));
  23. end;
  24.  
  25. begin
  26.   TGen<TDummy>.print; // 1
  27.   TGen<TMyClass1>.print; // 2
  28.  
  29.   ReadLn;
  30. end.

But I have to switch to Delphi mode (I prefer to write in ObjFPC). In ObjFPC mode the `Duplicate identifier` error is generated.

I was looking for a possible modeswitch that would allow duplicate identifiers in ObjFPC mode, but there doesn't seem to be such a switch (although such a switch would be useful in some situations).
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on January 03, 2025, 04:44:18 am
I also found a discussion from way back in 2013 where Sven seemed to be talking about possibly adding a similar modeswitch in the future that would work for ObjFPC mode in this situation as well

I wonder if there is any progress in this direction since then? )

@Sven/Sarah ?

https://lists.freepascal.org/fpc-devel/2013-March/032120.html
Title: Re: Question about the use of internal types in generics
Post by: PascalDragon on January 03, 2025, 03:12:26 pm
I was looking for a possible modeswitch that would allow duplicate identifiers in ObjFPC mode, but there doesn't seem to be such a switch (although such a switch would be useful in some situations).

{$Modeswitch DuplicateLocals}

I also found a discussion from way back in 2013 where Sven seemed to be talking about possibly adding a similar modeswitch in the future that would work for ObjFPC mode in this situation as well

I wonder if there is any progress in this direction since then? )

No, there is not.
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on January 03, 2025, 04:10:00 pm
Quote
{$Modeswitch DuplicateLocals}

Yes, I've seen this switch and even tried it, but it doesn't work in this case (like in my last example - it still swears at duplicate identifiers, then maybe it's a bug??).
Title: Re: Question about the use of internal types in generics
Post by: PascalDragon on January 06, 2025, 11:41:45 am
Quote
{$Modeswitch DuplicateLocals}

Yes, I've seen this switch and even tried it, but it doesn't work in this case (like in my last example - it still swears at duplicate identifiers, then maybe it's a bug??).

As written on the bug tracker: sorry, I had misunderstood your issue here. The DuplicateLocals modeswitch does not apply in this situation.
Title: Re: Question about the use of internal types in generics
Post by: ALLIGATOR on January 06, 2025, 12:05:50 pm
Yes, thank you! 🙏
TinyPortal © 2005-2018