View Issue Details

IDProjectCategoryView StatusLast Update
0033700FPCCompilerpublic2018-07-10 12:23
ReporterVaalKIAAssigned ToMichael Van Canneyt 
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionno change required 
Platformx86_64OSWindows 7 x64OS Versionsp1
Product Version3.1.1Product Build57729 
Target VersionFixed in Version 
Summary0033700: Generics. Does not compile.
DescriptionIn all versions of the compiler.
I am attaching the lazarus project (line 71 (lst:=lst^.next;)).

   type
  LstGrandTyp = ^LstGrandForwardTyp;

  LstGrandForwardTyp = packed record
    early, Next: LstGrandTyp;
  end;


  LstTyp01 = ^LstForwardTyp01;
  LstForwardTyp01 = packed record
    early, Next: LstTyp01;
    Value: byte;
  end;

  LstTyp02 = ^LstForwardTyp02;
  LstForwardTyp02 = packed record
    early, Next: LstTyp02;
    Value: string[255];
  end;

implementation

type
  generic LstEnumerator<T> = record
  private
    lst, lst_save: T;
  public
    constructor Create(const Value: T);
    function GetEnumerator: LstEnumerator;
    function MoveNext: boolean;
    property Current: T read lst;
  end;

constructor LstEnumerator.Create(const Value: T);
begin
  lst := Value;
  lst_save := nil;
end;

function LstEnumerator.GetEnumerator: LstEnumerator;
begin
  Result := Self;
end;

function LstEnumerator.MoveNext: boolean;
begin
  if lst <> nil then
  begin
  //LstGrandTyp(lst) := LstGrandTyp(lst)^.next;//particular case. Compilled
  lst:=lst^.next;
  //T(lst) := T(lst)^.next;//T is specialize of LstTyp01 of LstTyp02 or LstGrandTyp
  Result := True;
  end else Result := False;
end;


{$R *.lfm}
var
  i01, lst01: LstTyp01;
  i02, lst02: LstTyp02;
  i03, lst03: LstGrandTyp;
  en01: specialize LstEnumerator<LstTyp01>;
  en02: specialize LstEnumerator<LstTyp02>;
  en03: specialize LstEnumerator<LstGrandTyp>;
begin

  for i01 in en01.Create(lst01) do
  begin
    i01^.Value := 10;
  end;

  for i02 in en02.Create(lst02) do
  begin
    i02^.Value := 'ten';
  end;

  for i03 in en03.Create(lst03) do
  begin
  end;

