Static specifier and overriding class methods

I’ve encountered an interesting problem while overriding virtual class method. Consider the next program:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TBase = class
    class function GetName: string; virtual;
    class procedure Test; static;
  end;

  TChild = class(TBase)
    class function GetName: string; override;
  end;

class function TBase.GetName: string;
begin
  Result:= 'TBase';
end;

class procedure TBase.Test;
begin
  Writeln(GetName);
end;

class function TChild.GetName: string;
begin
  Result:= 'TChild';
end;

begin
  TBase.Test;
  TChild.Test;
  Readln;
end.

Despite the GetName function is overridden in TChild class the output is
StaticBug

The bug is static specifier of the TBase.Test method. After the specifier is removed the output is as expected:
StaticOut

The reason of the above compiler behavior is clear – with static specifier in TBase.Test method declaration the compiler (Delphi XE was used) receives no class information and statically binds TBase.GetName class method. The bad thing is that the compiler is silent about the problem and issues no warning.

Advertisements

9 thoughts on “Static specifier and overriding class methods

  1. Class static methods have no “self” referring to the current class, differently from regular class methods. So this is as expected, although it might be surprising. Not sure how the compiler should figure out and issue a warning.

    • On the second thought, the case maybe considered as a compile time error. The compiler should treat the case somehow – either as an error or as a warning.

  2. Well, Marco – calling a virtual class method from a static class method does not really make sense – the method call will be hard-wired – so the compiler could issue a hint or warning on the GetName call inside TBase.Test, something like:

    Warning: static class method TBase.Test tries to call virtual class method GetName – call will always resolve to TBase.Test

    This gives a que to the programmer to change TBase.Test to a non-static class method or to remove the virtual from GetName – depending on what behavior is desired.

    • I doubt the above compiler behavior can ever be desirable. I’ve encountered the case while refactoring static class method and moving some code from it into a virtual class method to be overridden in a derived class. I would prefer the compiler to treat the case as a compile time error.

    • I understand and agree that this really doesn’t make sense, but I’m not 100% sure this isn’t legitimate. And it if is legitimate, either there should be no warning or there should be a way to get rid of it… Not saying I disagree with the request of a way for the compiler to let developers know they are doing something really odd.

  3. >Class static methods have no โ€œselfโ€ referring to the current class, differently from regular class
    >methods.

    Delphi’s terminology sometimes leaves my head spinning. In other languages a static method would have no self referring to the object, and class methods would at least have a class referring to the current class. If Delphi’s class static method really had no self referring to the current class, then I don’t understand how it would compile. I tried reproducing the example in a language with explicit self (Python) declaring the method as static. It sensibly noted that there was no function GetName visible within Test. When I made it a class method so that there would be a class reference and modified the call accordingly, I was able to get the code to run and it did indeed return what one would logically expect:

    TBase
    TChild

    as output.

    >So this is as expected, although it might be surprising.
    I found the Delphi output both surprising and unexpected. It makes no sense to me how it compiles, and moving past that, why it generates the output it does.

    Of course, the biggest question is why a language with first-class functions ever needs to use static methods in the first place. ๐Ÿ™‚ That’s a Java/C# loophole because they can’t admit that you can’t squeeze every problem into an object-oriented paradigm and “sometimes a function is just a function”.

    >The reason of the above compiler behavior is clear

    Clear as mud. ๐Ÿ™‚
    > โ€“ with static specifier in TBase.Test method declaration the compiler (Delphi XE was used) receives no
    >class information

    Are you sure? According to EMBT’s documentation about “class static” methods:

    >They still have access to class fields, class properties, and class methods.

    You can’t do that without class information.

    >and statically binds TBase.GetName class method.

    Why? If it had no class information it should fail because there’s no GetName class within scope.

    • Static class methods in Delphi have no hidden class “Self” argument (don’t be confused with object methods that also can have “static” specifier but always have hidden “Self” argument referencing class instance; the “Self” argument in a class method references global class metadata, something different).
      Since there is no “Self” argument in “TBase.Test” there is no reference to vtable to implement polymorphic behavior, and the compiler really have two alternatives – to treat the case as a bug or call “TBase.GetName” statically.
      That is clear only if you understand Delphi internals.

    • There is no implicit Self: TClass parameter, so there is no dynamic, run-time class information.

      However, there is compile-time, static class information – the compiler knows that the Test method belongs to the TBase class, so it statically calls the TBase.GetName method, even though it is declared virtual.

      This is most likely not what the programmer intended, so it would make sense to generate a warning or error. To fix the warning, the programmer could use an explicit call, if that is what he really wants to do:
      writeln(TBase.GetName);

  4. I see a simple explanation of why this hapens.
    When calling TChild.Test delphi tries to find “Test” method in TChild class declaration. As it can’t find it there it then searches the TChild parrent class declaration which is TBase.
    So when you cal TChild.Test the TBase.Test function is actually being called becouse TChild doesn’t have Test function implemented and TBase si first parented class which have such function implemented.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s