Sign in Join

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

Mailing lists / fpc-devel / 2023-03

Inconsistency in generics ?

3 messages · 2 people ·

Reply by email Subscribe to this list Your subscription

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

Martin Frb

To me the below behaviour appears inconsistent.
But before I file a bug, I want to double check, if maybe this is intention....

Tested with 3.2.3  and 3.3.1

Apparently
- when the generic TGen is compiled, it does check if a type "TRec1" and "TRec2" is in it verifies that the generic itself compiles with that type
- **but** then it allows that type to be replaced by another, if the specialized param contains such another type

So if the specialized class, can have a type of it's own choosing for "TRec1" then why insist that a random other type of that name exists when the generic is declared.
(And why reject "TRec2" as it too could be available at the time of specialization?)

Or should the specialized class "TClass1" use the global TRec1? and ignore TBase2.TRec1 ?

program testgen;
{$mode objfpc}{$H+}
type
   TRec1 = record r1: integer; end; // this will be used to check the
generic itself
   TBase1 = class
   end;

  { TBase2 }

   TBase2 = class(TBase1)
   public type
     TRec1 = record r1_b2: integer; end; // this will be used in specialize
     TRec2 = record r2_b2: integer; end;
   end;

  generic TGen<B: TBase1> = class(B)
    f1: Trec1;  // here TBase2.Rec is not known     //f2: Trec2;  //  Error: Identifier not found "Trec2"     procedure x;
  end;

   TClass1 = class(specialize TGen<TBase2>)
     procedure Bar;
   end;
procedure TGen.x; // neither works...
begin
   //f1.r1 := 2;  // fails when specialized
   //f1.r1_b2 := 2;  // fails for the generic
end;
procedure TClass1.Bar;
begin
   f1.r1_b2 := 1; // This is TBase2.Rec, and compiles with 3.2.3  and 3.3.1
end;
begin
end.

Reply by email

Sven Barth
to Martin Frb

Martin Frb via fpc-devel schrieb am Fr.,
3. März 2023, 12:55:

Show quoted text 19 lines
To me the below behaviour appears inconsistent. But before I file a bug, I want to double check, if maybe this is intention.... Tested with 3.2.3 and 3.3.1 Apparently - when the generic TGen is compiled, it does check if a type "TRec1" and "TRec2" is in scope at all. - it verifies that the generic itself compiles with that type - **but** then it allows that type to be replaced by another, if the specialized param contains such another type So if the specialized class, can have a type of it's own choosing for "TRec1" then why insist that a random other type of that name exists when the generic is declared. (And why reject "TRec2" as it too could be available at the time of specialization?)

All identifiers must be known when declaring a generic. In this case Trec1 is known thanks to the global type. Trec2 is not known (neither in its parent (at declaration time TBase1) nor globally) , so that will fail.

When you specialize with TBase2 you change the parent thus a completely new scoping situation arises and Trec1 will no longer be the global Trec1, but that of TBase2.

Considering how generics are implemented this is both as expected and as designed. Not to mention that using the generic parameter as parent is *not* officially considered a feature. It's simply not forbidden.

Regards,
Sven

Reply by email

Martin Frb
to Sven Barth
Sven Barth wrote 14 lines
All identifiers must be known when declaring a generic. In this case Trec1 is known thanks to the global type. Trec2 is not known (neither in its parent (at declaration time TBase1) nor globally) , so that will fail. When you specialize with TBase2 you change the parent thus a completely new scoping situation arises and Trec1 will no longer be the global Trec1, but that of TBase2. Considering how generics are implemented this is both as expected and as designed. Not to mention that using the generic parameter as parent is *not* officially considered a feature. It's simply not forbidden.

I got to this only while generating code to feed code-tools. So it's not that I need it to be either way.

I understand there is a set of rules to explain it all (or most of it, see "why" in code below).
But the result of those rules are somewhat "interesting"

It still feels strange....
It leads to  "TRec1"  being checked as something that it is not. If the "specialize" allows TRec1 to be something else (and something completely unrelated), then why does the generic check at all, that the non-specialized code compiles against the one seen by the generic?

type
   generic TGen<B> = class
     procedure x;
  end;
procedure TGen.x;
begin
    B.x := 1;  // works, even so the compiler has no info what B might
be (only that there is a name B)
//   B.y();  // then again, does not work - why?
end;

Reply by email