end.
Tagsgenerics
Fixed in Revision
FPCOldBugId
FPCTarget
Attached Files
  • projects.zip (129,383 bytes)
  • testbug.pas (1,747 bytes)
    program testbug;
    {$mode delphi}{$H+}
    type
      PLstGrandTyp = ^TLstGrandForwardTyp;
    
      TLstGrandForwardTyp = packed record
        early, Next: PLstGrandTyp;
      end;
    
    
      PLstTyp01 = ^TLstForwardTyp01;
      TLstForwardTyp01 = packed record
        early, Next: PLstTyp01;
        Value: byte;
      end;
    
      PLstTyp02 = ^TLstForwardTyp02;
      TLstForwardTyp02 = packed record
        early, Next: PLstTyp02;
        Value: string[255];
      end;
    
      TLstEnumerator<T> = record
      private
        lst, lst_save: T;
      public
        constructor Create(const Value: T);
        function GetEnumerator: TLstEnumerator<T>;
        function MoveNext: boolean;
        property Current: T read lst;
      end;
    
    constructor TLstEnumerator<T>.Create(const Value: T);
    begin
      lst := Value;
      lst_save := nil;
    end;
    
    function TLstEnumerator<T>.GetEnumerator: TLstEnumerator<T>;
    begin
      Result := Self;
    end;
    
    function TLstEnumerator<T>.MoveNext: boolean;
    begin
      if lst <> nil then
      begin
        // At this point it is simply not known that lst is a type that has 
        // a field called next. So the compiler throws an illegal qualifier.
        // The compiler is correct. This is not a bug!
        lst:=lst^.next;
        Result := True;
      end else Result := False;
    end;
    
    
    var
      i01:PLstTyp01 = nil;
      lst01: PLstTyp01 = nil;
      i02:PlstTyp02 = nil;
      lst02: PLstTyp02 = nil;
      i03:PlstGrandTyp = nil;
      lst03: PLstGrandTyp = nil;
      en01: TLstEnumerator<PLstTyp01>;
      en02: TLstEnumerator<PLstTyp02>;
      en03: TLstEnumerator<PLstGrandTyp>;
    begin
    
      for i01 in en01.Create(lst01) do
      begin
        i01^.Value := 10;
      end;
    
      for i02 in en02.Create(lst02) do
      begin
        i02^.Value := 'ten';
      end;
    
      for i03 in en03.Create(lst03) do
      begin
      end;
    
    end.
    
    
    testbug.pas (1,747 bytes)
  • genericsbug.PNG (31,450 bytes)
    genericsbug.PNG (31,450 bytes)
  • testbugobjfpc.pas (1,836 bytes)
    program testbugobjfpc;
    {$mode objfpc}{$modeswitch advancedrecords}{$H+}
    type
      PLstGrandTyp = ^TLstGrandForwardTyp;
    
      TLstGrandForwardTyp = packed record
        early, Next: PLstGrandTyp;
      end;
    
    
      PLstTyp01 = ^TLstForwardTyp01;
      TLstForwardTyp01 = packed record
        early, Next: PLstTyp01;
        Value: byte;
      end;
    
      PLstTyp02 = ^TLstForwardTyp02;
      TLstForwardTyp02 = packed record
        early, Next: PLstTyp02;
        Value: string[255];
      end;
    
      generic TLstEnumerator<T> = record
      private
        lst, lst_save: T;
      public
        constructor Create(const Value: T);
        function GetEnumerator: specialize TLstEnumerator<T>;
        function MoveNext: boolean;
        property Current: T read lst;
      end;
    
    constructor TLstEnumerator.Create(const Value: T);
    begin
      lst := Value;
      lst_save := nil;
    end;
    
    function TLstEnumerator.GetEnumerator: specialize TLstEnumerator<T>;
    begin
      Result := Self;
    end;
    
    function TLstEnumerator.MoveNext: boolean;
    begin
      if lst <> nil then
      begin
        // At this point it is simply not known that lst is a type that has 
        // a field called next. So the compiler throws an illegal qualifier.
        // The compiler is correct. This is not a bug!
        lst:=lst^.next;
        Result := True;
      end else Result := False;
    end;
    
    
    var
      i01:PLstTyp01 = nil;
      lst01: PLstTyp01 = nil;
      i02:PlstTyp02 = nil;
      lst02: PLstTyp02 = nil;
      i03:PlstGrandTyp = nil;
      lst03: PLstGrandTyp = nil;
      en01: specialize TLstEnumerator<PLstTyp01>;
      en02: specialize TLstEnumerator<PLstTyp02>;
      en03: specialize TLstEnumerator<PLstGrandTyp>;
    begin
    
      for i01 in en01.Create(lst01) do
      begin
        i01^.Value := 10;
      end;
    
      for i02 in en02.Create(lst02) do
      begin
        i02^.Value := 'ten';
      end;
    
      for i03 in en03.Create(lst03) do
      begin
      end;
    
    end.
    
    
    testbugobjfpc.pas (1,836 bytes)
  • testbugobjfpc_answer.pas (1,414 bytes)
    program testbugobjfpc_answer;
    {$mode objfpc}{$modeswitch advancedrecords}{$H+}
    type
    
      PLstTyp01 = ^TLstForwardTyp01;
      TLstForwardTyp01 = packed record
        early, Next: PLstTyp01;
        Value: byte;
      end;
    
    
    
      generic TLstEnumerator<T> = record
      type
      T_generic=T;
      private
        lst, lst_save: T_generic;
      public
        constructor Create(const Value: T_generic);
        function GetEnumerator: specialize TLstEnumerator<T_generic>;
        function MoveNext: boolean;
        property Current: T_generic read lst;
      end;
    
    constructor TLstEnumerator.Create(const Value: T_generic);
    begin
      lst := Value;
      lst_save := nil;
    end;
    
    function TLstEnumerator.GetEnumerator: specialize TLstEnumerator<T_generic>;
    begin
      Result := Self;
    end;
    
    function TLstEnumerator.MoveNext: boolean;
    begin
      if lst <> nil then
      begin
        //At this point, it's completely unknown that lst is a type that has a field called next.
        //This is confirmed by the T_generis type that is used at the end of the program, to gain access to the field of "next".
        //lst:=lst^.next;
        Result := True;
      end else Result := False;
    end;
    
    
    var
      en01: specialize TLstEnumerator<PLstTyp01>;
      i01 : en01.T_generic = nil;
      i02 : en01.T_generic = nil;
      lst01: en01.T_generic = nil;
    begin
    
      for i01 in en01.Create(lst01) do
      begin
        i01^.Value := 10;
        i02 := i01^.next;
      end;
    
    
    end.
    
    
    
    testbugobjfpc_answer.pas (1,414 bytes)
  • EmptyGeneric.pas (501 bytes)
    {$mode objfpc}{$modeswitch advancedrecords}{$H+}
    type
    
    generic GenTyp<T> = record
      public
        procedure proc(var Value: T);
      end;
    
    procedure GenTyp.proc(var Value: T);
    begin
      //No specialization has been written yet
      //There is no such field in any type, but the compilation will be successful,
      //because we do not ask to create any variant of the program text, and hence
      //the creation of the executable code.
      Value.future_field := Value.future_field;
    end;
    
    
    begin
    end.
    
    EmptyGeneric.pas (501 bytes)
  • SimpleGeneric.pas (789 bytes)
    {$mode objfpc}{$modeswitch advancedrecords}{$H+}
    type
    
    generic GenTyp<T> = record
      public
        procedure proc(var Value: T);
      end;
    
    procedure GenTyp.proc(var Value: T);
    begin
      //Only the text of the field looks the same, the executable
      //code will be completely different.
      Value.everywhere_there_is := Value.everywhere_there_is;
      //Any text should be suitable for all specializations.
      //This one is not suitable for specialization T_A
      Value.some_field := Value.some_field;
    end;
    
    
    type
    T_A = record
      everywhere_there_is:Byte;
      //This field does not affect the generic
      many_field:word;
    end;
    
    T_B = record
      everywhere_there_is:shortstring;
      some_field : real;
    end;
    
    var
      a:specialize GenTyp <T_A>;
      b:specialize GenTyp <T_B>;
    begin
    end.
    
    SimpleGeneric.pas (789 bytes)
  • IteratorWithoutGeneric.pas (986 bytes)
    program IteratorWithoutGeneric;
    {$mode objfpc}{$modeswitch advancedrecords}{$H+}
    type
    
      PLstTyp01 = ^TLstForwardTyp01;
      TLstForwardTyp01 = packed record
        early, Next: PLstTyp01;
        Value: byte;
      end;
    
    
    
      IteratorTyp = record
      private
        lst, lst_save: PLstTyp01;
      public
        constructor Create(const Value: PLstTyp01);
        function GetEnumerator: IteratorTyp;
        function MoveNext: boolean;
        property Current: PLstTyp01 read lst;
      end;
    
    constructor IteratorTyp.Create(const Value: PLstTyp01);
    begin
      lst := Value;
      lst_save := nil;
    end;
    
    function IteratorTyp.GetEnumerator: IteratorTyp;
    begin
      Result := Self;
    end;
    
    function IteratorTyp.MoveNext: boolean;
    begin
      if lst <> nil then
      begin
        lst:=lst^.next;
        Result := True;
      end else Result := False;
    end;
    
    
    var
      en01: PLstTyp01 = nil;
      i01 : PLstTyp01 = nil;
    begin
    
      for i01 in IteratorTyp.Create(en01) do
      begin
        i01^.Value := 10;
      end;
    
    
    end.
    

