1 /** 2 Some tools to help to auto-generate interface implementation. 3 4 Copyright: © 2018 Eliott Dumeix 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 */ 7 module autointf; 8 9 import std.traits : hasUDA; 10 import vibe.internal.meta.uda : onlyAsUda; 11 12 public import std.traits : ReturnType; 13 14 /// Methods marked with this attribute will not be auto implemented. 15 package struct NoAutoImplementMethod 16 { 17 } 18 19 NoAutoImplementMethod noAutoImplement() @safe 20 { 21 if (!__ctfe) 22 assert(false, onlyAsUda!__FUNCTION__); 23 return NoAutoImplementMethod(); 24 } 25 26 /// attributes utils 27 private enum isEnabledMethod(alias M) = !hasUDA!(M, NoAutoImplementMethod); 28 29 /// Base class for creating a context conserved during the invocation process. 30 class UserContext 31 { 32 } 33 34 struct SubInterface 35 { 36 37 } 38 39 /// Provides all necessary informations to implement an automated interface or class. 40 /// inspired by /web/vibe/web/internal/rest/common.d 41 struct InterfaceInfo(T) if (is(T == class) || is(T == interface)) 42 { 43 @safe: 44 45 import std.meta : Filter; 46 import std.traits : FunctionTypeOf, InterfacesTuple, MemberFunctionsTuple, 47 ParameterIdentifierTuple, ParameterStorageClass, 48 ParameterStorageClassTuple, ParameterTypeTuple, ReturnType; 49 import std.typetuple : TypeTuple; 50 import vibe.internal.meta.funcattr : IsAttributedParameter; 51 import vibe.internal.meta.traits : derivedMethod; 52 import vibe.internal.meta.uda; 53 54 // determine the implementation interface I and check for validation errors 55 alias BaseInterfaces = InterfacesTuple!T; 56 57 // some static checks 58 static assert(BaseInterfaces.length > 0 || is(T == interface), 59 "Cannot get interface infos for type '" ~ T.stringof ~ "' because it doesn't implement an interface"); 60 static if (BaseInterfaces.length > 1) 61 pragma(msg, "Type '" ~ T.stringof 62 ~ "' implements more than one interface: make sure the one describing the auto interface is the first one"); 63 64 // alias the base interface 65 static if (is(T == interface)) 66 alias I = T; 67 else 68 alias I = BaseInterfaces[0]; 69 70 /// The name of each interface member (Runtime). 71 enum memberNames = [__traits(allMembers, I)]; 72 73 /// Aliases to all interface methods (Compile-time). 74 alias Members = GetMembers!(); 75 76 /** Aliases for each method (Compile-time). 77 This tuple has the same number of entries as `methods`. */ 78 alias Methods = GetMethods!(); 79 80 /// Number of methods (Runtime). 81 enum methodCount = Methods.length; 82 83 /** Information about each route (Runtime). 84 This array has the same number of fields as `RouteFunctions`. */ 85 MethodInfo[methodCount] methods; 86 87 /// Static information about each route (Compile-time). 88 static if (methodCount) 89 static const StaticMethodInfo[methodCount] staticMethods = computeStaticRoutes(); 90 else 91 static const StaticMethodInfo[0] staticMethods; 92 93 /// Fills the struct with information. 94 this(int dummy) 95 { 96 computeMethods(); 97 } 98 99 // copying this struct is costly, so we forbid it 100 @disable this(this); 101 102 private void computeMethods() 103 { 104 import std.algorithm.searching : any; 105 106 foreach (si, RF; Methods) 107 { 108 enum smethod = staticMethods[si]; 109 110 MethodInfo route; 111 route.name = smethod.name; 112 113 route.parameters.length = smethod.parameters.length; 114 115 bool prefix_id = false; 116 117 alias PT = ParameterTypeTuple!RF; 118 foreach (i, _; PT) 119 { 120 enum sparam = smethod.parameters[i]; 121 ParameterInfo pi; 122 pi.name = sparam.name; 123 124 route.parameters[i] = pi; 125 } 126 127 methods[si] = route; 128 } 129 } 130 131 // //////////////////////////////////////////////////////////////////////// 132 // compile time methods 133 // //////////////////////////////////////////////////////////////////////// 134 private template SubInterfaceType(alias F) 135 { 136 import std.traits : ReturnType, isInstanceOf; 137 138 alias RT = ReturnType!F; 139 static if (is(RT == interface)) 140 alias SubInterfaceType = RT; 141 else 142 alias SubInterfaceType = void; 143 } 144 145 private template GetMembers() 146 { 147 template Impl(size_t idx) 148 { 149 static if (idx < memberNames.length) 150 { 151 enum name = memberNames[idx]; 152 static if (name.length != 0) 153 alias Impl = TypeTuple!(MemberFunctionsTuple!(I, name), Impl!(idx + 1)); 154 else 155 alias Impl = Impl!(idx + 1); 156 } 157 else 158 alias Impl = TypeTuple!(); 159 } 160 161 alias GetMembers = Impl!0; 162 } 163 164 private template GetMethods() 165 { 166 template Impl(size_t idx) 167 { 168 static if (idx < Members.length) 169 { 170 alias F = Members[idx]; 171 alias SI = SubInterfaceType!F; 172 static if (is(SI == void) && isEnabledMethod!F) 173 alias Impl = TypeTuple!(F, Impl!(idx + 1)); 174 else 175 alias Impl = Impl!(idx + 1); 176 } 177 else 178 alias Impl = TypeTuple!(); 179 } 180 181 alias GetMethods = Impl!0; 182 } 183 184 private static StaticMethodInfo[methodCount] computeStaticRoutes() 185 { 186 static import std.traits; 187 import std.algorithm.searching : any, count; 188 import std.algorithm : countUntil; 189 import std.meta : AliasSeq; 190 191 assert(__ctfe); 192 193 StaticMethodInfo[methodCount] ret; 194 195 foreach (fi, func; Methods) 196 { 197 StaticMethodInfo sroute; 198 sroute.name = __traits(identifier, func); 199 200 static if (!is(T == I)) 201 alias cfunc = derivedMethod!(T, func); 202 else 203 alias cfunc = func; 204 205 alias FuncType = FunctionTypeOf!func; 206 alias ParameterTypes = ParameterTypeTuple!FuncType; 207 alias ReturnType = std.traits.ReturnType!FuncType; 208 enum parameterNames = [ParameterIdentifierTuple!func]; 209 210 // get some meta 211 enum name = __traits(identifier, func); 212 213 sroute.name = name; 214 215 foreach (i, PT; ParameterTypes) 216 { 217 enum pname = parameterNames[i]; 218 219 // Comparison template for anySatisfy 220 // template Cmp(WebParamAttribute attr) { enum Cmp = (attr.identifier == ParamNames[i]); } 221 // alias CompareParamName = GenCmp!("Loop"~func.mangleof, i, parameterNames[i]); 222 // mixin(CompareParamName.Decl); 223 StaticParameterInfo pi; 224 pi.name = parameterNames[i]; 225 226 // determine in/out storage class 227 enum SC = ParameterStorageClassTuple!func[i]; 228 static assert(!(SC & ParameterStorageClass.out_)); 229 230 sroute.parameters ~= pi; 231 } 232 233 ret[fi] = sroute; 234 } 235 236 return ret; 237 } 238 } 239 240 /// 241 unittest 242 { 243 import std.typecons : tuple; 244 import std.traits; 245 246 @("api") 247 interface IAPI 248 { 249 @("value") 250 string hello(int number); 251 252 @noAutoImplement() 253 void disabledMethod(); 254 } 255 256 alias Info = InterfaceInfo!IAPI; // Compile-time infos 257 auto info = new Info(0); // Runtime infos 258 259 // Runtime infos 260 assert(info.memberNames.length == 2); 261 assert(info.memberNames[0] == "hello"); 262 assert(info.memberNames[1] == "disabledMethod"); 263 assert(info.methodCount == 1 ); 264 assert(info.methods.length == 1); 265 assert(info.methods[0].name == "hello"); 266 assert(info.methods[0].parameters.length == 1); 267 assert(info.methods[0].parameters[0].name == "number"); 268 269 // Compile time 270 static assert(Info.Members.length == 2); 271 static assert(Info.Methods.length == 1); 272 273 static assert(Info.staticMethods.length == 1); 274 static assert(Info.staticMethods[0].name == "hello"); 275 static assert(Info.staticMethods[0].parameters.length == 1); 276 static assert(Info.staticMethods[0].parameters[0].name == "number"); 277 } 278 279 280 /// Static informations about a method. 281 struct StaticMethodInfo 282 { 283 string name; // D name of the function 284 StaticParameterInfo[] parameters; 285 } 286 287 /// Static informations about a method parameter. 288 struct StaticParameterInfo 289 { 290 string name; 291 } 292 293 /// Informations about a method. 294 struct MethodInfo 295 { 296 string name; // D name of the function 297 ParameterInfo[] parameters; 298 } 299 300 /// Informations about a method parameter. 301 struct ParameterInfo 302 { 303 string name; 304 } 305 306 string autoImplementMethods(I, alias ExecuteMethod)() 307 { 308 import std.array : join; 309 import std..string : format; 310 import std.traits : ParameterIdentifierTuple; 311 312 alias Info = InterfaceInfo!I; 313 314 // add required import 315 string ret = q{ 316 import vibe.internal.meta.codegen : CloneFunction; 317 }; 318 319 // generate method implementation 320 foreach (i, F; Info.Methods) 321 { 322 alias ParamNames = ParameterIdentifierTuple!F; 323 324 static if (ParamNames.length == 0) 325 enum pnames = ""; 326 else 327 enum pnames = [ParamNames].join(", "); 328 329 ret ~= q{ 330 mixin CloneFunction!(%3$s, q{ 331 return %2$s!(%3$s)(%1$s); 332 }, true); 333 }.format(pnames, __traits(identifier, ExecuteMethod), __traits(identifier, F)); 334 } 335 336 return ret; 337 } 338 339 unittest 340 { 341 class AutoFunctionName(I) : I 342 { 343 private ReturnType!Func executeMethod(alias Func, ARGS...)(ARGS arg) 344 { 345 import std.traits; 346 import std.conv : to; 347 348 // retrieve some compile time informations 349 alias RT = ReturnType!Func; 350 alias PTT = ParameterTypeTuple!Func; 351 352 string ret; 353 354 foreach (i, PT; PTT) 355 { 356 ret ~= to!string(arg[i]); 357 } 358 359 return __traits(identifier, Func) ~ ret; 360 } 361 362 mixin(autoImplementMethods!(I, executeMethod)); 363 } 364 365 interface ISubAPi 366 { 367 string getNumber(int n); 368 369 @noAutoImplement() 370 final string foo() 371 { 372 return "foo"; 373 } 374 } 375 376 interface IAPI : ISubAPi 377 { 378 @("foo") string hello(int number, string str); 379 380 string helloWorld(); 381 } 382 383 auto api = new AutoFunctionName!IAPI(); 384 385 assert(api.hello(42, "foo") == "hello42foo"); 386 assert(api.helloWorld() == "helloWorld"); 387 assert(api.getNumber(12) == "getNumber12"); 388 }