Recent

Author Topic: Overzealous implicit conversions (possible type checking bug)?  (Read 308 times)

Khrys

  • Sr. Member
  • ****
  • Posts: 342
Overzealous implicit conversions (possible type checking bug)?
« on: September 18, 2025, 08:06:12 am »
Yesterday I was caught off-guard when code containing a typo did not fail to compile, but instead silently led to a nonsensical chain of implicit conversions.

Here's a minimal reproducible example (record/class fields and return values omitted for brevity; the bug still happens regardless):

Code: Pascal  [Select][+][-]
  1. program Test;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. type
  6.   TEnum = (_);
  7.   TRecord = record end;
  8.   TClazz = class end;
  9.  
  10. operator :=(Self: TEnum): String;
  11. begin
  12.   WriteLn('<assign> enum -> String');
  13. end;
  14.  
  15. operator :=(Self: Pointer): TRecord;
  16. begin
  17.   WriteLn('<assign> Pointer -> record');
  18. end;
  19.  
  20. operator =(const Self: TRecord; const Other: String): Boolean;
  21. begin
  22.   WriteLn('<equal> (record = String)');
  23. end;
  24.  
  25. function Main(): Boolean;
  26. var
  27.   Clazz: TClazz;
  28.   Enum: TEnum;
  29. begin
  30.   Result := (Clazz = Enum); // In my actual code I intended to write `Clazz.Enum = Enum` but forgot to access the field
  31. end;
  32.  
  33. begin
  34.   Main();
  35. end.

In my opinion, this code should fail to compile due to the comparison at line 30.  TClazz  defines no methods or operators, so it should be a dead-end.
The compiler (trunk) disagrees, though:

Quote
$ fpc test.pas && ./test
<assign> enum -> String
<assign> Pointer -> record
<equal> (record = String)

Apparently  TClazz = Pointer  (???)  so that  Clazz  can be converted to  TRecord, which in turn can be compared to  String  (leading to  Enum  being stringified). Of course, it's so obvious...  :o



The implicit conversion from a class type to  Pointer  here seems like a bug to me.

On its own it makes sense (class instances are references after all), but I think it shouldn't apply in this situation. Normally at most one implicit conversion may be inserted by the compiler, but here it's two  (TClazz  →  Pointer  →  TRecord). Multiple implicit conversions in general are disallowed in most (all?) languages (and for good reason - imagine nonsense like  Boolean  →  PtrInt  →  Pointer  →  TForm).

What do you think?

ALLIGATOR

  • Sr. Member
  • ****
  • Posts: 283
  • I use FPC [main] 💪🐯💪
Re: Overzealous implicit conversions (possible type checking bug)?
« Reply #1 on: September 18, 2025, 10:16:09 am »
UPD: Although, of course, it's not quite the same thing. You have two transformations, I have one.

Yes, it looks creepy

But, by the way, if you try to rewrite it in Delphi style, you'll get roughly the same behavior, and Delphi itself (12.1CE) will compile it

Code: Pascal  [Select][+][-]
  1. program Test;
  2. {$ifdef FPC}{$mode delphi}{$endif}
  3.  
  4. type
  5.   TR2 = record
  6.     class operator Implicit(A: TR2): String;
  7.     class operator Equal(A: TR2; B: String): Boolean;
  8.   end;
  9.   TR1 = record
  10.     class operator Implicit(A: TR1): TR2;
  11.   end;
  12.  
  13.  
  14. class operator TR1.Implicit(A: TR1): TR2;
  15. begin
  16.   WriteLn('<assign> TR1 -> TR2');
  17. end;
  18. class operator TR2.Implicit(A: TR2): String;
  19. begin
  20.   WriteLn('<assign> TR2 -> String');
  21. end;
  22.  
  23. class operator TR2.Equal(A: TR2; B: String): Boolean;
  24. begin
  25.   WriteLn('<equal> (TR2 = String)');
  26. end;
  27.  
  28. var
  29.   R1: TR1;
  30.   R2: TR2;
  31.  
  32. begin
  33.   WriteLn (R1 = R2);
  34.   ReadLn;
  35. end.

FPC & D12.1CE:
Code: Pascal  [Select][+][-]
  1. <assign> TR1 -> TR2
  2. <assign> TR2 -> String
  3. <equal> (TR2 = String)
« Last Edit: September 18, 2025, 10:28:32 am by ALLIGATOR »
I may seem rude - please don't take it personally

 

TinyPortal © 2005-2018