Relationships

related to 0033960 resolvedMichael Van Canneyt generics chapter 8.8 "A word about scope" needs maybe a remark about accessing members of T that are not yet specialized. 

Activities

VaalKIA

2018-05-06 08:18

reporter  

projects.zip (129,383 bytes)

Thaddy de Koning

2018-05-06 13:54

reporter   ~0108163

I have my reservations regarding your code. (But will try to test it)
Why don't you use one of the generic TLists that are available? All three (fgl, fcl-stl and rtl-generics) are stable generic implementations of a linked list.
Did you ask on the forum before filing a bug report? And their default enumerators work out of the box. I don't think - based on analysis - that your code is even supposed to work.

Serge Anvarov

2018-05-06 14:03

reporter   ~0108164

That's OK, because type T is not restricted. I'm more surprised why the compiler doesn't swear at the line "list_save := nil;".

Thaddy de Koning

2018-05-06 14:07

reporter   ~0108165

That's what I saw too, but there's more wrong at first glance. I am now trying to fish them out.

Marco van de Voort

2018-05-06 14:53

manager   ~0108166

Delphi also has some similar issues, at least in older (XE..XE3) versions.

A standard workaround was to define PT = ^T; in the generic and then do

PT(@lst)^ := (PT(@lst)^)^.next;

VaalKIA

2018-05-06 18:21

reporter   ~0108168

Last edited: 2018-05-06 18:28

View 2 revisions

Yes, I asked at one of the forums, in 2016, they agreed with me, but suggested that the implementation of generics is still damp, however, nothing has changed since then. I do not use the ready-made solutions for the linked list, because I do not like classes, and in this version of the project I use a ring list with the properties and behavior that I need. The iterator itself, without the generic, works fine, but I'm tired of duplicating each code of new type.

Thanks for (PT(@lst)^ := (PT(@lst)^)^.next; ), I'll try. Of course, it's not in MoveNext, but in other procedures that need to know the structure to copy/del an element, so your hint is very useful.

VaalKIA

2018-05-24 01:41

reporter   ~0108500

Last edited: 2018-05-24 01:51

View 5 revisions

Here's how you can add an intermediate type so you can see the difference inside and outside the iterator. Changed are marked by stars.

implementation

type
  generic LstEnumerator<T> = record
  type
    T_test=T;//*******
  private
    lst, lst_save: T_test;//*******
  public
    constructor Create(const Value: T_test); //*******
    function GetEnumerator: LstEnumerator;
    function MoveNext: boolean;
    property Current: T_test read lst;//*******
  end;

constructor LstEnumerator.Create(const Value: T_test);//*******
begin
  lst := Value;
  lst_save := nil;
end;

function LstEnumerator.GetEnumerator: LstEnumerator;
begin
  Result := Self;
end;

function LstEnumerator.MoveNext: boolean;
begin
  if lst <> nil then
  begin

  lst:=lst^.next;//*******ERROR***********

  Result := True;
  end else Result := False;
end;


