From 79fd6aac7956179b300c43f3320347ce0c1fe46b Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Mon, 23 Nov 2015 22:37:46 -0500 Subject: [PATCH] Enable getSymbolsByUDA to retrieve private members. Resolves #15335: getSymbolsByUDA fails if type has private members. Generous (ab)use of mixins allows getSymbolsByUDA to reference symbols without trying to access them, allowing it to inspect private members without failing. Testing this required private symbols defined outside of std.traits. This adds std.internal.test.uda, which defines a struct containing private members that is used in a unittest added to std.traits. --- std/internal/test/uda.d | 16 ++++++++++++++++ std/traits.d | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 std/internal/test/uda.d diff --git a/std/internal/test/uda.d b/std/internal/test/uda.d new file mode 100644 index 00000000000..88e3a1b5ae9 --- /dev/null +++ b/std/internal/test/uda.d @@ -0,0 +1,16 @@ +/** +For testing only. +Provides a struct with UDA's defined in an external module. +Useful for validating behavior with member privacy. +*/ +module std.internal.test.uda; + +enum Attr; + +struct HasPrivateMembers +{ + @Attr int a; + int b; + @Attr private int c; + private int d; +} diff --git a/std/traits.d b/std/traits.d index e9641776771..122dd6b321b 100644 --- a/std/traits.d +++ b/std/traits.d @@ -6727,14 +6727,29 @@ unittest * This is not recursive; it will not search for symbols within symbols such as * nested structs or unions. */ -template getSymbolsByUDA(alias symbol, alias attribute) -{ - import std.typetuple : Filter, staticMap, TypeTuple; +template getSymbolsByUDA(alias symbol, alias attribute) { + import std.string : format; + import std.meta : AliasSeq, Filter; + + // translate a list of strings into symbols. mixing in the entire alias + // avoids trying to access the symbol, which could cause a privacy violation + template toSymbols(names...) { + static if (names.length == 1) + mixin("alias toSymbols = AliasSeq!(symbol.%s);".format(names[0])); + else + mixin("alias toSymbols = AliasSeq!(symbol.%s, toSymbols!(names[1..$]));" + .format(names[0])); + } + + enum hasSpecificUDA(string name) = mixin("hasUDA!(symbol.%s, attribute)".format(name)); + + alias membersWithUDA = toSymbols!(Filter!(hasSpecificUDA, __traits(allMembers, symbol))); - static enum hasSpecificUDA(alias S) = hasUDA!(S, attribute); - alias StringToSymbol(alias Name) = Identity!(__traits(getMember, symbol, Name)); - alias getSymbolsByUDA = Filter!(hasSpecificUDA, TypeTuple!(symbol, - staticMap!(StringToSymbol, __traits(allMembers, symbol)))); + // if the symbol itself has the UDA, tack it on to the front of the list + static if (hasUDA!(symbol, attribute)) + alias getSymbolsByUDA = AliasSeq!(symbol, membersWithUDA); + else + alias getSymbolsByUDA = membersWithUDA; } /// @@ -6794,6 +6809,16 @@ unittest static assert(getSymbolsByUDA!(C, UDA)[1].stringof == "d"); } +// #15335: getSymbolsByUDA fails if type has private members +unittest +{ + // HasPrivateMembers has, well, private members, one of which has a UDA. + import std.internal.test.uda; + static assert(getSymbolsByUDA!(HasPrivateMembers, Attr).length == 2); + static assert(hasUDA!(getSymbolsByUDA!(HasPrivateMembers, Attr)[0], Attr)); + static assert(hasUDA!(getSymbolsByUDA!(HasPrivateMembers, Attr)[1], Attr)); +} + /** Returns: $(D true) iff all types $(D T) are the same. */