December 24, 2020
I would expect the unittest below to pass. Could not find
any explanation in [1].

~~~typeid.d
class A {}
class B : A {}
class C : B {}

interface Q {}
class R : Q {}
class S : R {}

unittest {
   import std.stdio : writeln;

   void runtest (X, Y) ()
   {
      X x = new Y ();
      writeln ("X = ", typeid (X), ", Y = ", typeid (Y));
      assert (typeid (x) == typeid (Y));
   }

   runtest!(A, C); // pass
   runtest!(R, S); // pass
   runtest!(Q, S); // fail
   runtest!(Q, R); // would also fail
}
~~~

$ dmd -checkaction=context -unittest -main -run typeid
X = typeid.A, Y = typeid.C
X = typeid.R, Y = typeid.S
X = typeid.Q, Y = typeid.S
typeid.d(16): [unittest] typeid.Q != typeid.S
1/1 unittests FAILED

[1] https://dlang.org/spec/interface.html
December 24, 2020
Interfaces always give their own typeid, this is because they might point to an object that doesn't have RTTI (though the compiler SHOULD be able to figure that out statically, it doesn't try).

To get the dynamic type, first cast it to Object, then type id.

typeid(cast(Object) o) is typeid(WhateverClass)