1 module jumped.introspection; 2 3 import std.traits; 4 import std.algorithm.searching; 5 import std.meta; 6 7 /// Checks if a symbol has a specified attribute. 8 /// If the attribute cannot be found, it checks for attributes 9 /// on the original attribute type itself. It will keep doing this 10 /// until it cannot found anymore attributes. 11 template hasAnnotation(alias uda, alias symbol) 12 { 13 static if (!__traits(compiles, __traits(getAttributes, symbol))) 14 { 15 alias hasAnnotation = Alias!false; 16 } 17 else static if (hasUDA!(symbol, uda)) 18 { 19 alias hasAnnotation = Alias!true; 20 } 21 else 22 { 23 alias hasAnnotation = hasAnnotation!(uda, __traits(getAttributes, symbol)); 24 } 25 } 26 27 private template hasAnnotation(alias uda, alias symbol, Symbols...) 28 { 29 static if (hasAnnotation!(uda, symbol)) 30 alias hasAnnotation = Alias!true; 31 else 32 alias hasAnnotation = hasAnnotation!(uda, Symbols); 33 } 34 35 private template hasAnnotation(alias UDA) 36 { 37 alias hasAnnotation = Alias!false; 38 } 39 40 @("hasAnnotation finds all base annotation") 41 unittest 42 { 43 struct annotationA; // @suppress(dscanner.style.phobos_naming_convention) 44 45 @annotationA 46 struct annotationB; // @suppress(dscanner.style.phobos_naming_convention) 47 48 @annotationB 49 struct Hello; 50 51 static assert(hasAnnotation!(annotationB, Hello) == true); 52 } 53 54 @("hasAnnotation finds all indirect annotation") 55 unittest 56 { 57 struct annotationA; // @suppress(dscanner.style.phobos_naming_convention) 58 59 @annotationA 60 struct annotationB; // @suppress(dscanner.style.phobos_naming_convention) 61 62 @annotationB 63 struct Hello; 64 65 static assert(hasAnnotation!(annotationA, Hello) == true); 66 } 67 68 69 @("hasAnnotation is false when the annotation cannot be found") 70 unittest 71 { 72 struct annotationA; // @suppress(dscanner.style.phobos_naming_convention) 73 74 struct annotationB; // @suppress(dscanner.style.phobos_naming_convention) 75 76 @annotationB 77 struct Hello; 78 79 static assert(hasAnnotation!(annotationA, Hello) == false); 80 } 81 82 /// Finds all members of a symbol that have a specific annotation, directly or 83 /// indirectly. It returns a tuple of the name of each each member that has them 84 /// annotation. 85 template getMembersByAnnotation(alias symbol, alias uda) 86 { 87 alias getMembersByAnnotation = filterSymbolsByAnnotation!(uda, symbol, [__traits(allMembers, symbol)]); 88 } 89 90 private template filterSymbolsByAnnotation(alias uda, alias symbol, alias members) 91 if (members.length > 1) 92 { 93 alias filterSymbolsByAnnotation = AliasSeq!( 94 filterSymbolsByAnnotation!(uda, symbol, [members[0]]), 95 filterSymbolsByAnnotation!(uda, symbol, members[1..$]) 96 ); 97 } 98 99 private template filterSymbolsByAnnotation(alias uda, alias symbol, alias members) 100 if (members.length == 1) 101 { 102 alias member = __traits(getMember, symbol, members[0]); 103 static if (hasAnnotation!(uda, member)) 104 { 105 alias filterSymbolsByAnnotation = Alias!(member); 106 } 107 else 108 { 109 alias filterSymbolsByAnnotation = AliasSeq!(); 110 } 111 } 112 113 @("getSymbolsByAnnotation can find symbols with direct annotation") 114 unittest 115 { 116 struct annotation; // @suppress(dscanner.style.phobos_naming_convention) 117 118 static class MyClass 119 { 120 @annotation public void someMethod() {} 121 122 public void otherMethod() {} 123 } 124 125 alias annotations = getMembersByAnnotation!(MyClass, annotation); 126 static assert(annotations.length == 1); 127 static assert(__traits(identifier, annotations[0]) == "someMethod"); 128 } 129 130 @("getSymbolsByAnnotation can find symbols with indirect annotation") 131 unittest 132 { 133 struct annotationA; // @suppress(dscanner.style.phobos_naming_convention) 134 135 @annotationA 136 struct annotationB; // @suppress(dscanner.style.phobos_naming_convention) 137 138 static class MyClass 139 { 140 @annotationB public void someMethod() {} 141 142 public void otherMethod() {} 143 } 144 145 alias annotations = getMembersByAnnotation!(MyClass, annotationA); 146 static assert(annotations.length == 1); 147 static assert(__traits(identifier, annotations[0]) == "someMethod"); 148 } 149 150 @("getSymbolsByAnnotation returns empty tuple if none were found") 151 unittest 152 { 153 struct annotationA; // @suppress(dscanner.style.phobos_naming_convention) 154 static class MyClass 155 { 156 public void otherMethod() {} 157 } 158 159 alias annotations = getMembersByAnnotation!(MyClass, annotationA); 160 static assert(annotations.length == 0); 161 } 162 163 @("structures can contain third-party symbols with arguments") 164 unittest 165 { 166 static struct otherAnnotation 167 { 168 string value; 169 } 170 171 static struct myAnnotation; 172 173 static class MyClass 174 { 175 @otherAnnotation("foo") 176 public void methodA() {} 177 } 178 179 alias annotations = getMembersByAnnotation!(MyClass, myAnnotation); 180 static assert(annotations.length == 0); 181 } 182 183 @("methods can have multiple annotations") 184 unittest 185 { 186 struct annotationA; 187 188 struct annotationB; 189 190 static class MyClass 191 { 192 @annotationA 193 @annotationB 194 private int getValue() 195 { 196 return 5; 197 } 198 } 199 200 alias annotations = getMembersByAnnotation!(MyClass, annotationA); 201 static assert(annotations.length == 1); 202 }