Sign in Join

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

Mailing lists / fpc-devel / 2025-10

Question about generics / what is checked, and what not -- and why?

6 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

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

Martin Frb

Just out of curiosity....

I understand when the generic is parsed, the compiler makes checks that it compiles. So certain things must be known.
Some things the compiler happily assumes to be ok.

Given an non-constrained param, then (in code) any member is assumed to be ok.
But in declarations, members of the param can only be used if the compiler knows them.

   generic TGenA<T1,T2> = class
     X: T1;
     Y: T2.unknown;
   end;

What reason may there be to reject the line for Y?

"X: T1" shows that the compiler can continue with a field of unknown type (because T1 is not known, until a specialize happens). "Y" would be exactly as unknown as "X", or wouldn't it?

Also, in code you can do
     n := T2.foo.bar;
and this can later be a field or a method, or even a constant....

The  reason why I got curious is that, I can imagine (and that may be to limited) 2 reasons:
- technical => the compiler can't do it (but I don't see how, since "T2.unknown" is as indeterminable as "T1"
  (the compiler just needs to parse the members, as it already does when it compiles code in the begin...end)

- checking early for errors. Ensuring that the generic can be specialized   I.e. ensuring that "T2" has that field.

The 2nd option (if it is) does however IMHO not ensure anything. (Ignoring that error can still happen in the begin..end), they can also still happen in the declaration when being specialized. E.g. when having

   generic TGenA<T2: TMyClass> = class // TMyClass has  "public type
unknown = byte;"
     Y: T2.unknown;
   end;

Then that does guarantee (almost) nothing.
Since TGen can be specialized with any sublclass of TMyClass, any such subclass may redefine or hide what "unknown" is.
So when specialising, it can still fail. (it only can't be completely absent, as then the base class will provide).

So what did I miss, what is the gain of that check?

Example
"TX2" will fail, because a field can not be used as type. (the presence in the base class does not prevent this)

program Project1; {$mode objfpc}
uses Unit1;
type
   TClassA1 = class(TClassA)
   private type    T1 = byte;
   end;
   TClassA2 = class(TClassA)
     T1: integer;
   end;
   TX1 = specialize TGenA<TClassA1>;
//  TX2 = specialize TGenA<TClassA2>;

begin {} end.

unit Unit1; {$mode ObjFPC}
interface
type
   TClassA = class
   private type     T1 = record a: byte; end;
   end;
   generic TGenA<T_A: TClassA> = class
     F1: T_A.T1;
   end;
implementation
end.

Reply by email

Hairy Pixels
to Martin Frb

On Oct 19, 2025 at 10:27:50 PM, Martin Frb via fpc-devel <

Show quoted text 14 lines
wrote: Given an non-constrained param, then (in code) any member is assumed to be ok. But in declarations, members of the param can only be used if the compiler knows them. generic TGenA<T1,T2> = class X: T1; Y: T2.unknown; end; What reason may there be to reject the line for Y?

I agree the compiler can not know that T2.unknown is a valid member before specializing so it should be valid syntax.

I think it's just a limitation in the parser. Ideally some subset of typeless syntax should be allowed during the initial parse and only only validated once the the type is specialized.

Reply by email

Martin Frb
to Hairy Pixels
Hairy Pixels wrote 20 lines
On Oct 19, 2025 at 10:27:50 PM, Martin Frb via fpc-devel wrote: > Given an non-constrained param, then (in code) any member is assumed to > be ok. > But in declarations, members of the param can only be used if the > compiler knows them. > >   generic TGenA<T1,T2> = class >     X: T1; >     Y: T2.unknown; >   end; > > What reason may there be to reject the line for Y? I agree the compiler can not know that T2.unknown is a valid member before specializing so it should be valid syntax. I think it's just a limitation in the parser. Ideally some subset of typeless syntax should be allowed during the initial parse and only only validated once the the type is specialized.

Well, yes, it may be unimplemented in the parser... And maybe that is all of it.

Maybe historically it would have been a complex task to add. Yet, currently it would seem to be that the parser just needs to skip any amount of "dot identifier"?

I though, if it may be some internal representation (nodes) generated by the parser, that must later (at specialization) match. But then I can have the base class

{advanced records}
TClassA = class
   public type record
     public type TUnknown: record end;
   end;
end;
and replace it by
TClassA = class
   public type class
     public type TUnknown: byte;
   end;
end;

which surely generates something different?

But then, of course I don't know the compiler internals... So hence I am asking...

Reply by email

Hairy Pixels
to Martin Frb

On Oct 19, 2025 at 10:50:42 PM, Martin Frb via fpc-devel <

Show quoted text 9 lines
wrote: Well, yes, it may be unimplemented in the parser... And maybe that is all of it. Maybe historically it would have been a complex task to add. Yet, currently it would seem to be that the parser just needs to skip any amount of "dot identifier"?

Ideally the parser catches syntax errors before specialization. FPC has an undefined type so upon encountering this you would be an "undefined sub tree expression parser" which could handle all possible expressions which originated from the identifier.

FPC just stores the raw tokens so all the compiler really needs to know is where the start and end of the generic type/proc are.

Reply by email

Martin Frb
to Hairy Pixels
Hairy Pixels wrote 10 lines
On Oct 19, 2025 at 10:50:42 PM, Martin Frb via fpc-devel wrote: > Well, yes, it may be unimplemented in the parser... And maybe that is > all of it. > > Maybe historically it would have been a complex task to add. Yet, > currently it would seem to be that the parser just needs to skip any > amount of "dot identifier"? Ideally the parser catches syntax errors before specialization.

Yes, but as you can see from my example, even if a constrained type was checked, the sub-class that is given to specialization may contain something different. And with nested declarations, that can even mean that a member that is accessible via the base class, does no longer exist in the subclass, i.e the nested type from the base class is hidden, and the "reintroduced" type does not have the same tree.

Originally I thought, well ok, it allows better checks. But then I discovered, that this isn't the case.

Reply by email

Sven Barth
to Martin Frb: Well, yes, it may be unimplemented in the parser... And maybe that is all of it. Maybe…

Martin Frb via fpc-devel schrieb am So.,
19. Okt. 2025, 17:50:

Show quoted text 30 lines
On 19/10/2025 17:33, Hairy Pixels via fpc-devel wrote: On Oct 19, 2025 at 10:27:50 PM, Martin Frb via fpc-devel < > wrote: > Given an non-constrained param, then (in code) any member is assumed to > be ok. > But in declarations, members of the param can only be used if the > compiler knows them. > > generic TGenA<T1,T2> = class > X: T1; > Y: T2.unknown; > end; > > What reason may there be to reject the line for Y? > I agree the compiler can not know that T2.unknown is a valid member before specializing so it should be valid syntax. I think it's just a limitation in the parser. Ideally some subset of typeless syntax should be allowed during the initial parse and only only validated once the the type is specialized. Well, yes, it may be unimplemented in the parser... And maybe that is all of it.

That's precisely it, nothing more, nothing less. These corner cases are fixed on a case-by-case base, cause the locations where a fix needs to happen are easier found that way than trying to understand and fix the whole parser.

So if you want to see this fixed, then please report a bug.

Regards,
Sven

Reply by email