[Estimated Reading Time: 4 minutes]

Yesterday I found myself having to write some code that would never be used in order to co-erce the compiler into not complaining that something would not be used when in fact it was.

Something I have learned over the years is that hints and warnings are useful guides to code quality. That being the case, I make a point of ensuring that in (final) code, all hints and warnings are addressed, so that if any new hints or warnings appear, they are a useful “Red Flag” that warrants investigation.

As soon as you accept that “1 or 2 hints/warnings” may be tolerated, you might as well give up on them as any sort of useful indicator at all, because those “1 or 2” will quickly become “5 or 10” then “10 or 20” etc etc.

Hints and warning often crop up during, for example, refactoring sessions, as variables get removed, procedures become redundant etc etc. Hints about unused variables and unused procedures then become even more useful because they tell me what I can now remove from my refactored code.

Except yesterday, when the compiler “HINT”ed me that I now had a procedure that wasn’t being used when I knew for a fact, and could demonstrate, that it was!

The situation was a little odd, I grant you. I had two overloaded forms of a private method, one of which was in turn called via a public method (but which I didn’t wish to expose publicly directly itself) and the other which was in turn called by another private method, but indirectly.

To borrow a feature from Raymond Chen’s blog:

Pre-emptive Nitpickers Corner: I am not interested in how what I am about to show might be done differently. There are aspects of the class that are not directly relevant to the issue at hand which influence how the code ended up this way. The implementation details are not the issue; the behaviour from the compiler is what is under examination here.

WIth that out of the way, here are the salient aspects of the class:

TFoo = class
  private
    procedure Call(aProc: TNotifyEvent);
    procedure InternalDelete; overload;
    procedure InternalDelete(Sender: TObject); overload;
  public
    procedure Delete;
  end;

The public Delete method calls InternalDelete(), so InternalDelete() is used.

The private InternalDelete() method ends up calling Call(InternalDelete) and there are very good reasons why it doesn’t simply call it directly itself. You can see from the declaration of the Call() method that the “InternalDelete” referenced here is the InternalDelete(Sender) method. So InternalDelete(Sender) is also used.

However, the compiler insists on hinting that InternalDelete(Sender) is not used. The fact that it is “referenced” isn’t enough for the compiler to consider it “used”.

How to deal with this ?

Well, there are (at least) two ways to skin this particular cat, but let’s look at the most interesting and fun way first. With a compiler this easily fooled I thought I would see if I could use that foolishness to solve the “problem”.

What I needed was someway of making the compiler see that I was calling this method. A simple solution is to add an initialization section to the unit containing the class, instantiate the class and call the method (exploiting the fact that “private” class members are only private outside the unit in which they are declared):

initialization
  with TFoo.Create do
  begin
    InternalDelete(NIL);
    Free;
  end;

Another Pre-emptive Nitpickers Corner: This is one of those cases where “with” is entirely safe and entirely harmless. If you can’t deal with the fact that such cases exist and have a dogmatic-bordering-on-religious objection to the use of with please keep it to yourself. It is not the subject under discussion. πŸ™‚

Anyhoo, sure enough, this is enough to satisfy the compiler that the method is in fact used and so it now keeps quiet about hinting that it is not. But the InternalDelete(Sender) method is not designed to be called with a NIL object reference so this code actually causes a crash in the real code. We could deal with that by introducing some redundant safety checks within the InternalDelete() method (redundant because those safety checks are applied where necessary by the methods that call the internal method in the first place). But so far we haven’t really taken advantage of any foolishness in the compiler, so it seems a bit premature to go adding more foolishness of our own.

We only need the compiler to think the method is called, but we don’t want the code that does the calling to ever be executed. The simplest way of leaving code in place that does nothing is to put it inside a conditional flow where the condition is never met (a comment isn’t “code”, so “commenting out” does not fit the criteria).

So let’s try that:

initialization
  if FALSE then with TFoo.Create do
  begin
    InternalDelete(NIL);
    Free;
  end;

Surely the compiler is smart enough to figure out that this code is a no-op and won’t even bother compiling it, and thus our erroneous hint re-surfaces ?

