1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 module thrift.internal.codegen; 21 22 import std.traits : InterfacesTuple, isSomeFunction, isSomeString; 23 import std.typetuple : staticIndexOf, staticMap, NoDuplicates, TypeTuple; 24 import thrift.codegen.base; 25 26 /** 27 * Removes all type qualifiers from T. 28 * 29 * In contrast to std.traits.Unqual, FullyUnqual also removes qualifiers from 30 * array elements (e.g. immutable(byte[]) -> byte[], not immutable(byte)[]), 31 * excluding strings (string isn't reduced to char[]). 32 */ 33 template FullyUnqual(T) { 34 static if (is(T _ == const(U), U)) { 35 alias FullyUnqual!U FullyUnqual; 36 } else static if (is(T _ == immutable(U), U)) { 37 alias FullyUnqual!U FullyUnqual; 38 } else static if (is(T _ == shared(U), U)) { 39 alias FullyUnqual!U FullyUnqual; 40 } else static if (is(T _ == U[], U) && !isSomeString!T) { 41 alias FullyUnqual!(U)[] FullyUnqual; 42 } else static if (is(T _ == V[K], K, V)) { 43 alias FullyUnqual!(V)[FullyUnqual!K] FullyUnqual; 44 } else { 45 alias T FullyUnqual; 46 } 47 } 48 49 /** 50 * true if null can be assigned to the passed type, false if not. 51 */ 52 template isNullable(T) { 53 enum isNullable = __traits(compiles, { T t = null; }); 54 } 55 56 template isStruct(T) { 57 enum isStruct = is(T == struct); 58 } 59 60 template isException(T) { 61 enum isException = is(T : Exception); 62 } 63 64 template isEnum(T) { 65 enum isEnum = is(T == enum); 66 } 67 68 /** 69 * Aliases itself to T.name. 70 */ 71 template GetMember(T, string name) { 72 mixin("alias T." ~ name ~ " GetMember;"); 73 } 74 75 /** 76 * Aliases itself to typeof(symbol). 77 */ 78 template TypeOf(alias symbol) { 79 alias typeof(symbol) TypeOf; 80 } 81 82 /** 83 * Aliases itself to the type of the T member called name. 84 */ 85 alias Compose!(TypeOf, GetMember) MemberType; 86 87 /** 88 * Returns the field metadata array for T if any, or an empty array otherwise. 89 */ 90 template getFieldMeta(T) if (isStruct!T || isException!T) { 91 static if (is(typeof(T.fieldMeta) == TFieldMeta[])) { 92 enum getFieldMeta = T.fieldMeta; 93 } else { 94 enum TFieldMeta[] getFieldMeta = []; 95 } 96 } 97 98 /** 99 * Merges the field metadata array for D with the passed array. 100 */ 101 template mergeFieldMeta(T, alias fieldMetaData = cast(TFieldMeta[])null) { 102 // Note: We don't use getFieldMeta here to avoid bug if it is instantiated 103 // from TIsSetFlags, see comment there. 104 static if (is(typeof(T.fieldMeta) == TFieldMeta[])) { 105 enum mergeFieldMeta = T.fieldMeta ~ fieldMetaData; 106 } else { 107 enum TFieldMeta[] mergeFieldMeta = fieldMetaData; 108 } 109 } 110 111 /** 112 * Returns the field requirement level for T.name. 113 */ 114 template memberReq(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) { 115 enum memberReq = memberReqImpl!(T, name, fieldMetaData).result; 116 } 117 118 private { 119 import std.algorithm : find; 120 // DMD @@BUG@@: Missing import leads to failing build without error 121 // message in unittest/debug/thrift/codegen/async_client. 122 import std.array : empty, front; 123 124 template memberReqImpl(T, string name, alias fieldMetaData) { 125 enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name); 126 static if (meta.empty || meta.front.req == TReq.AUTO) { 127 static if (isNullable!(MemberType!(T, name))) { 128 enum result = TReq.OPTIONAL; 129 } else { 130 enum result = TReq.REQUIRED; 131 } 132 } else { 133 enum result = meta.front.req; 134 } 135 } 136 } 137 138 139 template notIgnored(T, string name, alias fieldMetaData = cast(TFieldMeta[])null) { 140 enum notIgnored = memberReq!(T, name, fieldMetaData) != TReq.IGNORE; 141 } 142 143 /** 144 * Returns the method metadata array for T if any, or an empty array otherwise. 145 */ 146 template getMethodMeta(T) if (isService!T) { 147 static if (is(typeof(T.methodMeta) == TMethodMeta[])) { 148 enum getMethodMeta = T.methodMeta; 149 } else { 150 enum TMethodMeta[] getMethodMeta = []; 151 } 152 } 153 154 155 /** 156 * true if T.name is a member variable. Exceptions include methods, static 157 * members, artifacts like package aliases, … 158 */ 159 template isValueMember(T, string name) { 160 static if (!is(MemberType!(T, name))) { 161 enum isValueMember = false; 162 } else static if ( 163 is(MemberType!(T, name) == void) || 164 isSomeFunction!(MemberType!(T, name)) || 165 __traits(compiles, { return mixin("T." ~ name); }()) 166 ) { 167 enum isValueMember = false; 168 } else { 169 enum isValueMember = true; 170 } 171 } 172 173 /** 174 * Returns a tuple containing the names of the fields of T, not including 175 * inherited fields. If a member is marked as TReq.IGNORE, it is not included 176 * as well. 177 */ 178 template FieldNames(T, alias fieldMetaData = cast(TFieldMeta[])null) { 179 alias StaticFilter!( 180 All!( 181 PApply!(isValueMember, T), 182 PApply!(notIgnored, T, PApplySkip, fieldMetaData) 183 ), 184 __traits(derivedMembers, T) 185 ) FieldNames; 186 } 187 188 template derivedMembers(T) { 189 alias TypeTuple!(__traits(derivedMembers, T)) derivedMembers; 190 } 191 192 template AllMemberMethodNames(T) if (isService!T) { 193 alias NoDuplicates!( 194 FilterMethodNames!( 195 T, 196 staticMap!( 197 derivedMembers, 198 TypeTuple!(T, InterfacesTuple!T) 199 ) 200 ) 201 ) AllMemberMethodNames; 202 } 203 204 template FilterMethodNames(T, MemberNames...) { 205 alias StaticFilter!( 206 CompilesAndTrue!( 207 Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T)) 208 ), 209 MemberNames 210 ) FilterMethodNames; 211 } 212 213 /** 214 * Returns a type tuple containing only the elements of T for which the 215 * eponymous template predicate pred is true. 216 * 217 * Example: 218 * --- 219 * alias StaticFilter!(isIntegral, int, string, long, float[]) Filtered; 220 * static assert(is(Filtered == TypeTuple!(int, long))); 221 * --- 222 */ 223 template StaticFilter(alias pred, T...) { 224 static if (T.length == 0) { 225 alias TypeTuple!() StaticFilter; 226 } else static if (pred!(T[0])) { 227 alias TypeTuple!(T[0], StaticFilter!(pred, T[1 .. $])) StaticFilter; 228 } else { 229 alias StaticFilter!(pred, T[1 .. $]) StaticFilter; 230 } 231 } 232 233 /** 234 * Binds the first n arguments of a template to a particular value (where n is 235 * the number of arguments passed to PApply). 236 * 237 * The passed arguments are always applied starting from the left. However, 238 * the special PApplySkip marker template can be used to indicate that an 239 * argument should be skipped, so that e.g. the first and third argument 240 * to a template can be fixed, but the second and remaining arguments would 241 * still be left undefined. 242 * 243 * Skipping a number of parameters, but not providing enough arguments to 244 * assign all of them during instantiation of the resulting template is an 245 * error. 246 * 247 * Example: 248 * --- 249 * struct Foo(T, U, V) {} 250 * alias PApply!(Foo, int, long) PartialFoo; 251 * static assert(is(PartialFoo!float == Foo!(int, long, float))); 252 * 253 * alias PApply!(Test, int, PApplySkip, float) SkippedTest; 254 * static assert(is(SkippedTest!long == Test!(int, long, float))); 255 * --- 256 */ 257 template PApply(alias Target, T...) { 258 template PApply(U...) { 259 alias Target!(PApplyMergeArgs!(ConfinedTuple!T, U).Result) PApply; 260 } 261 } 262 263 /// Ditto. 264 template PApplySkip() {} 265 266 private template PApplyMergeArgs(alias Preset, Args...) { 267 static if (Preset.length == 0) { 268 alias Args Result; 269 } else { 270 enum nextSkip = staticIndexOf!(PApplySkip, Preset.Tuple); 271 static if (nextSkip == -1) { 272 alias TypeTuple!(Preset.Tuple, Args) Result; 273 } else static if (Args.length == 0) { 274 // Have to use a static if clause instead of putting the condition 275 // directly into the assert to avoid DMD trying to access Args[0] 276 // nevertheless below. 277 static assert(false, 278 "PArgsSkip encountered, but no argument left to bind."); 279 } else { 280 alias TypeTuple!( 281 Preset.Tuple[0 .. nextSkip], 282 Args[0], 283 PApplyMergeArgs!( 284 ConfinedTuple!(Preset.Tuple[nextSkip + 1 .. $]), 285 Args[1 .. $] 286 ).Result 287 ) Result; 288 } 289 } 290 } 291 292 unittest { 293 struct Test(T, U, V) {} 294 alias PApply!(Test, int, long) PartialTest; 295 static assert(is(PartialTest!float == Test!(int, long, float))); 296 297 alias PApply!(Test, int, PApplySkip, float) SkippedTest; 298 static assert(is(SkippedTest!long == Test!(int, long, float))); 299 300 alias PApply!(Test, int, PApplySkip, PApplySkip) TwoSkipped; 301 static assert(!__traits(compiles, TwoSkipped!long)); 302 } 303 304 305 /** 306 * Composes a number of templates. The result is a template equivalent to 307 * all the passed templates evaluated from right to left, akin to the 308 * mathematical function composition notation: Instantiating Compose!(A, B, C) 309 * is the same as instantiating A!(B!(C!(…))). 310 * 311 * This is especially useful for creating a template to use with staticMap/ 312 * StaticFilter, as demonstrated below. 313 * 314 * Example: 315 * --- 316 * template AllMethodNames(T) { 317 * alias StaticFilter!( 318 * CompilesAndTrue!( 319 * Compose!(isSomeFunction, TypeOf, PApply!(GetMember, T)) 320 * ), 321 * __traits(allMembers, T) 322 * ) AllMethodNames; 323 * } 324 * 325 * pragma(msg, AllMethodNames!Object); 326 * --- 327 */ 328 template Compose(T...) { 329 static if (T.length == 0) { 330 template Compose(U...) { 331 alias U Compose; 332 } 333 } else { 334 template Compose(U...) { 335 alias Instantiate!(T[0], Instantiate!(.Compose!(T[1 .. $]), U)) Compose; 336 } 337 } 338 } 339 340 /** 341 * Instantiates the given template with the given list of parameters. 342 * 343 * Used to work around syntactic limiations of D with regard to instantiating 344 * a template from a type tuple (e.g. T[0]!(...) is not valid) or a template 345 * returning another template (e.g. Foo!(Bar)!(Baz) is not allowed). 346 */ 347 template Instantiate(alias Template, Params...) { 348 alias Template!Params Instantiate; 349 } 350 351 /** 352 * Combines several template predicates using logical AND, i.e. instantiating 353 * All!(a, b, c) with parameters P for some templates a, b, c is equivalent to 354 * a!P && b!P && c!P. 355 * 356 * The templates are evaluated from left to right, aborting evaluation in a 357 * shurt-cut manner if a false result is encountered, in which case the latter 358 * instantiations do not need to compile. 359 */ 360 template All(T...) { 361 static if (T.length == 0) { 362 template All(U...) { 363 enum All = true; 364 } 365 } else { 366 template All(U...) { 367 static if (Instantiate!(T[0], U)) { 368 alias Instantiate!(.All!(T[1 .. $]), U) All; 369 } else { 370 enum All = false; 371 } 372 } 373 } 374 } 375 376 /** 377 * Combines several template predicates using logical OR, i.e. instantiating 378 * Any!(a, b, c) with parameters P for some templates a, b, c is equivalent to 379 * a!P || b!P || c!P. 380 * 381 * The templates are evaluated from left to right, aborting evaluation in a 382 * shurt-cut manner if a true result is encountered, in which case the latter 383 * instantiations do not need to compile. 384 */ 385 template Any(T...) { 386 static if (T.length == 0) { 387 template Any(U...) { 388 enum Any = false; 389 } 390 } else { 391 template Any(U...) { 392 static if (Instantiate!(T[0], U)) { 393 enum Any = true; 394 } else { 395 alias Instantiate!(.Any!(T[1 .. $]), U) Any; 396 } 397 } 398 } 399 } 400 401 template ConfinedTuple(T...) { 402 alias T Tuple; 403 enum length = T.length; 404 } 405 406 /* 407 * foreach (Item; Items) { 408 * List = Operator!(Item, List); 409 * } 410 * where Items is a ConfinedTuple and List is a type tuple. 411 */ 412 template ForAllWithList(alias Items, alias Operator, List...) if ( 413 is(typeof(Items.length) : size_t) 414 ){ 415 static if (Items.length == 0) { 416 alias List ForAllWithList; 417 } else { 418 alias .ForAllWithList!( 419 ConfinedTuple!(Items.Tuple[1 .. $]), 420 Operator, 421 Operator!(Items.Tuple[0], List) 422 ) ForAllWithList; 423 } 424 } 425 426 /** 427 * Wraps the passed template predicate so it returns true if it compiles and 428 * evaluates to true, false it it doesn't compile or evaluates to false. 429 */ 430 template CompilesAndTrue(alias T) { 431 template CompilesAndTrue(U...) { 432 static if (is(typeof(T!U) : bool)) { 433 enum bool CompilesAndTrue = T!U; 434 } else { 435 enum bool CompilesAndTrue = false; 436 } 437 } 438 }