{$R *.lfm}
var
  en01: specialize LstEnumerator<LstTyp01>;
  en02: specialize LstEnumerator<LstTyp02>;
  en03: specialize LstEnumerator<LstGrandTyp>;
  i01, i01_test, lst01: en01.T_test;//*******
  i02, i02_test, lst02: en02.T_test;//*******
  i03, i03_test, lst03: en03.T_test;//*******

begin

  for i01 in en01.Create(lst01) do
  begin
    i01^.Value := 10;
    i01_test:=i01^.next;//*******OK***********
  end;

  for i02 in en02.Create(lst02) do
  begin
    i02^.Value := 'ten';
    i02_test:=i02^.next;//*******OK***********
  end;

  for i03 in en03.Create(lst03) do
  begin
    i03_test:=i03^.next;//*******OK***********
  end;

end.

P.S. To Marco van de Voort. PT(@lst)^ := (PT(@lst)^)^.next; Does not work

VaalKIA

2018-06-24 12:37

reporter   ~0109027

Is there any progress?

Thaddy de Koning

2018-06-27 09:20

reporter   ~0109080

Last edited: 2018-06-27 10:06

View 3 revisions

Well the only progress I could make is that I now definitely know you have a bug:
(I changed the style and the mode, but didn't touch the code)

function TLstEnumerator<T>.MoveNext: boolean;
begin
  if lst <> nil then
  begin
    lst:=lst^.next; // it is simply not known that lst is a type that has a field called next
    Result := True;
  end else Result := False;
end;

You try to access a concrete member of T that YOU happen to know is there, but the compiler can't possibly know that at that point. I have attached a program with a full explanation.
(testbug.pas)
Please close the report when you understand it is your bug, not the compiler...

Thaddy de Koning

2018-06-27 10:05

reporter  

testbug.pas (1,747 bytes)
program testbug;
{$mode delphi}{$H+}
type
  PLstGrandTyp = ^TLstGrandForwardTyp;

  TLstGrandForwardTyp = packed record
    early, Next: PLstGrandTyp;
  end;


  PLstTyp01 = ^TLstForwardTyp01;
  TLstForwardTyp01 = packed record
    early, Next: PLstTyp01;
    Value: byte;
  end;

  PLstTyp02 = ^TLstForwardTyp02;
  TLstForwardTyp02 = packed record
    early, Next: PLstTyp02;
    Value: string[255];
  end;

  TLstEnumerator<T> = record
  private
    lst, lst_save: T;
  public
    constructor Create(const Value: T);
    function GetEnumerator: TLstEnumerator<T>;
    function MoveNext: boolean;
    property Current: T read lst;
  end;

constructor TLstEnumerator<T>.Create(const Value: T);
begin
  lst := Value;
  lst_save := nil;
end;

function TLstEnumerator<T>.GetEnumerator: TLstEnumerator<T>;
begin
  Result := Self;
end;

function TLstEnumerator<T>.MoveNext: boolean;
begin
  if lst <> nil then
  begin
    // At this point it is simply not known that lst is a type that has 
    // a field called next. So the compiler throws an illegal qualifier.
    // The compiler is correct. This is not a bug!
    lst:=lst^.next;
    Result := True;
  end else Result := False;
end;


var
  i01:PLstTyp01 = nil;
  lst01: PLstTyp01 = nil;
  i02:PlstTyp02 = nil;
  lst02: PLstTyp02 = nil;
  i03:PlstGrandTyp = nil;
  lst03: PLstGrandTyp = nil;
  en01: TLstEnumerator<PLstTyp01>;
  en02: TLstEnumerator<PLstTyp02>;
  en03: TLstEnumerator<PLstGrandTyp>;
begin

  for i01 in en01.Create(lst01) do
  begin
    i01^.Value := 10;
  end;

  for i02 in en02.Create(lst02) do
  begin
    i02^.Value := 'ten';
  end;

  for i03 in en03.Create(lst03) do
  begin
  end;

end.

testbug.pas (1,747 bytes)

Thaddy de Koning

2018-06-27 12:31

reporter   ~0109082

Last edited: 2018-06-27 12:34

View 3 revisions

Note I specifically cleaned up your code until the bug was the only thing left.
That should make it easier to understand. The compiler throws just one error, warning or hint: at your bug.
It can be solved by making the records generic too, or by using inheritance and use a class or interfaced classes and an interface.

Thaddy de Koning

2018-06-27 13:39

reporter   ~0109083

Last edited: 2018-06-28 08:00

View 3 revisions

Note I did the mode conversion to test against Delphi: same error, same place.
See screenshot. Note Delphi throws even more errors, but includes the same bug at the same line.
Also note that if the code is changed so that PT=^T the error is still thrown as: [dcc32 Error] testbug.dpr(54): E2003 Undeclared identifier: 'next'
Which is exactly what I already explained: both compilers can not resolve that field "next" at that point.

Thaddy de Koning

2018-06-28 07:40

reporter  

genericsbug.PNG (31,450 bytes)
genericsbug.PNG (31,450 bytes)

Marco van de Voort

2018-06-28 14:24

manager   ~0109112

Thaddy: that is true for Delphi generics, but not for FPC generics.

Thaddy de Koning

2018-06-28 14:57

reporter   ~0109115

Last edited: 2018-06-28 15:20

View 5 revisions

Well Marco as per my demo: it is also true for FPC generics. I tested both modes and it does not matter. That particular line can not be resolved in any mode in FPC and it can also not be solved in Delphi (I used 10.1).
Attached the same code in the other mode. This is not a bug. Period.
Not only does it not compile, it should not compile: it would crash a specialization of a type that has no "next".
It may work with highly sophisticated constraints but that mechanism is not in place (maybe yet).

Thaddy de Koning

2018-06-28 15:12

reporter  

testbugobjfpc.pas (1,836 bytes)
program testbugobjfpc;
{$mode objfpc}{$modeswitch advancedrecords}{$H+}
type
  PLstGrandTyp = ^TLstGrandForwardTyp;

  TLstGrandForwardTyp = packed record
    early, Next: PLstGrandTyp;
  end;


  PLstTyp01 = ^TLstForwardTyp01;
  TLstForwardTyp01 = packed record
    early, Next: PLstTyp01;
    Value: byte;
  end;

  PLstTyp02 = ^TLstForwardTyp02;
  TLstForwardTyp02 = packed record
    early, Next: PLstTyp02;
    Value: string[255];
  end;

  generic TLstEnumerator<T> = record
  private
    lst, lst_save: T;
  public
    constructor Create(const Value: T);
    function GetEnumerator: specialize TLstEnumerator<T>;
    function MoveNext: boolean;
    property Current: T read lst;
  end;

constructor TLstEnumerator.Create(const Value: T);
begin
  lst := Value;
  lst_save := nil;
end;

function TLstEnumerator.GetEnumerator: specialize TLstEnumerator<T>;
begin
  Result := Self;
end;

function TLstEnumerator.MoveNext: boolean;
begin
  if lst <> nil then
  begin
    // At this point it is simply not known that lst is a type that has 
    // a field called next. So the compiler throws an illegal qualifier.
    // The compiler is correct. This is not a bug!
    lst:=lst^.next;
    Result := True;
  end else Result := False;
end;


var
  i01:PLstTyp01 = nil;
  lst01: PLstTyp01 = nil;
  i02:PlstTyp02 = nil;
  lst02: PLstTyp02 = nil;
  i03:PlstGrandTyp = nil;
  lst03: PLstGrandTyp = nil;
  en01: specialize TLstEnumerator<PLstTyp01>;
  en02: specialize TLstEnumerator<PLstTyp02>;
  en03: specialize TLstEnumerator<PLstGrandTyp>;
begin

  for i01 in en01.Create(lst01) do
  begin
    i01^.Value := 10;
  end;

  for i02 in en02.Create(lst02) do
  begin
    i02^.Value := 'ten';
  end;

  for i03 in en03.Create(lst03) do
  begin
  end;

end.

testbugobjfpc.pas (1,836 bytes)

VaalKIA

2018-07-09 01:21

reporter   ~0109296

Last edited: 2018-07-09 01:50

View 7 revisions

I do not understand why the compiler does not know what to eat next? Look at my code in the comment (https://bugs.freepascal.org/view.php?id=33700#c108500): there is a specialization of the type:

en01: specialize LstEnumerator <LstTyp01>;

There is a type that I specifically deduced through the generic:

i01, i01_test, lst01: en01.T_test;

This works great:

for i01 in en01.Create (lst01) do
to begin
i01 ^ .Value: = 10;
i01_test: = i01 ^ .next; // ******* OK ***********
end;

But in the generic, no!

type
common LstEnumerator <T> = record
type
T_test = T;
private
lst, lst_save: T_test;
.....
function LstEnumerator.MoveNext: boolean;
begin
if lst <> nil, then
begin

LST: = LST ^ .next; // ******* *********** ERROR

Result: = True;
end else Result: = False;
end;

Types lst, i01_test are the same T_test

The type specialization gives information about the fact that there is a field next, this is obvious. (specialize LstEnumerator <LstTyp01> => T; LstTyp01^.next => T^.next)

i upload file.

VaalKIA

2018-07-09 01:50

reporter  

testbugobjfpc_answer.pas (1,414 bytes)
program testbugobjfpc_answer;
{$mode objfpc}{$modeswitch advancedrecords}{$H+}
type

  PLstTyp01 = ^TLstForwardTyp01;
  TLstForwardTyp01 = packed record
    early, Next: PLstTyp01;
    Value: byte;
  end;



  generic TLstEnumerator<T> = record
  type
  T_generic=T;
  private
    lst, lst_save: T_generic;
  public
    constructor Create(const Value: T_generic);
    function GetEnumerator: specialize TLstEnumerator<T_generic>;
    function MoveNext: boolean;
    property Current: T_generic read lst;
  end;

constructor TLstEnumerator.Create(const Value: T_generic);
begin
  lst := Value;
  lst_save := nil;
end;

function TLstEnumerator.GetEnumerator: specialize TLstEnumerator<T_generic>;
begin
  Result := Self;
end;

function TLstEnumerator.MoveNext: boolean;
begin
  if lst <> nil then
  begin
    //At this point, it's completely unknown that lst is a type that has a field called next.
    //This is confirmed by the T_generis type that is used at the end of the program, to gain access to the field of "next".
    //lst:=lst^.next;
    Result := True;
  end else Result := False;
end;


var
  en01: specialize TLstEnumerator<PLstTyp01>;
  i01 : en01.T_generic = nil;
  i02 : en01.T_generic = nil;
  lst01: en01.T_generic = nil;
begin

  for i01 in en01.Create(lst01) do
  begin
    i01^.Value := 10;
    i02 := i01^.next;
  end;


end.


testbugobjfpc_answer.pas (1,414 bytes)

Thaddy de Koning

2018-07-09 07:37

reporter   ~0109300

Last edited: 2018-07-09 08:18

View 3 revisions

As I showed, my cleaned up code fails in FPC in all modes *and* in Delphi and always in the same spot. If that's not enough to convince you...
And yes, the actual specialization provides enough context to access the next field. In your original code there simply isn't enough known context.
That is the whole point. You need a specialization of T to provide context for a field called next...
Or a constraint on T that describes it has a field called next. Already possible with classes.

VaalKIA

2018-07-09 10:01

reporter   ~0109303

Last edited: 2018-07-09 10:16

View 3 revisions

"In your original code there simply isn't enough known context."
You are talking about the file testbugobjfpc_answer.pas?
In what place is there not enough specialization to provide the context of the field?
i01 in the loop is specialized as well as the variable lst, which, suddenly, does not have specialization, realy?
I have the impression that I'm talking to someone who does not understand what generics are.

Thaddy de Koning

2018-07-09 15:28

reporter   ~0109316

Last edited: 2018-07-09 15:40

View 4 revisions

Well I have provided you with examples in all flavors possible and you could only make your code compile by commenting the offending line. Generics are not variants: they need context for type-safety. Your code does not have that context at that particular line to resolve T to a type with a field "next". Period.
Delphi barks even *before* that line as was already observed before and which I have proven with the Delphi compatible example. Use classes and constraints.
Maybe Serge can go over my code and confirm I am right. Or one of the devs: it is obvious there is no context for "next". And if there is no context you can not use generics for that field. Rather basic.
Generics need to be type safe: what if your T has no "next"? But you don't get the point...The point is that the specialization works, in your last example, because there you provide context.

VaalKIA

2018-07-09 22:11

reporter  

EmptyGeneric.pas (501 bytes)
{$mode objfpc}{$modeswitch advancedrecords}{$H+}
type

generic GenTyp<T> = record
  public
    procedure proc(var Value: T);
  end;

procedure GenTyp.proc(var Value: T);
begin
  //No specialization has been written yet
  //There is no such field in any type, but the compilation will be successful,
  //because we do not ask to create any variant of the program text, and hence
  //the creation of the executable code.
  Value.future_field := Value.future_field;
end;


begin
end.
EmptyGeneric.pas (501 bytes)

VaalKIA

2018-07-09 22:30

reporter  

SimpleGeneric.pas (789 bytes)
{$mode objfpc}{$modeswitch advancedrecords}{$H+}
type

generic GenTyp<T> = record
  public
    procedure proc(var Value: T);
  end;

procedure GenTyp.proc(var Value: T);
begin
  //Only the text of the field looks the same, the executable
  //code will be completely different.
  Value.everywhere_there_is := Value.everywhere_there_is;
  //Any text should be suitable for all specializations.
  //This one is not suitable for specialization T_A
  Value.some_field := Value.some_field;
end;


type
T_A = record
  everywhere_there_is:Byte;
  //This field does not affect the generic
  many_field:word;
end;

T_B = record
  everywhere_there_is:shortstring;
  some_field : real;
end;

var
  a:specialize GenTyp <T_A>;
  b:specialize GenTyp <T_B>;
begin
end.
SimpleGeneric.pas (789 bytes)

VaalKIA

2018-07-09 22:39

reporter   ~0109319

As far as I understand generics, they do not create any executable code at all and it's impossible to check the correctness, only syntax in general. But as soon as specialization appears in the program, it makes it possible to make a binary code and completely check the correctness of the program. The code will be duplicated as many times as there are different specializations and the same number is checked. Accordingly, there can be no "suddenly no field", all specializations are provided, all types are defined, all the information about the fields is in these types. In fact, the meaning of the generic: the compiler writes code for you that works differently in different ways, but looks almost identical in the form of text, as a rule, differences are present in types, which entails completely different functionality (addition of lines or addition of floating-point numbers) . This "<T>" in generics means what the text of the program variant will differ, but what exactly will be there, should be written in specialization. And for all specializations, the text of the program should be correct, that is, types can contain a variety of fields, but only those in the text whose names are present in each specialization are mentioned in the text. Therefore, if there is specialization, there can not be any questions to the generic, if there is no specialization at all, then there is no information that there is something meaningful in the text of the program.

I uploaded two files (EmptyGeneric.Pas SimpleGeneric.Pas), in one there is no specialization, in another specialization cause conflict.

Now back to testbugobjfpc_answer.pas, specialization is, what other context do you need? Type is known completely, what kind of security are you talking about?

VaalKIA

2018-07-09 22:57

reporter   ~0109320

Last edited: 2018-07-09 23:27

View 3 revisions

>Generics need to be type safe: what if your T has no "next"?
If T does not have the next field, then it does not exist in one of the existing specializations, then there will be a compilation error, as in the SimpleGeneris.Pas file. In the file testbugobjfpc_answer.pass the field is in all specializations, it has access, except for the iterator. Why the iterator does not have access is an error.

If you mean the type Pointer in the Iterator, then I added the file iterator_without_generic.pas. As I said (https://bugs.freepascal.org/view.php?id=33700#c108168), there are no problems with the operation of the iterator itself.

VaalKIA

2018-07-09 23:18

reporter  

IteratorWithoutGeneric.pas (986 bytes)
program IteratorWithoutGeneric;
{$mode objfpc}{$modeswitch advancedrecords}{$H+}
type

  PLstTyp01 = ^TLstForwardTyp01;
  TLstForwardTyp01 = packed record
    early, Next: PLstTyp01;
    Value: byte;
  end;



  IteratorTyp = record
  private
    lst, lst_save: PLstTyp01;
  public
    constructor Create(const Value: PLstTyp01);
    function GetEnumerator: IteratorTyp;
    function MoveNext: boolean;
    property Current: PLstTyp01 read lst;
  end;

constructor IteratorTyp.Create(const Value: PLstTyp01);
begin
  lst := Value;
  lst_save := nil;
end;

function IteratorTyp.GetEnumerator: IteratorTyp;
begin
  Result := Self;
end;

function IteratorTyp.MoveNext: boolean;
begin
  if lst <> nil then
  begin
    lst:=lst^.next;
    Result := True;
  end else Result := False;
end;


var
  en01: PLstTyp01 = nil;
  i01 : PLstTyp01 = nil;
begin

  for i01 in IteratorTyp.Create(en01) do
  begin
    i01^.Value := 10;
  end;


end.

Thaddy de Koning

2018-07-10 10:02

reporter   ~0109331

Correct. They are kind of a macro replay.
See this remark in the documentation, though:
"Despite the fact that generics act as a macro which is replayed at specialization time, the reference to DoLocalThings is resolved when TMyClass is defined, not when TB is defined. This means that the output of the program is:

home: >fpc -S2 myb.pp
home: >myb
mya.DoLocalThings
This behaviour is dictated by safety and necessity:
See https://freepascal.org/docs-html/ref/ref.html#QQ2-115-143 Chapter 8 section 8.
"It should be stressed that all identifiers other than the template placeholders should be known when the generic class is declared. This works in 2 ways. First, all types must be known, that is, a type identifier with the same name must exist. The following unit will produce an error:
" etc.
And:
"A programmer specializing a class has no way of knowing which local procedures are used, so he cannot accidentally ’override’ it.
A programmer specializing a class has no way of knowing which local procedures are used, so he cannot implement it either, since he does not know the parameters.
If implementation procedures are used as in the example above, they cannot be referenced from outside the unit. They could be in another unit altogether, and the programmer has no way of knowing he should include them before specializing his class."

And indeed, the iterator works, but not w/o commenting the offending field and not w/o specializing first. That is per the above documentation.

Michael Van Canneyt

2018-07-10 11:11

administrator   ~0109335

The compiler cannot and should not compile this code.

at declaration time, it has no way to know that

  lst:=lst^.next;

is valid code, since it does not know what lst is.

If you want that code to compile, you need to restrict the type of the template type T, but this serves no purpose with records. With classes it would work.

VaalKIA

2018-07-10 12:15

reporter   ~0109340

What relation of item 8.8 has to the problem under consideration? This is paragraph 8.2, I quote (https://freepascal.org/docs-html/ref/refse50.html#x104-1260008.2):
"Not only generic classes can be defined, but also other types: "

Also, classes are not relevant to iterators paragraph 9.2 https://freepascal.org/docs-html/ref/refse59.html#x119-1410009.2

Michael Van Canneyt

2018-07-10 12:23

administrator   ~0109341

I have updated the documentation, quoting your case and explaning how it can be solved using classes and type restrictions.
But you cannot fix it using records (unless maybe using some dirty hacks)

However this updated version of the documentation is not yet published, it will be published with the next release of FPC.

None of this changes the fact that your example simply is not supposed to compile, by design.

Issue History

Date Modified Username Field Change
2018-05-06 08:18 VaalKIA New Issue
2018-05-06 08:18 VaalKIA File Added: projects.zip
2018-05-06 13:54 Thaddy de Koning Note Added: 0108163
2018-05-06 14:03 Serge Anvarov Note Added: 0108164
2018-05-06 14:07 Thaddy de Koning Note Added: 0108165
2018-05-06 14:53 Marco van de Voort Note Added: 0108166
2018-05-06 18:21 VaalKIA Note Added: 0108168
2018-05-06 18:28 VaalKIA Note Edited: 0108168 View Revisions
2018-05-24 01:41 VaalKIA Note Added: 0108500
2018-05-24 01:41 VaalKIA Note Edited: 0108500 View Revisions
2018-05-24 01:48 VaalKIA Note Edited: 0108500 View Revisions
2018-05-24 01:50 VaalKIA Note Edited: 0108500 View Revisions
2018-05-24 01:51 VaalKIA Note Edited: 0108500 View Revisions
2018-06-24 12:37 VaalKIA Note Added: 0109027
2018-06-27 09:20 Thaddy de Koning Note Added: 0109080
2018-06-27 10:04 Thaddy de Koning Note Edited: 0109080 View Revisions
2018-06-27 10:05 Thaddy de Koning File Added: testbug.pas
2018-06-27 10:06 Thaddy de Koning Note Edited: 0109080 View Revisions
2018-06-27 12:31 Thaddy de Koning Note Added: 0109082
2018-06-27 12:33 Thaddy de Koning Note Edited: 0109082 View Revisions
2018-06-27 12:34 Thaddy de Koning Note Edited: 0109082 View Revisions
2018-06-27 13:39 Thaddy de Koning Note Added: 0109083
2018-06-28 07:40 Thaddy de Koning File Added: genericsbug.PNG
2018-06-28 07:42 Thaddy de Koning Note Edited: 0109083 View Revisions
2018-06-28 08:00 Thaddy de Koning Note Edited: 0109083 View Revisions
2018-06-28 14:24 Marco van de Voort Note Added: 0109112
2018-06-28 14:57 Thaddy de Koning Note Added: 0109115
2018-06-28 15:11 Thaddy de Koning Note Edited: 0109115 View Revisions
2018-06-28 15:12 Thaddy de Koning File Added: testbugobjfpc.pas
2018-06-28 15:14 Thaddy de Koning Note Edited: 0109115 View Revisions
2018-06-28 15:17 Thaddy de Koning Note Edited: 0109115 View Revisions
2018-06-28 15:20 Thaddy de Koning Note Edited: 0109115 View Revisions
2018-07-09 01:21 VaalKIA Note Added: 0109296
2018-07-09 01:25 VaalKIA Note Edited: 0109296 View Revisions
2018-07-09 01:25 VaalKIA Note Edited: 0109296 View Revisions
2018-07-09 01:27 VaalKIA Note Edited: 0109296 View Revisions
2018-07-09 01:28 VaalKIA Note Edited: 0109296 View Revisions
2018-07-09 01:29 VaalKIA Note Edited: 0109296 View Revisions
2018-07-09 01:50 VaalKIA Note Edited: 0109296 View Revisions
2018-07-09 01:50 VaalKIA File Added: testbugobjfpc_answer.pas
2018-07-09 07:37 Thaddy de Koning Note Added: 0109300
2018-07-09 07:49 Thaddy de Koning Note Edited: 0109300 View Revisions
2018-07-09 08:18 Thaddy de Koning Note Edited: 0109300 View Revisions
2018-07-09 10:01 VaalKIA Note Added: 0109303
2018-07-09 10:08 VaalKIA Note Edited: 0109303 View Revisions
2018-07-09 10:16 VaalKIA Note Edited: 0109303 View Revisions
2018-07-09 15:07 Cyrax Tag Attached: generics
2018-07-09 15:28 Thaddy de Koning Note Added: 0109316
2018-07-09 15:32 Thaddy de Koning Note Edited: 0109316 View Revisions
2018-07-09 15:37 Thaddy de Koning Note Edited: 0109316 View Revisions
2018-07-09 15:40 Thaddy de Koning Note Edited: 0109316 View Revisions
2018-07-09 22:11 VaalKIA File Added: EmptyGeneric.pas
2018-07-09 22:30 VaalKIA File Added: SimpleGeneric.pas
2018-07-09 22:39 VaalKIA Note Added: 0109319
2018-07-09 22:57 VaalKIA Note Added: 0109320
2018-07-09 23:18 VaalKIA File Added: IteratorWithoutGeneric.pas
2018-07-09 23:21 VaalKIA Note Edited: 0109320 View Revisions
2018-07-09 23:27 VaalKIA Note Edited: 0109320 View Revisions
2018-07-10 10:02 Thaddy de Koning Note Added: 0109331
2018-07-10 11:11 Michael Van Canneyt Note Added: 0109335
2018-07-10 11:11 Michael Van Canneyt Status new => resolved
2018-07-10 11:11 Michael Van Canneyt Resolution open => no change required
2018-07-10 11:11 Michael Van Canneyt Assigned To => Michael Van Canneyt
2018-07-10 11:33 Michael Van Canneyt Relationship added related to 0033960
2018-07-10 12:15 VaalKIA Note Added: 0109340
2018-07-10 12:15 VaalKIA Status resolved => feedback
2018-07-10 12:15 VaalKIA Resolution no change required => reopened
2018-07-10 12:23 Michael Van Canneyt Note Added: 0109341
2018-07-10 12:23 Michael Van Canneyt Status feedback => resolved
2018-07-10 12:23 Michael Van Canneyt Resolution reopened => no change required