Actually, no. This is enough to fool the compiler and thus, assured that this code will never actually be called we can get really dirty and dispense with the “with” (do I hear cheers at the back there ? πŸ™‚ ):

initialization
  if FALSE then TFoo.Create.InternalDelete(NIL);

As much fun as it might be to blind-side the compiler like this, you might rail at such skulduggery, which brings us to the second way of dealing with the situation: Disabling hints on the specific procedure in question.

TFoo = class
  private
    procedure Call(aProc: TNotifyEvent);
    procedure InternalDelete; overload;
  {$hints OFF}
    procedure InternalDelete(Sender: TObject); overload;
  {$hints ON}
  public
    procedure Delete;
  end;

The trouble is, this means that if ever the code is refactored and the InternalDelete(Sender) method truly does become unused, you will never find out about it. This is also the case with the sneaky no-op initialization code method, but the mechanism required to deal with this potential future problem is going to be messy, involving a custom define to stop this or any similar hint suppression:

TFoo = class
  private
    procedure Call(aProc: TNotifyEvent);
    procedure InternalDelete; overload;
  {$ifNdef ALLHINTS} {$hints OFF} {$endif}
    procedure InternalDelete(Sender: TObject); overload;
  {$hints ON}
  public
    procedure Delete;
  end;

There is a whole separate discussion to be had about hint suppression and how to ensure that if the compiler behaviours in this area ever change, how those changes might affect any technique used to suppress the erroneous hint in the meantime.

But I shall deal with those if and when they arise, but with the way Delphi development has been going lately, I shan’t hold my breath waiting for the compiler to be fixed in this area.

The only thing left for me to check is whether hints can be suppressed on individual procedures within a class declaration in Delphi 7 (my minimum to-be-supported version). I have a vague recollection that at some point warnings could only be turned off for an entire class in older versions. Whether that changed pre or post Delphi 7 and whether it applied to hints as well as warnings, I have yet to verify.

10 thoughts on “Hint: Using a Wrong to Make a Wrong Wrong Right”

  1. That is an interesting example… I wanted to test whether FPC would show the hint or not, but it won’t even compile your example, because it tries to use the “InternalDelete” method instead of the “InternalDelete(Sender: TNotifyEvent)” one…

    Correction: it will compile if I change the order of the “InternalDelete” declarations and only in mode ObjFPC (thus the “InternalDelete” needs to be prefixed with a “@”). But nevertheless no hint πŸ˜€

    I will also need to test what Delphi(!) does if “InternalDelete” would be a function (remember: the function name is a synonym for “Result”).

    So in this regard: thank you for sharing this.

    And bonus points for mentioning Raymond Chen’s blog πŸ˜€

    Regards,
    Sven

    1. Oh, FPC is such nitpicky bastard… proting my Delphi code to it i was amazed by my code, ot refused to compile in default ObjFpc mode

      Function SomeClass.Items(const Items:ItemsType);

      πŸ™‚

      1. In ObjFPC mode you ure not allowed to name parameters or local variables the same as members of the class. This was introduced to avoid confusion of (newbie) developers. Sadly this wasn’t consequently applied when support for nested types was added…

        Regards,
        Sven

  2. And there was me thinking Hints and Warnings make a programmer’s life easier.

  3. “one or two warnings” is not a reasonable attitude. “This and that kind of warnings” could be – but Delphi gives little granularity about that…

  4. “This is one of those cases where β€œwith” is entirely safe ”

    No. With-do-begin is not! with-do-try-finally is though.

  5. procedure TFoo.Call(aProc: TNotifyEvent);
    Begin
    {$IfOpt D+}
    If nil = Self then Self.InternalDelete(Self);
    {$EndIf}
    …. the real code…..
    end;

    1. “What makes you think it’s a “with” ?
      -: Well, it turned me into a newt!
      “A newt ?”
      -: … I got better.
      BURN HIM ANYWAY!

      Thanks for the Python moment !! πŸ˜‰

  6. I never saw the point of reducing hints and warnings to zero by source-only means, partially because some Delphi versions couldn’t suppress certain hints specifically (like the .NET or BCB related ones). I therefore long since moved to a scheme of filtering compiler output with known warnings to see what pops up new.

Comments are closed.