1 /** 2 Templates and CTFE-functions useful for type introspection during code generation. 3 4 Some of those are very similar to `traits` utilities but instead of general type 5 information focus on properties that are most important during such code generation. 6 7 Copyright: © 2013 RejectedSoftware e.K. 8 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 9 Authors: Sönke Ludwig, Михаил Страшун 10 */ 11 12 module vson.meta.codegen; 13 14 import std.traits : FunctionTypeOf, isSomeFunction; 15 16 /* 17 As user types defined inside unittest blocks don't have proper parent 18 module, those need to be defined outside for tests that require module 19 inspection for some reasons. All such tests use single declaration 20 compiled in this module in unittest version. 21 */ 22 version(unittest) 23 { 24 private: 25 interface TestInterface 26 { 27 static struct Inner 28 { 29 } 30 31 const(Inner[]) func1(ref string name); 32 ref int func1(); 33 shared(Inner[4]) func2(...) const; 34 immutable(int[string]) func3(in Inner anotherName) @safe; 35 } 36 } 37 38 /** 39 For a given type T finds all user-defined symbols it embeds. 40 41 Important property of such symbols is that they are likely to 42 need an explicit import if used in some other scope / module. 43 44 Implementation is incomplete and tuned for REST interface generation needs. 45 46 Params: 47 T = type to introspect for qualified symbols 48 49 Returns: 50 tuple of "interesting" symbols, no duplicates 51 */ 52 template getSymbols(T) 53 { 54 import std.typetuple : TypeTuple, NoDuplicates; 55 import std.traits; 56 57 private template Implementation(T) 58 { 59 static if (isAggregateType!T || is(T == enum)) { 60 alias Implementation = TypeTuple!T; 61 } 62 else static if (isStaticArray!T || isArray!T) { 63 alias Implementation = Implementation!(typeof(T.init[0])); 64 } 65 else static if (isAssociativeArray!T) { 66 alias Implementation = TypeTuple!( 67 Implementation!(ValueType!T), 68 Implementation!(KeyType!T) 69 ); 70 } 71 else static if (isPointer!T) { 72 alias Implementation = Implementation!(PointerTarget!T); 73 } 74 else 75 alias Implementation = TypeTuple!(); 76 } 77 78 alias getSymbols = NoDuplicates!(Implementation!T); 79 } 80 81 /// 82 unittest 83 { 84 import std.typetuple : TypeTuple; 85 86 struct A {} 87 interface B {} 88 alias Type = A[const(B[A*])]; 89 90 // can't directly compare tuples thus comparing their string representation 91 static assert (getSymbols!Type.stringof == TypeTuple!(A, B).stringof); 92 static assert (getSymbols!int.stringof == TypeTuple!().stringof); 93 } 94 95 /** 96 For a given interface I finds all modules that types in its methods 97 come from. 98 99 These modules need to be imported in the scope code generated from I 100 is used to avoid errors with unresolved symbols for user types. 101 102 Params: 103 I = interface to inspect 104 105 Returns: 106 list of module name strings, no duplicates 107 */ 108 string[] getRequiredImports(I)() 109 if (is(I == interface)) 110 { 111 import std.traits : MemberFunctionsTuple, moduleName, 112 ParameterTypeTuple, ReturnType; 113 114 if( !__ctfe ) 115 assert(false); 116 117 bool[string] visited; 118 string[] ret; 119 120 void addModule(string name) 121 { 122 if (name !in visited) { 123 ret ~= name; 124 visited[name] = true; 125 } 126 } 127 128 foreach (method; __traits(allMembers, I)) { 129 foreach (overload; MemberFunctionsTuple!(I, method)) { 130 alias FuncType = FunctionTypeOf!overload; 131 132 foreach (symbol; getSymbols!(ReturnType!FuncType)) { 133 static if (__traits(compiles, moduleName!symbol)) { 134 addModule(moduleName!symbol); 135 } 136 } 137 138 foreach (P; ParameterTypeTuple!FuncType) { 139 foreach (symbol; getSymbols!P) { 140 static if (__traits(compiles, moduleName!symbol)) { 141 addModule(moduleName!symbol); 142 } 143 } 144 } 145 } 146 } 147 148 return ret; 149 } 150 151 /// 152 unittest 153 { 154 // `Test` is an interface using single user type 155 enum imports = getRequiredImports!TestInterface; 156 static assert (imports.length == 1); 157 static assert (imports[0] == "vson.meta.codegen"); 158 } 159 160 /** 161 * Returns a Tuple of the parameters. 162 * It can be used to declare function. 163 */ 164 template ParameterTuple(alias Func) 165 { 166 static if (is(FunctionTypeOf!Func Params == __parameters)) { 167 alias ParameterTuple = Params; 168 } else static assert(0, "Argument to ParameterTuple must be a function"); 169 } 170 171 /// 172 unittest 173 { 174 void foo(string val = "Test", int = 10); 175 void bar(ParameterTuple!foo) { assert(val == "Test"); } 176 // Variadic functions require special handling: 177 import core.vararg; 178 void foo2(string val, ...); 179 void bar2(ParameterTuple!foo2, ...) { assert(val == "42"); } 180 181 bar(); 182 bar2("42"); 183 184 // Note: outside of a parameter list, it's value is the type of the param. 185 import std.traits : ParameterDefaultValueTuple; 186 ParameterTuple!(foo)[0] test = ParameterDefaultValueTuple!(foo)[0]; 187 assert(test == "Test"); 188 } 189 190 /// Returns a Tuple containing a 1-element parameter list, with an optional default value. 191 /// Can be used to concatenate a parameter to a parameter list, or to create one. 192 template ParameterTuple(T, string identifier, TUnused = void) 193 { 194 import std..string : format; 195 mixin(q{private void __func(T %s);}.format(identifier)); 196 alias ParameterTuple = ParameterTuple!__func; 197 } 198 199 200 /// Ditto 201 template ParameterTuple(T, string identifier, T DefVal) 202 { 203 import std..string : format; 204 mixin(q{private void __func(T %s = DefVal);}.format(identifier)); 205 alias ParameterTuple = ParameterTuple!__func; 206 } 207 208 /// 209 unittest 210 { 211 void foo(ParameterTuple!(int, "arg2")) { assert(arg2 == 42); } 212 foo(42); 213 214 void bar(string arg); 215 void bar2(ParameterTuple!bar, ParameterTuple!(string, "val")) { assert(val == arg); } 216 bar2("isokay", "isokay"); 217 218 // For convenience, you can directly pass the result of std.traits.ParameterDefaultValueTuple 219 // without checking for void. 220 import std.traits : PDVT = ParameterDefaultValueTuple; 221 import std.traits : arity; 222 void baz(string test, int = 10); 223 224 static assert(is(PDVT!(baz)[0] == void)); 225 // void baz2(string test2, string test); 226 void baz2(ParameterTuple!(string, "test2", PDVT!(baz)[0]), ParameterTuple!(baz)[0..$-1]) { assert(test == test2); } 227 static assert(arity!baz2 == 2); 228 baz2("Try", "Try"); 229 230 // void baz3(string test, int = 10, int ident = 10); 231 void baz3(ParameterTuple!baz, ParameterTuple!(int, "ident", PDVT!(baz)[1])) { assert(ident == 10); } 232 baz3("string"); 233 } 234 235 /// Returns a string of the functions attributes, suitable to be mixed 236 /// on the LHS of the function declaration. 237 /// 238 /// Unfortunately there is no "nice" syntax for declaring a function, 239 /// so we have to resort on string for functions attributes. 240 template FuncAttributes(alias Func) 241 { 242 static if (__VERSION__ <= 2065) 243 { 244 import std.traits; 245 enum FuncAttributes = { 246 alias FA = FunctionAttribute; 247 string res; 248 enum attr = functionAttributes!Func; 249 if (attr & FA.nothrow_) res ~= "nothrow "; 250 if (attr & FA.property) res ~= "@property "; 251 if (attr & FA.pure_) res ~= "pure "; 252 if (attr & FA.ref_) res ~= "ref "; 253 if (attr & FA.safe) res ~= "@safe "; 254 if (attr & FA.trusted) res ~= "@trusted "; 255 static if (is(FunctionTypeOf!Func == const)) res ~= "const "; 256 static if (is(FunctionTypeOf!Func == immutable)) res ~= "immutable "; 257 static if (is(FunctionTypeOf!Func == inout)) res ~= "inout "; 258 static if (is(FunctionTypeOf!Func == shared)) res ~= "shared "; 259 return res.length ? res[0 .. $-1] : res; 260 }(); 261 } 262 else 263 { 264 import std.array : join; 265 enum FuncAttributes = [__traits(getFunctionAttributes, Func)].join(" "); 266 } 267 } 268 269 270 271 /// A template mixin which allow you to clone a function, and specify the implementation. 272 mixin template CloneFunction(alias Func, string body_, string identifier = __traits(identifier, Func)) 273 { 274 // Template mixin: everything has to be self-contained. 275 import std..string : format; 276 import std.traits : ReturnType; 277 import vson.meta.codegen : ParameterTuple, FuncAttributes; 278 // Sadly this is not possible: 279 // class Test { 280 // int foo(string par) pure @safe nothrow { /* ... */ } 281 // typeof(foo) bar { 282 // return foo(par); 283 // } 284 // } 285 mixin(q{ 286 ReturnType!Func %s(ParameterTuple!Func) %s { 287 %s 288 } 289 }.format(identifier, FuncAttributes!Func, body_)); 290 } 291 292 /// 293 unittest 294 { 295 interface ITest 296 { 297 int foo(string par, int, string p = "foo", int = 10) pure @safe nothrow const; 298 @property int foo2() pure @safe nothrow const; 299 } 300 301 class Test : ITest 302 { 303 mixin CloneFunction!(ITest.foo, q{ 304 return 84; 305 }, "customname"); 306 override: 307 mixin CloneFunction!(ITest.foo, q{ 308 return 42; 309 }); 310 mixin CloneFunction!(ITest.foo2, q{ 311 return 42; 312 }); 313 } 314 315 assert(new Test().foo("", 21) == 42); 316 assert(new Test().foo2 == 42); 317 assert(new Test().customname("", 21) == 84); 318 }