www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Idea to verify virtual/final methods

reply Jacob Carlborg <doob me.com> writes:
In the thread "Slow performance compared to C++, ideas?" Manu is arguing 
that methods should be non-virtual by default. The problem he is having 
is that developers will forget to declare methods as final.

This issue got me think if there's a way to automatically detect that a 
method is virtual that shouldn't be.

If one modifies druntime to take advantage out of RTInfo. If I 
understand everything correctly RTInfo will be instantiated once for 
each user defined type. With that and the help of a UDA you can check 
that all methods that should be final really are final. Something like:

struct virtual {} // the UDA

class Foo
{
     void a () {} // static assert, not declared as  virtual
      virtual void b () {} // ok
      virtual final void c () {} // static assert, declared as final and 
 virtual
     final d () {} // ok
}

checkVirtual!(Foo);

import std.typetuple;

void checkVirtual (T) ()
{
     static if (!is(T == class))
         return;

     foreach (m ; __traits(derivedMembers, T))
     {
         alias TypeTuple!(__traits(getAttributes, mixin("T." ~ m))) attrs;
         enum methodName = T.stringof ~ "." ~ m.stringof;

         static if (staticIndexOf!(virtual, attrs) != -1)
         {
             static if (!__traits(isVirtualMethod, mixin("T." ~ m)))
                 static assert (false, "Method " ~ methodName ~ " marked 
as  virtual is not virtual");
         }

         else
         {
             static if (__traits(isVirtualMethod, mixin("T." ~ m)))
                 static assert (false, "Method " ~ methodName ~ " is 
virtual but not marked as  virtual");
         }
     }
}

-- 
/Jacob Carlborg
Jun 04 2013
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jacob Carlborg:

 struct virtual {} // the UDA

 class Foo
 {
     void a () {} // static assert, not declared as  virtual
      virtual void b () {} // ok
      virtual final void c () {} // static assert, declared as 
 final and  virtual
     final d () {} // ok
 }

 checkVirtual!(Foo);
Is it possible to also write: checkVirtual!myModuleName; And verify all the classes in a module? Bye, bearophile
Jun 04 2013
parent reply Jacob Carlborg <doob me.com> writes:
On 2013-06-04 12:45, bearophile wrote:

 Is it possible to also write:

 checkVirtual!myModuleName;

 And verify all the classes in a module?
Not with this version. I tried that first but I couldn't get __traits(allMembers) to work on a module. I think by overriding RTInfo it will be more automatic. -- /Jacob Carlborg
Jun 04 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jacob Carlborg:

 Is it possible to also write:

 checkVirtual!myModuleName;

 And verify all the classes in a module?
Or something like: checkVirtual!(__MODULE__);
 Not with this version. I tried that first but I couldn't get 
 __traits(allMembers) to work on a module. I think by overriding 
 RTInfo it will be more automatic.
If there are some things that don't give you enough static introspection to perform that, then it's worth adding that missing static introspection to D language. Bye, bearophile
Jun 04 2013
parent Jacob Carlborg <doob me.com> writes:
On 2013-06-04 13:13, bearophile wrote:

 If there are some things that don't give you enough static introspection
 to perform that, then it's worth adding that missing static
 introspection to D language.
It actually works. My mistake. Here's a version that works with modules as well: void checkVirtual (alias T) () { static if (is(T == class)) { foreach (m ; __traits(derivedMembers, T)) { alias TypeTuple!(__traits(getAttributes, mixin("T." ~ m))) attrs; enum methodName = T.stringof ~ "." ~ m.stringof; static if (staticIndexOf!(virtual, attrs) != -1) { static if (!__traits(isVirtualMethod, mixin("T." ~ m))) static assert (false, "Method " ~ methodName ~ " marked as virtual is not virtual"); } else { static if (__traits(isVirtualMethod, mixin("T." ~ m))) static assert (false, "Method " ~ methodName ~ " is virtual but not marked as virtual"); } } } else { foreach (m ; __traits(allMembers, T)) { mixin("alias " ~ m ~ " member;"); static if (is(member == class)) checkVirtual!(member); } } } -- /Jacob Carlborg
Jun 04 2013
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On 4 June 2013 18:23, Jacob Carlborg <doob me.com> wrote:

 In the thread "Slow performance compared to C++, ideas?" Manu is arguing
 that methods should be non-virtual by default. The problem he is having is
 that developers will forget to declare methods as final.

 This issue got me think if there's a way to automatically detect that a
 method is virtual that shouldn't be.

 If one modifies druntime to take advantage out of RTInfo. If I understand
 everything correctly RTInfo will be instantiated once for each user defined
 type. With that and the help of a UDA you can check that all methods that
 should be final really are final. Something like:

 struct virtual {} // the UDA

 class Foo
 {
     void a () {} // static assert, not declared as  virtual
      virtual void b () {} // ok
      virtual final void c () {} // static assert, declared as final and
  virtual
     final d () {} // ok
 }

 checkVirtual!(Foo);

 import std.typetuple;

 void checkVirtual (T) ()
 {
     static if (!is(T == class))
         return;

     foreach (m ; __traits(derivedMembers, T))
     {
         alias TypeTuple!(__traits(**getAttributes, mixin("T." ~ m)))
 attrs;
         enum methodName = T.stringof ~ "." ~ m.stringof;

         static if (staticIndexOf!(virtual, attrs) != -1)
         {
             static if (!__traits(isVirtualMethod, mixin("T." ~ m)))
                 static assert (false, "Method " ~ methodName ~ " marked as
  virtual is not virtual");
         }

         else
         {
             static if (__traits(isVirtualMethod, mixin("T." ~ m)))
                 static assert (false, "Method " ~ methodName ~ " is
 virtual but not marked as  virtual");
         }
     }
 }

 --
 /Jacob Carlborg
This is a great idea. If final-by-default is ultimately rejected, I'll definitely try this out. That said, it only solves half the problem, that is, internal programmers forgetting to mark their functions final (and assuming they also remember 'checkVirtual!' in their moules). It offers nothing to address the 3rd party library problem.
Jun 04 2013
parent Jacob Carlborg <doob me.com> writes:
On 2013-06-04 13:26, Manu wrote:

 This is a great idea. If final-by-default is ultimately rejected, I'll
 definitely try this out.
 That said, it only solves half the problem, that is, internal
 programmers forgetting to mark their functions final (and assuming they
 also remember 'checkVirtual!' in their moules).
 It offers nothing to address the 3rd party library problem.
Yes, exactly. If you override RTInfo in druntime I think it will work automatically. Currently RTInfo looks like this: template RTInfo(T) { enum RTInfo = null; } Override it to look like this: template RTInfo(T) { enum RTInfo = checkVirtual!(T); } If this doesn't work you could create a tool which imports all modules in the source code and calls the function for all modules. -- /Jacob Carlborg
Jun 04 2013
prev sibling next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Jacob Carlborg:

 struct virtual {} // the UDA

 class Foo
 {
     void a () {} // static assert, not declared as  virtual
      virtual void b () {} // ok
      virtual final void c () {} // static assert, declared as 
 final and  virtual
     final d () {} // ok
 }

 checkVirtual!(Foo);
My original purposes for UDAs was to extend the type system, as you have essentially done here. But to do that well enough the attributes need to be more active. An idea: struct Small(T) if (T.sizeof <= 16) {} Small struct Bar { int x; } Here Small get instantiated with T == Bar, and its constraint performs the active compile-time test. Bye, bearophile
Jun 04 2013
next sibling parent Jacob Carlborg <doob me.com> writes:
On 2013-06-04 15:15, bearophile wrote:

 My original purposes for UDAs was to extend the type system, as
 you have essentially done here. But to do that well enough the
 attributes need to be more active.

 An idea:

 struct Small(T) if (T.sizeof <= 16) {}

  Small struct Bar {
        int x;
 }


 Here Small get instantiated with T == Bar, and its constraint
 performs the active compile-time test.
Absolutely. My idea for AST macros, which includes integration with UDA's, could possibly solve that. https://dl.dropboxusercontent.com/u/18386187/ast_macros.html See the bottom for attributes. -- /Jacob Carlborg
Jun 04 2013
prev sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Tuesday, 4 June 2013 at 13:15:39 UTC, bearophile wrote:
 An idea:
bearophile, you're a genius. I've been trying to think of a way to do this for the last week or so and you've got it. usage: struct small(T) if(T.sizeof < 9) {} CustomCheck!small class RTTest {} object.d additions: enum CustomCtCheckResult { fail, pass } template CustomCheck(alias C) { template CustomCheck(T) { static if(__traits(compiles, C!T)) enum CustomCheck = CustomCtCheckResult.pass; else enum CustomCheck = CustomCtCheckResult.fail; } } bool doCustomChecks(T)() { if(__ctfe) { foreach(attr; __traits(getAttributes, T)) { static if(is(typeof(attr!T) == CustomCtCheckResult)) { static assert(attr!T == CustomCtCheckResult.pass); } } return true; } } template RTInfo(T) { /* other rtinfo stuff can remain */ enum customChecksPassed = doCustomChecks!T; } If we updated this to go right through the members too, we'd be really cooking. object.d(755): Error: static assert (cast(CustomCtCheckResult)0 == cast(CustomCtCheckResult)1) is false object.d(774): instantiated from here: doCustomChecks!(RTTest) minimal.d(168): instantiated from here: RTInfo!(RTTest) the last line helps find it. For members it will be trickier but there the CustomCheck template could take __FILE__ and __LINE__ as default arguments and just print them instead of static assert 0.
Jun 04 2013
parent "Adam D. Ruppe" <destructionator gmail.com> writes:
Though a downside here is you can't require a check attribute to 
be present on a class without modifying druntime. The class 
attribute could go down and check its members, but there's no way 
to error on an unchecked class without changing object.d, or at 
least a runtime check to loop through the modules and check it 
then.

It just checks ones that are there, and at that point, it isn't 
really that much different than just writing static 
assert(test.sizeof < 8); right there in the module.

so maybe not as awesome as i first thought, but still, this is an 
exciting technique and I'm sure there's more we can do with it.
Jun 04 2013
prev sibling parent reply "Namespace" <rswhite4 googlemail.com> writes:
Awesome! But I miss the filename and the line number where the 
error happened.
Jun 04 2013
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2013-06-04 15:22, Namespace wrote:
 Awesome! But I miss the filename and the line number where the error
 happened.
Hmm, I wonder if that's possible to get. -- /Jacob Carlborg
Jun 04 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
On Tuesday, 4 June 2013 at 13:25:37 UTC, Jacob Carlborg wrote:
 On 2013-06-04 15:22, Namespace wrote:
 Awesome! But I miss the filename and the line number where the 
 error
 happened.
Hmm, I wonder if that's possible to get.
I do not know how. But with that it would be really incredible.
Jun 04 2013
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2013-06-04 15:22, Namespace wrote:
 Awesome! But I miss the filename and the line number where the error
 happened.
template RTInfo(T, string mod = __MODULE__, string file = __FILE__, size_t line = __LINE__) { enum RTInfo = checkVirtual!(T, mod, file, line); } The above could work. -- /Jacob Carlborg
Jun 04 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
On Tuesday, 4 June 2013 at 13:44:49 UTC, Jacob Carlborg wrote:
 On 2013-06-04 15:22, Namespace wrote:
 Awesome! But I miss the filename and the line number where the 
 error
 happened.
template RTInfo(T, string mod = __MODULE__, string file = __FILE__, size_t line = __LINE__) { enum RTInfo = checkVirtual!(T, mod, file, line); } The above could work.
No chance without changing and rebuilding druntime?
Jun 04 2013
parent Jacob Carlborg <doob me.com> writes:
On 2013-06-04 18:43, Namespace wrote:

 No chance without changing and rebuilding druntime?
Not that I can think of right now. -- /Jacob Carlborg
Jun 04 2013