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