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):
program Test;
{$mode objfpc}{$H+}
type
TEnum = (_);
TRecord = record end;
TClazz = class end;
operator :=(Self: TEnum): String;
begin
WriteLn('<assign> enum -> String');
end;
operator :=(Self: Pointer): TRecord;
begin
WriteLn('<assign> Pointer -> record');
end;
operator =(const Self: TRecord; const Other: String): Boolean;
begin
WriteLn('<equal> (record = String)');
end;
function Main(): Boolean;
var
Clazz: TClazz;
Enum: TEnum;
begin
Result := (Clazz = Enum); // In my actual code I intended to write `Clazz.Enum = Enum` but forgot to access the field
end;
begin
Main();
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:
$ 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...

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?