D's reflection capabilities can be used to perform some checks that the lint tools are needed to perform for the C or C++ code. Here, we'll implement the code to warn the user at compile time whether their class has an unmarked virtual function.
Let's execute the following steps to implement a custom lint-style check for virtual functions:
enum
called Virtual
to use as the annotation to silence the warning.__traits(derivedMembers, Class)
to get all the members, excluding the base class members.__traits(isVirtualMethod, member)
to determine whether it is a virtual function.__traits(getAttributes, member)
in a loop with static if(is(Virtual))
to look for the annotation.pragma(msg)
to warn the user.check
function. The user may check this flag with static assert
to turn the warning into an error. Alternatively, the user can write enum virtualPassed = virtualCheck!item_of_interest;
to get warnings without errors.The code is as follows:
// the UDA we should put on authorized virtuals enum Virtual; // alias helper for looping over members alias helper(alias T) = T; // Helper function to test for presence of @Virtual bool isAuthorizedVirtual(alias member)() { foreach(attr; __traits(getAttributes, member)) if(is(attr == Virtual)) return true; return false; } // Loop over all members, looking for classes to drill // into and virtual functions to test. bool virtualCheck(alias T)() { bool passes = true; foreach(memberName; __traits(derivedMembers, T)) { static if(memberName.length) { alias member = helper!(__traits(getMember, T, memberName)); // drill down into classes static if(is(member == class)) passes = passes && virtualCheck!member; // check overloaded functions (if any) for // unmarked virtuals foreach(overload; __traits(getOverloads, T, memberName)) static if(__traits(isVirtualMethod, overload)) { static if(!isAuthorizedVirtual!overload) { // and warn if we find any pragma(msg, T.stringof ~ "." ~ memberName ~ " " ~ typeof(overload).stringof ~ " is virtual"); passes = false; } } } } return passes; } class Test { @Virtual void foo() {} // specifically marked, ok void foo(int) {} // virtual but not marked = problem final void f() {} // final, ok } // We'll use static assert to run the test and cause a compile // error if any tests fail. static assert(virtualCheck!(mixin(__MODULE__))); // test all classes in the module
The following is the compilation result of the preceding code:
Test.foo void(int) is virtual virt.d(51): Error: static assert (virtualCheck!(virt)) is false
D's reflection gives us access to other information about items beyond their name and type. We can also retrieve other details, such as whether a function is virtual, its calling convention, and other attributes.
The rest of the implementation is a fairly straightforward application of what we already learned. We use compile-time reflection to drill down into all members of the module, check the information we're interested in, and then use pragma(msg, "string")
to print the warning, if necessary. Since it is currently impossible to get the exact file and line numbers of a declaration with reflection, we instead print the full name and type so that the user can identify the function from the message. Finally, we return a flag instead of using the static assert
statement in the test so that compilation doesn't fail immediately with the first note.
3.14.145.82